mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-08-18 17:06:15 +00:00
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import * as React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface TabsContextValue {
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
}
|
|
|
|
const TabsContext = React.createContext<TabsContextValue | undefined>(undefined);
|
|
|
|
function useTabs() {
|
|
const context = React.useContext(TabsContext);
|
|
if (!context) {
|
|
throw new Error('useTabs must be used within a Tabs');
|
|
}
|
|
return context;
|
|
}
|
|
|
|
interface TabsProps {
|
|
defaultValue?: string;
|
|
value?: string;
|
|
onValueChange?: (value: string) => void;
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
const Tabs: React.FC<TabsProps> = ({
|
|
defaultValue,
|
|
value: controlledValue,
|
|
onValueChange,
|
|
children,
|
|
className,
|
|
}) => {
|
|
const [internalValue, setInternalValue] = React.useState(defaultValue || '');
|
|
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
|
|
const handleValueChange = (newValue: string) => {
|
|
if (controlledValue === undefined) {
|
|
setInternalValue(newValue);
|
|
}
|
|
onValueChange?.(newValue);
|
|
};
|
|
|
|
return (
|
|
<TabsContext.Provider value={{ value, onValueChange: handleValueChange }}>
|
|
<div className={className}>{children}</div>
|
|
</TabsContext.Provider>
|
|
);
|
|
};
|
|
|
|
const TabsList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
({ className, ...props }, ref) => (
|
|
<div
|
|
ref={ref}
|
|
className={cn(
|
|
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
);
|
|
TabsList.displayName = 'TabsList';
|
|
|
|
interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
value: string;
|
|
}
|
|
|
|
const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
|
|
({ className, value: triggerValue, onClick, ...props }, ref) => {
|
|
const { value, onValueChange } = useTabs();
|
|
const isActive = value === triggerValue;
|
|
|
|
return (
|
|
<button
|
|
ref={ref}
|
|
className={cn(
|
|
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none',
|
|
isActive
|
|
? 'bg-background text-foreground shadow-sm'
|
|
: 'hover:bg-accent hover:text-accent-foreground',
|
|
className
|
|
)}
|
|
onClick={(e) => {
|
|
onValueChange(triggerValue);
|
|
onClick?.(e);
|
|
}}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
);
|
|
TabsTrigger.displayName = 'TabsTrigger';
|
|
|
|
interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
value: string;
|
|
}
|
|
|
|
const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(
|
|
({ className, value: contentValue, ...props }, ref) => {
|
|
const { value } = useTabs();
|
|
if (value !== contentValue) return null;
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={cn(
|
|
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
);
|
|
TabsContent.displayName = 'TabsContent';
|
|
|
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|