{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-animate-tabs",
  "title": "Tabs",
  "description": "A set of layered sections of content—known as tab panels—that are displayed one at a time.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@odysseyui/primitives-effects-highlight",
    "@odysseyui/primitives-animate-slot",
    "@odysseyui/lib-get-strict-context"
  ],
  "files": [
    {
      "path": "registry/primitives/animate/tabs/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, type Transition, type HTMLMotionProps } from 'motion/react';\n\nimport {\n  Highlight,\n  HighlightItem,\n  type HighlightItemProps,\n  type HighlightProps,\n} from '@/components/odyssey/primitives/effects/highlight';\nimport { getStrictContext } from '@/lib/get-strict-context';\nimport { Slot, type WithAsChild } from '@/components/odyssey/primitives/animate/slot';\n\ntype TabsContextType = {\n  activeValue: string;\n  handleValueChange: (value: string) => void;\n  registerTrigger: (value: string, node: HTMLElement | null) => void;\n};\n\nconst [TabsProvider, useTabs] =\n  getStrictContext<TabsContextType>('TabsContext');\n\ntype BaseTabsProps = React.ComponentProps<'div'> & {\n  children: React.ReactNode;\n};\n\ntype UnControlledTabsProps = BaseTabsProps & {\n  defaultValue?: string;\n  value?: never;\n  onValueChange?: never;\n};\n\ntype ControlledTabsProps = BaseTabsProps & {\n  value: string;\n  onValueChange?: (value: string) => void;\n  defaultValue?: never;\n};\n\ntype TabsProps = UnControlledTabsProps | ControlledTabsProps;\n\nfunction Tabs({\n  defaultValue,\n  value,\n  onValueChange,\n  children,\n  ...props\n}: TabsProps) {\n  const [activeValue, setActiveValue] = React.useState<string | undefined>(\n    defaultValue,\n  );\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 (\n      !isControlled &&\n      activeValue === undefined &&\n      triggersRef.current.size > 0 &&\n      !initialSet.current\n    ) {\n      const firstTab = triggersRef.current.keys().next().value as\n        | string\n        | undefined;\n      if (firstTab !== undefined) {\n        setActiveValue(firstTab);\n        initialSet.current = true;\n      }\n    }\n  }, [activeValue, isControlled]);\n\n  const registerTrigger = React.useCallback(\n    (val: string, node: HTMLElement | null) => {\n      if (node) {\n        triggersRef.current.set(val, node);\n        if (!isControlled && activeValue === undefined && !initialSet.current) {\n          setActiveValue(val);\n          initialSet.current = true;\n        }\n      } else {\n        triggersRef.current.delete(val);\n      }\n    },\n    [activeValue, isControlled],\n  );\n\n  const handleValueChange = React.useCallback(\n    (val: string) => {\n      if (!isControlled) setActiveValue(val);\n      else onValueChange?.(val);\n    },\n    [isControlled, onValueChange],\n  );\n\n  return (\n    <TabsProvider\n      value={{\n        activeValue: (value ?? activeValue) as string,\n        handleValueChange,\n        registerTrigger,\n      }}\n    >\n      <div data-slot=\"tabs\" {...props}>\n        {children}\n      </div>\n    </TabsProvider>\n  );\n}\n\ntype TabsHighlightProps = Omit<HighlightProps, 'controlledItems' | 'value'>;\n\nfunction TabsHighlight({\n  transition = { type: 'spring', stiffness: 200, damping: 25 },\n  ...props\n}: TabsHighlightProps) {\n  const { activeValue } = useTabs();\n\n  return (\n    <Highlight\n      data-slot=\"tabs-highlight\"\n      controlledItems\n      value={activeValue}\n      transition={transition}\n      click={false}\n      {...props}\n    />\n  );\n}\n\ntype TabsListProps = React.ComponentProps<'div'> & {\n  children: React.ReactNode;\n};\n\nfunction TabsList(props: TabsListProps) {\n  return <div role=\"tablist\" data-slot=\"tabs-list\" {...props} />;\n}\n\ntype TabsHighlightItemProps = HighlightItemProps & {\n  value: string;\n};\n\nfunction TabsHighlightItem(props: TabsHighlightItemProps) {\n  return <HighlightItem data-slot=\"tabs-highlight-item\" {...props} />;\n}\n\ntype TabsTriggerProps = WithAsChild<\n  {\n    value: string;\n    children: React.ReactNode;\n  } & HTMLMotionProps<'button'>\n>;\n\nfunction TabsTrigger({\n  ref,\n  value,\n  asChild = false,\n  ...props\n}: TabsTriggerProps) {\n  const { activeValue, handleValueChange, registerTrigger } = useTabs();\n\n  const localRef = React.useRef<HTMLButtonElement | null>(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  const Component = asChild ? Slot : motion.button;\n\n  return (\n    <Component\n      ref={localRef}\n      data-slot=\"tabs-trigger\"\n      role=\"tab\"\n      onClick={() => handleValueChange(value)}\n      data-state={activeValue === value ? 'active' : 'inactive'}\n      {...props}\n    />\n  );\n}\n\ntype TabsContentsProps = HTMLMotionProps<'div'> & {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction TabsContents({\n  children,\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  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const itemRefs = React.useRef<Array<HTMLDivElement | null>>([]);\n  const [height, setHeight] = React.useState(0);\n  const roRef = React.useRef<ResizeObserver | null>(null);\n\n  const measure = React.useCallback(() => {\n    const pane = itemRefs.current[activeIndex];\n    const container = containerRef.current;\n    if (!pane || !container) return 0;\n\n    const base = pane.getBoundingClientRect().height || 0;\n\n    const cs = getComputedStyle(container);\n    const isBorderBox = cs.boxSizing === 'border-box';\n    const paddingY =\n      (parseFloat(cs.paddingTop || '0') || 0) +\n      (parseFloat(cs.paddingBottom || '0') || 0);\n    const borderY =\n      (parseFloat(cs.borderTopWidth || '0') || 0) +\n      (parseFloat(cs.borderBottomWidth || '0') || 0);\n\n    let total = base + (isBorderBox ? paddingY + borderY : 0);\n\n    const dpr =\n      typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n    total = Math.ceil(total * dpr) / dpr;\n\n    return total;\n  }, [activeIndex]);\n\n  React.useEffect(() => {\n    if (roRef.current) {\n      roRef.current.disconnect();\n      roRef.current = null;\n    }\n\n    const pane = itemRefs.current[activeIndex];\n    const container = containerRef.current;\n    if (!pane || !container) return;\n\n    setHeight(measure());\n\n    const ro = new ResizeObserver(() => {\n      const next = measure();\n      requestAnimationFrame(() => setHeight(next));\n    });\n\n    ro.observe(pane);\n    ro.observe(container);\n\n    roRef.current = ro;\n    return () => {\n      ro.disconnect();\n      roRef.current = null;\n    };\n  }, [activeIndex, childrenArray.length, measure]);\n\n  React.useLayoutEffect(() => {\n    if (height === 0 && activeIndex >= 0) {\n      const next = measure();\n      if (next !== 0) setHeight(next);\n    }\n  }, [activeIndex, height, measure]);\n\n  return (\n    <motion.div\n      ref={containerRef}\n      data-slot=\"tabs-contents\"\n      style={{ overflow: 'hidden' }}\n      animate={{ height }}\n      transition={transition}\n      {...props}\n    >\n      <motion.div\n        className=\"flex -mx-2\"\n        animate={{ x: activeIndex * -100 + '%' }}\n        transition={transition}\n      >\n        {childrenArray.map((child, index) => (\n          <div\n            key={index}\n            ref={(el) => {\n              itemRefs.current[index] = el;\n            }}\n            className=\"w-full shrink-0 px-2 h-full\"\n          >\n            {child}\n          </div>\n        ))}\n      </motion.div>\n    </motion.div>\n  );\n}\n\ntype TabsContentProps = WithAsChild<\n  {\n    value: string;\n    children: React.ReactNode;\n  } & HTMLMotionProps<'div'>\n>;\n\nfunction TabsContent({\n  value,\n  style,\n  asChild = false,\n  ...props\n}: TabsContentProps) {\n  const { activeValue } = useTabs();\n  const isActive = activeValue === value;\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <Component\n      role=\"tabpanel\"\n      data-slot=\"tabs-content\"\n      inert={!isActive}\n      style={{ overflow: 'hidden', ...style }}\n      initial={{ filter: 'blur(0px)' }}\n      animate={{ filter: isActive ? 'blur(0px)' : 'blur(4px)' }}\n      exit={{ filter: 'blur(0px)' }}\n      transition={{ type: 'spring', stiffness: 200, damping: 25 }}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Tabs,\n  TabsList,\n  TabsHighlight,\n  TabsHighlightItem,\n  TabsTrigger,\n  TabsContents,\n  TabsContent,\n  useTabs,\n  type TabsProps,\n  type TabsListProps,\n  type TabsHighlightProps,\n  type TabsHighlightItemProps,\n  type TabsTriggerProps,\n  type TabsContentsProps,\n  type TabsContentProps,\n  type TabsContextType,\n};\n",
      "type": "registry:ui",
      "target": "components/odysseyui/primitives/animate/tabs.tsx"
    }
  ],
  "type": "registry:ui"
}