{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@radix-ui/react-tabs", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "tabs.tsx",
      "content": "\"use client\";\n\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport {\n  MotionConfig,\n  motion,\n  type Transition,\n  useReducedMotion,\n} from \"motion/react\";\nimport {\n  Children,\n  type ComponentPropsWithoutRef,\n  createContext,\n  type ElementRef,\n  forwardRef,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst springTransition: Transition = {\n  type: \"spring\",\n  stiffness: 170,\n  damping: 24,\n  mass: 1.2,\n};\n\nconst instantTransition: Transition = { duration: 0 };\n\nconst EMPTY_INDICATOR = { height: 0, left: 0, top: 0, width: 0 };\n\ntype TabsVariant = \"pill\" | \"underline\";\ntype TabsOrientation = \"horizontal\" | \"vertical\";\n\ntype IndicatorRect = typeof EMPTY_INDICATOR;\n\ntype TabsContextType = {\n  animateContent: boolean;\n  baseId: string;\n  listRef: React.RefObject<HTMLDivElement | null>;\n  orientation: TabsOrientation;\n  reduceMotion: boolean;\n  registerTrigger: (value: string, node: HTMLButtonElement | null) => void;\n  transition: Transition;\n  triggerRefs: React.MutableRefObject<Record<string, HTMLButtonElement | null>>;\n  variant: TabsVariant;\n  value: string;\n};\n\nconst TabsContext = createContext<TabsContextType | undefined>(undefined);\n\nfunction useTabsContext(componentName: string) {\n  const context = useContext(TabsContext);\n  if (!context) {\n    throw new Error(`${componentName} must be used within Tabs.`);\n  }\n  return context;\n}\n\nfunction setRefValue<T>(\n  ref: React.ForwardedRef<T> | undefined,\n  value: T | null\n) {\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n\n  if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction getTriggerId(baseId: string, value: string) {\n  return `${baseId}-trigger-${value}`;\n}\n\nfunction getContentId(baseId: string, value: string) {\n  return `${baseId}-content-${value}`;\n}\n\nfunction isTabsContentElement(\n  node: ReactNode\n): node is ReactElement<{ value?: string }> {\n  if (!isValidElement(node)) {\n    return false;\n  }\n\n  const displayName =\n    typeof node.type === \"string\"\n      ? node.type\n      : (node.type as { displayName?: string }).displayName;\n\n  return displayName === \"TabsContent\";\n}\n\nfunction collectTabsContentValues(node: ReactNode): string[] {\n  const values: string[] = [];\n\n  const visit = (childNode: ReactNode) => {\n    Children.forEach(childNode, (child) => {\n      if (!isValidElement(child)) {\n        return;\n      }\n\n      if (\n        isTabsContentElement(child) &&\n        typeof child.props.value === \"string\"\n      ) {\n        values.push(child.props.value);\n        return;\n      }\n\n      const element = child as ReactElement<{ children?: ReactNode }>;\n      if (element.props.children) {\n        visit(element.props.children);\n      }\n    });\n  };\n\n  visit(node);\n  return values;\n}\n\nfunction resolveTabValue(\n  candidate: string | undefined,\n  contentValueSet: Set<string>,\n  fallback: string\n) {\n  if (candidate !== undefined && contentValueSet.has(candidate)) {\n    return candidate;\n  }\n\n  return fallback;\n}\n\nfunction getOffsetWithinAncestor(\n  element: HTMLElement,\n  ancestor: HTMLElement\n): IndicatorRect {\n  let left = 0;\n  let top = 0;\n  let node: HTMLElement | null = element;\n\n  while (node && node !== ancestor) {\n    left += node.offsetLeft;\n    top += node.offsetTop;\n    node = node.parentElement;\n  }\n\n  return {\n    height: element.offsetHeight,\n    left,\n    top,\n    width: element.offsetWidth,\n  };\n}\n\nfunction measureIndicatorRect(\n  listElement: HTMLDivElement | null,\n  triggerElement: HTMLButtonElement | null\n): IndicatorRect | null {\n  if (!(listElement && triggerElement)) {\n    return null;\n  }\n\n  if (!listElement.contains(triggerElement)) {\n    return null;\n  }\n\n  const rect = getOffsetWithinAncestor(triggerElement, listElement);\n\n  if (rect.width <= 0 || rect.height <= 0) {\n    return null;\n  }\n\n  return rect;\n}\n\nfunction useTabIndicator({\n  listRef,\n  triggerRefs,\n  value,\n}: {\n  listRef: React.RefObject<HTMLDivElement | null>;\n  triggerRefs: React.MutableRefObject<Record<string, HTMLButtonElement | null>>;\n  value: string;\n}) {\n  const [indicatorRect, setIndicatorRect] =\n    useState<IndicatorRect>(EMPTY_INDICATOR);\n\n  const getActiveTrigger = useCallback(() => {\n    if (!value) {\n      return null;\n    }\n\n    return triggerRefs.current[value] ?? null;\n  }, [triggerRefs, value]);\n\n  const updateIndicator = useCallback(() => {\n    const nextRect = measureIndicatorRect(listRef.current, getActiveTrigger());\n\n    if (!nextRect) {\n      setIndicatorRect((current) =>\n        current.width === 0 && current.height === 0 ? current : EMPTY_INDICATOR\n      );\n      return;\n    }\n\n    setIndicatorRect((current) => {\n      if (\n        current.left === nextRect.left &&\n        current.top === nextRect.top &&\n        current.width === nextRect.width &&\n        current.height === nextRect.height\n      ) {\n        return current;\n      }\n\n      return nextRect;\n    });\n  }, [getActiveTrigger, listRef]);\n\n  useLayoutEffect(() => {\n    updateIndicator();\n\n    const listElement = listRef.current;\n    if (!listElement) {\n      return;\n    }\n\n    const handleUpdate = () => {\n      updateIndicator();\n    };\n\n    listElement.addEventListener(\"scroll\", handleUpdate, { passive: true });\n    window.addEventListener(\"resize\", handleUpdate);\n\n    if (typeof ResizeObserver === \"undefined\") {\n      return () => {\n        listElement.removeEventListener(\"scroll\", handleUpdate);\n        window.removeEventListener(\"resize\", handleUpdate);\n      };\n    }\n\n    const observer = new ResizeObserver(handleUpdate);\n    observer.observe(listElement);\n\n    const activeTrigger = getActiveTrigger();\n    if (activeTrigger) {\n      observer.observe(activeTrigger);\n    }\n\n    return () => {\n      observer.disconnect();\n      listElement.removeEventListener(\"scroll\", handleUpdate);\n      window.removeEventListener(\"resize\", handleUpdate);\n    };\n  }, [getActiveTrigger, listRef, updateIndicator]);\n\n  return {\n    indicatorRect,\n    showIndicator: indicatorRect.width > 0 && indicatorRect.height > 0,\n  };\n}\n\ntype TabsProps = ComponentPropsWithoutRef<typeof TabsPrimitive.Root> & {\n  animateContent?: boolean;\n  variant?: TabsVariant;\n};\n\nfunction Tabs({\n  animateContent = false,\n  children,\n  className,\n  defaultValue,\n  onValueChange,\n  orientation = \"horizontal\",\n  value: valueProp,\n  variant = \"pill\",\n  ...props\n}: TabsProps) {\n  const baseId = useId();\n  const listRef = useRef<HTMLDivElement | null>(null);\n  const triggerRefs = useRef<Record<string, HTMLButtonElement | null>>({});\n  const reduceMotion = useReducedMotion() ?? false;\n  const transition = reduceMotion ? instantTransition : springTransition;\n\n  const contentValues = useMemo(\n    () => collectTabsContentValues(children),\n    [children]\n  );\n  const contentValueSet = useMemo(\n    () => new Set(contentValues),\n    [contentValues]\n  );\n  const firstContentValue = contentValues[0] ?? \"\";\n  const hasPanels = contentValues.length > 0;\n\n  const [uncontrolledValue, setUncontrolledValue] = useState(() =>\n    resolveTabValue(defaultValue, contentValueSet, firstContentValue)\n  );\n\n  const isControlled = valueProp !== undefined;\n  const rawValue = isControlled ? valueProp : uncontrolledValue;\n  const value = resolveTabValue(rawValue, contentValueSet, firstContentValue);\n\n  const defaultValueWarningRef = useRef<string | undefined>(undefined);\n  const valuePropWarningRef = useRef<string | undefined>(undefined);\n\n  useEffect(() => {\n    if (!isControlled && value !== uncontrolledValue) {\n      setUncontrolledValue(value);\n    }\n  }, [isControlled, uncontrolledValue, value]);\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") {\n      return;\n    }\n\n    if (\n      defaultValue !== undefined &&\n      !contentValueSet.has(defaultValue) &&\n      defaultValueWarningRef.current !== defaultValue\n    ) {\n      defaultValueWarningRef.current = defaultValue;\n      console.warn(\n        `Tabs received a defaultValue of \"${defaultValue}\" that does not match any TabsContent. Falling back to \"${firstContentValue || \"undefined\"}\".`\n      );\n    }\n\n    if (\n      valueProp !== undefined &&\n      !contentValueSet.has(valueProp) &&\n      valuePropWarningRef.current !== valueProp\n    ) {\n      valuePropWarningRef.current = valueProp;\n      console.warn(\n        `Tabs received a value of \"${valueProp}\" that does not match any TabsContent. Falling back to \"${firstContentValue || \"undefined\"}\".`\n      );\n    }\n  }, [contentValueSet, defaultValue, firstContentValue, valueProp]);\n\n  const handleValueChange = useCallback(\n    (nextValue: string) => {\n      if (!isControlled) {\n        setUncontrolledValue(nextValue);\n      }\n      onValueChange?.(nextValue);\n    },\n    [isControlled, onValueChange]\n  );\n\n  const registerTrigger = useCallback(\n    (triggerValue: string, node: HTMLButtonElement | null) => {\n      if (node) {\n        triggerRefs.current[triggerValue] = node;\n        return;\n      }\n\n      delete triggerRefs.current[triggerValue];\n    },\n    []\n  );\n\n  const contextValue = useMemo(\n    () => ({\n      animateContent,\n      baseId,\n      listRef,\n      orientation,\n      reduceMotion,\n      registerTrigger,\n      transition,\n      triggerRefs,\n      variant,\n      value,\n    }),\n    [\n      animateContent,\n      baseId,\n      orientation,\n      reduceMotion,\n      registerTrigger,\n      transition,\n      variant,\n      value,\n    ]\n  );\n\n  const rootClassName = cn(\n    componentThemeClassName,\n    \"relative\",\n    orientation === \"vertical\" && \"flex gap-4\",\n    className\n  );\n\n  if (!hasPanels) {\n    return (\n      <div className={rootClassName} data-slot=\"tabs\">\n        {children}\n      </div>\n    );\n  }\n\n  return (\n    <MotionConfig reducedMotion={reduceMotion ? \"always\" : \"never\"}>\n      <TabsContext.Provider value={contextValue}>\n        <TabsPrimitive.Root\n          {...props}\n          className={rootClassName}\n          data-slot=\"tabs\"\n          onValueChange={handleValueChange}\n          orientation={orientation}\n          value={value}\n        >\n          {children}\n        </TabsPrimitive.Root>\n      </TabsContext.Provider>\n    </MotionConfig>\n  );\n}\n\ntype TabsListProps = ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {\n  fullWidth?: boolean;\n};\n\nconst TabsList = forwardRef<\n  ElementRef<typeof TabsPrimitive.List>,\n  TabsListProps\n>(({ children, className, fullWidth = false, loop = true, ...props }, ref) => {\n  const { listRef, orientation, transition, triggerRefs, variant, value } =\n    useTabsContext(\"TabsList\");\n\n  const { indicatorRect, showIndicator } = useTabIndicator({\n    listRef,\n    triggerRefs,\n    value,\n  });\n\n  const isVertical = orientation === \"vertical\";\n\n  return (\n    <TabsPrimitive.List\n      className={cn(\n        \"no-scrollbar relative isolate max-w-full gap-1\",\n        fullWidth ? \"flex w-full\" : \"inline-flex w-fit\",\n        isVertical\n          ? \"flex-col overflow-y-auto overflow-x-hidden overscroll-y-contain\"\n          : \"flex-row overflow-x-auto overflow-y-hidden overscroll-x-contain\",\n        variant === \"pill\"\n          ? \"rounded-2xl bg-muted p-2\"\n          : \"whitespace-nowrap px-1 after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:z-0 after:h-px after:bg-border/50 after:content-['']\",\n        className\n      )}\n      data-slot=\"tabs-list\"\n      loop={loop}\n      ref={(node) => {\n        listRef.current = node;\n        setRefValue(ref, node);\n      }}\n      {...props}\n    >\n      {children}\n      {showIndicator ? (\n        <motion.div\n          animate={\n            variant === \"pill\"\n              ? {\n                  height: indicatorRect.height,\n                  left: indicatorRect.left,\n                  top: indicatorRect.top,\n                  width: indicatorRect.width,\n                }\n              : isVertical\n                ? {\n                    height: indicatorRect.height,\n                    top: indicatorRect.top,\n                  }\n                : {\n                    left: indicatorRect.left,\n                    width: indicatorRect.width,\n                  }\n          }\n          aria-hidden\n          className={cn(\n            \"pointer-events-none absolute z-0 bg-foreground\",\n            variant === \"pill\"\n              ? \"rounded-lg shadow-[rgba(0,0,0,0.04)_0px_1px_6px] dark:shadow-[rgba(0,0,0,0.2)_0px_1px_6px]\"\n              : isVertical\n                ? \"right-0 w-px\"\n                : \"bottom-0 h-px\"\n          )}\n          initial={false}\n          key={`${variant}-${orientation}`}\n          transition={transition}\n        />\n      ) : null}\n    </TabsPrimitive.List>\n  );\n});\nTabsList.displayName = TabsPrimitive.List.displayName;\n\ntype TabsTriggerProps = ComponentPropsWithoutRef<\n  typeof TabsPrimitive.Trigger\n> & {\n  badge?: ReactNode;\n  icon?: ReactNode;\n};\n\nconst TabsTrigger = forwardRef<\n  ElementRef<typeof TabsPrimitive.Trigger>,\n  TabsTriggerProps\n>(\n  (\n    { badge, children, className, icon, value: triggerValue, ...props },\n    ref\n  ) => {\n    const { baseId, orientation, registerTrigger, variant, value } =\n      useTabsContext(\"TabsTrigger\");\n    const isActive = value === triggerValue;\n    const isVertical = orientation === \"vertical\";\n\n    return (\n      <TabsPrimitive.Trigger\n        aria-controls={getContentId(baseId, triggerValue)}\n        className={cn(\n          \"relative z-10 inline-flex min-h-9 min-w-0 shrink-0 touch-manipulation items-center justify-center gap-1.5 whitespace-nowrap rounded-lg px-3 py-1.5 font-medium text-sm outline-none transition-colors disabled:pointer-events-none disabled:opacity-50\",\n          variant === \"pill\"\n            ? cn(\n                \"text-muted-foreground hover:text-foreground\",\n                \"data-[state=active]:text-background\",\n                \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n              )\n            : cn(\n                \"rounded-md px-4 py-3 tracking-tight\",\n                \"text-muted-foreground hover:text-foreground\",\n                \"data-[state=active]:text-foreground\",\n                \"focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-inset\"\n              ),\n          isVertical && \"w-full justify-start\",\n          className\n        )}\n        data-slot=\"tabs-trigger\"\n        data-value={triggerValue}\n        id={getTriggerId(baseId, triggerValue)}\n        ref={(node) => {\n          registerTrigger(triggerValue, node);\n          setRefValue(ref, node);\n        }}\n        value={triggerValue}\n        {...props}\n      >\n        {icon ? (\n          <span\n            aria-hidden\n            className=\"inline-flex shrink-0 items-center [&>svg]:size-4\"\n          >\n            {icon}\n          </span>\n        ) : null}\n        <span className=\"truncate\">{children}</span>\n        {badge ? (\n          <span\n            className={cn(\n              \"inline-flex min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 py-0.5 font-medium text-[10px] tabular-nums\",\n              isActive && variant === \"pill\"\n                ? \"bg-background/15 text-inherit\"\n                : \"bg-muted text-muted-foreground\"\n            )}\n          >\n            {badge}\n          </span>\n        ) : null}\n      </TabsPrimitive.Trigger>\n    );\n  }\n);\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = forwardRef<\n  ElementRef<typeof TabsPrimitive.Content>,\n  ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ children, className, value: contentValue, ...props }, ref) => {\n  const {\n    animateContent,\n    baseId,\n    orientation,\n    reduceMotion,\n    transition,\n    value,\n  } = useTabsContext(\"TabsContent\");\n  const isActive = value === contentValue;\n\n  const body =\n    animateContent && isActive && !reduceMotion ? (\n      <motion.div\n        animate={{ opacity: 1, y: 0 }}\n        initial={{ opacity: 0, y: 8 }}\n        key={contentValue}\n        transition={{\n          ...transition,\n          opacity: { duration: 0.18, ease: \"easeOut\" },\n        }}\n      >\n        {children}\n      </motion.div>\n    ) : (\n      children\n    );\n\n  return (\n    <TabsPrimitive.Content\n      className={cn(\n        \"relative outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        orientation === \"vertical\" ? \"mt-0 min-w-0 flex-1\" : \"mt-2\",\n        className\n      )}\n      data-slot=\"tabs-content\"\n      id={getContentId(baseId, contentValue)}\n      ref={ref}\n      value={contentValue}\n      {...props}\n    >\n      {body}\n    </TabsPrimitive.Content>\n  );\n});\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport {\n  Tabs,\n  TabsContent,\n  TabsList,\n  TabsTrigger,\n  type TabsProps,\n  type TabsTriggerProps,\n  type TabsVariant,\n};\n",
      "type": "registry:ui"
    }
  ],
  "title": "Tabs",
  "description": "Segmented tabs with a sliding active pill, mix-blend label treatment, and Radix keyboard navigation."
}
