{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "tabs", "type": "registry:ui", "title": "Tabs", "description": "A set of layered sections with the same height of content—known as tab panels—that are displayed one at a time.", "dependencies": [ "motion" ], "registryDependencies": [ "https://animate-ui.com/r/motion-highlight" ], "files": [ { "path": "registry/components/tabs/index.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, type Transition, type HTMLMotionProps } from 'motion/react';\n\nimport { cn } from '@/lib/utils';\nimport {\n MotionHighlight,\n MotionHighlightItem,\n} from '@/components/animate-ui/effects/motion-highlight';\n\ntype TabsContextType = {\n activeValue: T;\n handleValueChange: (value: T) => void;\n registerTrigger: (value: T, node: HTMLElement | null) => void;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst TabsContext = React.createContext | undefined>(\n undefined,\n);\n\nfunction useTabs(): TabsContextType {\n const context = React.useContext(TabsContext);\n if (!context) {\n throw new Error('useTabs must be used within a TabsProvider');\n }\n return context;\n}\n\ntype BaseTabsProps = React.ComponentProps<'div'> & {\n children: React.ReactNode;\n};\n\ntype UnControlledTabsProps = BaseTabsProps & {\n defaultValue?: T;\n value?: never;\n onValueChange?: never;\n};\n\ntype ControlledTabsProps = BaseTabsProps & {\n value: T;\n onValueChange?: (value: T) => void;\n defaultValue?: never;\n};\n\ntype TabsProps =\n | UnControlledTabsProps\n | ControlledTabsProps;\n\nfunction Tabs({\n defaultValue,\n value,\n onValueChange,\n children,\n className,\n ...props\n}: TabsProps) {\n const [activeValue, setActiveValue] = React.useState(\n defaultValue ?? undefined,\n );\n const triggersRef = React.useRef(new Map());\n const initialSet = React.useRef(false);\n const isControlled = value !== undefined;\n\n React.useEffect(() => {\n if (\n !isControlled &&\n activeValue === undefined &&\n triggersRef.current.size > 0 &&\n !initialSet.current\n ) {\n const firstTab = Array.from(triggersRef.current.keys())[0];\n setActiveValue(firstTab as T);\n initialSet.current = true;\n }\n }, [activeValue, isControlled]);\n\n const registerTrigger = (value: string, node: HTMLElement | null) => {\n if (node) {\n triggersRef.current.set(value, node);\n if (!isControlled && activeValue === undefined && !initialSet.current) {\n setActiveValue(value as T);\n initialSet.current = true;\n }\n } else {\n triggersRef.current.delete(value);\n }\n };\n\n const handleValueChange = (val: T) => {\n if (!isControlled) setActiveValue(val);\n else onValueChange?.(val);\n };\n\n return (\n \n \n {children}\n \n \n );\n}\n\ntype TabsListProps = React.ComponentProps<'div'> & {\n children: React.ReactNode;\n activeClassName?: string;\n transition?: Transition;\n};\n\nfunction TabsList({\n children,\n className,\n activeClassName,\n transition = {\n type: 'spring',\n stiffness: 200,\n damping: 25,\n },\n ...props\n}: TabsListProps) {\n const { activeValue } = useTabs();\n\n return (\n \n \n {children}\n \n \n );\n}\n\ntype TabsTriggerProps = HTMLMotionProps<'button'> & {\n value: string;\n children: React.ReactNode;\n};\n\nfunction TabsTrigger({\n ref,\n value,\n children,\n className,\n ...props\n}: TabsTriggerProps) {\n const { activeValue, handleValueChange, registerTrigger } = useTabs();\n\n const localRef = React.useRef(null);\n React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement);\n\n React.useEffect(() => {\n registerTrigger(value, localRef.current);\n return () => registerTrigger(value, null);\n }, [value, registerTrigger]);\n\n return (\n \n handleValueChange(value)}\n data-state={activeValue === value ? 'active' : 'inactive'}\n className={cn(\n '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]',\n className,\n )}\n {...props}\n >\n {children}\n \n \n );\n}\n\ntype TabsContentsProps = React.ComponentProps<'div'> & {\n children: React.ReactNode;\n transition?: Transition;\n};\n\nfunction TabsContents({\n children,\n className,\n transition = {\n type: 'spring',\n stiffness: 300,\n damping: 30,\n bounce: 0,\n restDelta: 0.01,\n },\n ...props\n}: TabsContentsProps) {\n const { activeValue } = useTabs();\n const childrenArray = React.Children.toArray(children);\n const activeIndex = childrenArray.findIndex(\n (child): child is React.ReactElement<{ value: string }> =>\n React.isValidElement(child) &&\n typeof child.props === 'object' &&\n child.props !== null &&\n 'value' in child.props &&\n child.props.value === activeValue,\n );\n\n return (\n \n \n {childrenArray.map((child, index) => (\n
\n {child}\n
\n ))}\n \n \n );\n}\n\ntype TabsContentProps = HTMLMotionProps<'div'> & {\n value: string;\n children: React.ReactNode;\n};\n\nfunction TabsContent({\n children,\n value,\n className,\n ...props\n}: TabsContentProps) {\n const { activeValue } = useTabs();\n const isActive = activeValue === value;\n return (\n \n {children}\n \n );\n}\n\nexport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContents,\n TabsContent,\n useTabs,\n type TabsContextType,\n type TabsProps,\n type TabsListProps,\n type TabsTriggerProps,\n type TabsContentsProps,\n type TabsContentProps,\n};\n", "type": "registry:ui", "target": "components/animate-ui/components/tabs.tsx" } ] }