'use client'; import * as React from 'react'; import { motion, type Transition, type HTMLMotionProps } from 'motion/react'; import { cn } from '@workspace/ui/lib/utils'; import { MotionHighlight, MotionHighlightItem, } from '@/registry/effects/motion-highlight'; type TabsContextType = { activeValue: T; handleValueChange: (value: T) => void; registerTrigger: (value: T, node: HTMLElement | null) => void; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const TabsContext = React.createContext | undefined>( undefined, ); function useTabs(): TabsContextType { const context = React.useContext(TabsContext); if (!context) { throw new Error('useTabs must be used within a TabsProvider'); } return context; } type BaseTabsProps = React.ComponentProps<'div'> & { children: React.ReactNode; }; type UnControlledTabsProps = BaseTabsProps & { defaultValue?: T; value?: never; onValueChange?: never; }; type ControlledTabsProps = BaseTabsProps & { value: T; onValueChange?: (value: T) => void; defaultValue?: never; }; type TabsProps = | UnControlledTabsProps | ControlledTabsProps; function Tabs({ defaultValue, value, onValueChange, children, className, ...props }: TabsProps) { const [activeValue, setActiveValue] = React.useState( defaultValue ?? undefined, ); const triggersRef = React.useRef(new Map()); const initialSet = React.useRef(false); const isControlled = value !== undefined; React.useEffect(() => { if ( !isControlled && activeValue === undefined && triggersRef.current.size > 0 && !initialSet.current ) { const firstTab = Array.from(triggersRef.current.keys())[0]; setActiveValue(firstTab as T); initialSet.current = true; } }, [activeValue, isControlled]); const registerTrigger = (value: string, node: HTMLElement | null) => { if (node) { triggersRef.current.set(value, node); if (!isControlled && activeValue === undefined && !initialSet.current) { setActiveValue(value as T); initialSet.current = true; } } else { triggersRef.current.delete(value); } }; const handleValueChange = (val: T) => { if (!isControlled) setActiveValue(val); else onValueChange?.(val); }; return (
{children}
); } type TabsListProps = React.ComponentProps<'div'> & { children: React.ReactNode; activeClassName?: string; transition?: Transition; }; function TabsList({ children, className, activeClassName, transition = { type: 'spring', stiffness: 200, damping: 25, }, ...props }: TabsListProps) { const { activeValue } = useTabs(); return (
{children}
); } type TabsTriggerProps = HTMLMotionProps<'button'> & { value: string; children: React.ReactNode; }; function TabsTrigger({ ref, value, children, className, ...props }: TabsTriggerProps) { const { activeValue, handleValueChange, registerTrigger } = useTabs(); const localRef = React.useRef(null); React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement); React.useEffect(() => { registerTrigger(value, localRef.current); return () => registerTrigger(value, null); }, [value, registerTrigger]); return ( handleValueChange(value)} data-state={activeValue === value ? 'active' : 'inactive'} className={cn( 'inline-flex cursor-pointer items-center size-full justify-center whitespace-nowrap rounded-sm px-2 py-1 text-sm font-medium ring-offset-background transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground z-[1]', className, )} {...props} > {children} ); } type TabsContentsProps = React.ComponentProps<'div'> & { children: React.ReactNode; transition?: Transition; }; function TabsContents({ children, className, transition = { type: 'spring', stiffness: 300, damping: 30, bounce: 0, restDelta: 0.01, }, ...props }: TabsContentsProps) { const { activeValue } = useTabs(); const childrenArray = React.Children.toArray(children); const activeIndex = childrenArray.findIndex( (child): child is React.ReactElement<{ value: string }> => React.isValidElement(child) && typeof child.props === 'object' && child.props !== null && 'value' in child.props && child.props.value === activeValue, ); return (
{childrenArray.map((child, index) => (
{child}
))}
); } type TabsContentProps = HTMLMotionProps<'div'> & { value: string; children: React.ReactNode; }; function TabsContent({ children, value, className, ...props }: TabsContentProps) { const { activeValue } = useTabs(); const isActive = activeValue === value; return ( {children} ); } export { Tabs, TabsList, TabsTrigger, TabsContents, TabsContent, useTabs, type TabsContextType, type TabsProps, type TabsListProps, type TabsTriggerProps, type TabsContentsProps, type TabsContentProps, };