{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ui-motion-tabs","type":"registry:component","title":"Motion Tabs","description":"Tabs primitive with animated active-state transitions.","version":"1.0.0","status":"ga","files":[{"path":"src/components/ui/motion-tabs.tsx","type":"registry:component","content":"'use client'\n\nimport * as React from 'react'\n\nimport { motion, type Transition, type HTMLMotionProps } from 'motion/react'\n\nimport { cn } from '@/lib/utils'\nimport { MotionHighlight, MotionHighlightItem } from '@/components/ui/motion-highlight'\n\ntype TabsContextType<T extends string> = {\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<TabsContextType<any> | undefined>(undefined)\n\nfunction useTabs<T extends string = string>(): TabsContextType<T> {\n  const context = React.useContext(TabsContext)\n\n  if (!context) {\n    throw new Error('useTabs must be used within a TabsProvider')\n  }\n\n  return context\n}\n\ntype BaseTabsProps = React.ComponentProps<'div'> & {\n  children: React.ReactNode\n}\n\ntype UnControlledTabsProps<T extends string = string> = BaseTabsProps & {\n  defaultValue?: T\n  value?: never\n  onValueChange?: never\n}\n\ntype ControlledTabsProps<T extends string = string> = BaseTabsProps & {\n  value: T\n  onValueChange?: (value: T) => void\n  defaultValue?: never\n}\n\ntype TabsProps<T extends string = string> = UnControlledTabsProps<T> | ControlledTabsProps<T>\n\nfunction Tabs<T extends string = string>({\n  defaultValue,\n  value,\n  onValueChange,\n  children,\n  className,\n  ...props\n}: TabsProps<T>) {\n  const [activeValue, setActiveValue] = React.useState<T | undefined>(defaultValue ?? undefined)\n  const triggersRef = React.useRef(new Map<string, HTMLElement>())\n  const initialSet = React.useRef(false)\n  const isControlled = value !== undefined\n\n  React.useEffect(() => {\n    if (!isControlled && activeValue === undefined && triggersRef.current.size > 0 && !initialSet.current) {\n      const firstTab = Array.from(triggersRef.current.keys())[0]\n\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\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    <TabsContext.Provider\n      value={{\n        activeValue: (value ?? activeValue)!,\n        handleValueChange,\n        registerTrigger\n      }}\n    >\n      <div data-slot='tabs' className={cn('flex flex-col gap-2', className)} {...props}>\n        {children}\n      </div>\n    </TabsContext.Provider>\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    <MotionHighlight\n      controlledItems\n      className={cn('bg-background rounded-sm shadow-sm', activeClassName)}\n      value={activeValue}\n      transition={transition}\n    >\n      <div\n        role='tablist'\n        data-slot='tabs-list'\n        className={cn(\n          'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </MotionHighlight>\n  )\n}\n\ntype TabsTriggerProps = HTMLMotionProps<'button'> & {\n  value: string\n  children: React.ReactNode\n}\n\nfunction TabsTrigger({ ref, value, children, className, ...props }: TabsTriggerProps) {\n  const { activeValue, handleValueChange, registerTrigger } = useTabs()\n\n  const localRef = React.useRef<HTMLButtonElement | null>(null)\n\n  React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement)\n\n  React.useEffect(() => {\n    registerTrigger(value, localRef.current)\n\n    return () => registerTrigger(value, null)\n  }, [value, registerTrigger])\n\n  return (\n    <MotionHighlightItem value={value} className='size-full'>\n      <motion.button\n        ref={localRef}\n        data-slot='tabs-trigger'\n        role='tab'\n        onClick={() => handleValueChange(value)}\n        data-state={activeValue === value ? 'active' : 'inactive'}\n        className={cn(\n          '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',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </motion.button>\n    </MotionHighlightItem>\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\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    <div data-slot='tabs-contents' className={cn('overflow-hidden', className)} {...props}>\n      <motion.div className='-mx-2 flex' animate={{ x: activeIndex * -100 + '%' }} transition={transition}>\n        {childrenArray.map((child, index) => (\n          <div key={index} className='w-full shrink-0 px-2'>\n            {child}\n          </div>\n        ))}\n      </motion.div>\n    </div>\n  )\n}\n\ntype TabsContentProps = HTMLMotionProps<'div'> & {\n  value: string\n  children: React.ReactNode\n}\n\nfunction TabsContent({ children, value, className, ...props }: TabsContentProps) {\n  const { activeValue } = useTabs()\n  const isActive = activeValue === value\n\n  return (\n    <motion.div\n      role='tabpanel'\n      data-slot='tabs-content'\n      className={cn('overflow-hidden', className)}\n      initial={{ filter: 'blur(0px)' }}\n      animate={{ filter: isActive ? 'blur(0px)' : 'blur(2px)' }}\n      exit={{ filter: 'blur(0px)' }}\n      transition={{ type: 'spring', stiffness: 300, damping: 20 }}\n      {...props}\n    >\n      {children}\n    </motion.div>\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"}]}