'use client' import * as React from 'react' import { motion, type Transition, type HTMLMotionProps } from 'motion/react' import { cn } from '@/lib/utils' import { MotionHighlight, MotionHighlightItem } from '@/components/ui/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( 'ring-offset-background focus-visible:ring-ring data-[state=active]:text-foreground z-[1] inline-flex size-full cursor-pointer items-center justify-center rounded-sm px-2 py-1 text-sm font-medium whitespace-nowrap transition-transform focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50', 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 }