{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs",
  "type": "registry:ui",
  "title": "Tabs",
  "description": "Segmented and browser-style navigation with a stable active indicator.",
  "dependencies": [
    "@base-ui/react@^1.7.0"
  ],
  "registryDependencies": [
    "https://control-ui.dev/r/core.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/tabs-knobs.ts",
      "target": "@components/control-ui/knob-contracts/tabs-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/tabs.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const tabsKnobs = [\n  \"--cui-tabs-trigger-radius\",\n  \"--cui-tabs-list-padding\",\n  \"--cui-tabs-list-radius\",\n  \"--cui-tabs-list-background\",\n  \"--cui-tabs-list-backdrop-filter\",\n  \"--cui-tabs-indicator-background\",\n  \"--cui-tabs-foreground\",\n  \"--cui-tabs-active-foreground\",\n  \"--cui-tabs-indicator-shadow\",\n  \"--cui-tabs-border-color\",\n] as const;\nexport type TabsKnobStyle = Partial<Record<(typeof tabsKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/tabs.css",
      "target": "@components/control-ui/styles/recipes/tabs.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"tabs\"][data-slot=\"root\"]) {\n    --cui-tabs-trigger-radius: var(--radius-control);\n    --cui-tabs-list-padding: 0.25rem;\n    --cui-tabs-list-radius: calc(var(--_tabs-trigger-radius-fit) + var(--_tabs-list-radius-offset));\n    --cui-tabs-list-background: oklch(from var(--foreground) l c h / 0.07);\n    --cui-tabs-list-backdrop-filter: none;\n    --cui-tabs-indicator-background: var(--background);\n    --cui-tabs-foreground: var(--muted-foreground);\n    --cui-tabs-active-foreground: var(--foreground);\n    --cui-tabs-indicator-shadow: var(--shadow-sm);\n    --cui-tabs-border-color: var(--border);\n  }\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"]) {\n    --_tabs-trigger-radius-fit: min(var(--cui-tabs-trigger-radius), calc(var(--_tabs-trigger-h) / 2));\n    --_tabs-list-radius-offset: clamp(0px, calc(var(--_tabs-trigger-radius-fit) * 1000), var(--cui-tabs-list-padding));\n    position: relative;\n    display: flex;\n    min-height: calc(var(--_tabs-trigger-h) + var(--cui-tabs-list-padding) + var(--cui-tabs-list-padding));\n    width: fit-content;\n    align-items: center;\n    gap: 0.25rem;\n    border-radius: var(--cui-tabs-list-radius);\n    background: var(--cui-tabs-list-background);\n    box-shadow: inset 0 0 0 1px oklch(from var(--foreground) l c h / 0.06);\n    padding: var(--cui-tabs-list-padding);\n    -webkit-backdrop-filter: var(--cui-tabs-list-backdrop-filter);\n    backdrop-filter: var(--cui-tabs-list-backdrop-filter);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"indicator\"]) {\n    position: absolute;\n    top: 50%;\n    left: 0;\n    z-index: 0;\n    height: var(--_tabs-trigger-h);\n    width: var(--active-tab-width);\n    transform: translateX(var(--active-tab-left)) translateY(-50%);\n    border-radius: var(--_tabs-trigger-radius-fit);\n    background: var(--cui-tabs-indicator-background);\n    box-shadow: var(--cui-tabs-indicator-shadow);\n    transition:\n      width var(--duration-base) var(--ease-standard),\n      transform var(--duration-base) var(--ease-standard),\n      background-color var(--duration-fast) var(--ease-standard),\n      box-shadow var(--duration-fast) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"tab\"]) {\n    position: relative;\n    z-index: 1;\n    display: inline-flex;\n    height: var(--_tabs-trigger-h);\n    flex-shrink: 0;\n    cursor: pointer;\n    align-items: center;\n    justify-content: center;\n    gap: 0.375rem;\n    white-space: nowrap;\n    border-radius: var(--_tabs-trigger-radius-fit);\n    padding-inline: 0.875rem;\n    color: var(--cui-tabs-foreground);\n    font-size: var(--text-label);\n    font-weight: 500;\n    outline: none;\n    transition: color var(--duration-fast) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"tab\"]:hover) {\n    color: var(--foreground);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"tab\"]:is([aria-selected=\"true\"], [data-active])) {\n    color: var(--cui-tabs-active-foreground);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"tab\"][data-disabled]) {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"][data-variant=\"browser\"]) {\n    --_tabs-browser-corner-double: calc(var(--_tabs-trigger-radius-fit) + var(--_tabs-trigger-radius-fit));\n    min-height: calc(var(--_tabs-trigger-h) + var(--cui-tabs-list-padding));\n    align-items: flex-end;\n    isolation: isolate;\n    border-radius: var(--cui-tabs-list-radius) var(--cui-tabs-list-radius) 0 0;\n    box-shadow: inset 0 -1px 0 var(--cui-tabs-border-color);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"][data-variant=\"browser\"] [data-slot=\"indicator\"]) {\n    --_tabs-browser-indicator-width: calc(var(--active-tab-width) + var(--_tabs-browser-corner-double));\n    --_tabs-browser-indicator-x: calc(var(--active-tab-left) - var(--_tabs-trigger-radius-fit));\n    border-radius: 0px;\n    -webkit-mask:\n      radial-gradient(circle closest-side, oklch(0 0 0) calc(100% - 0.5px), transparent 100%) left var(--_tabs-trigger-radius-fit) top /\n      var(--_tabs-browser-corner-double) var(--_tabs-browser-corner-double) no-repeat,\n      radial-gradient(circle closest-side, oklch(0 0 0) calc(100% - 0.5px), transparent 100%) right var(--_tabs-trigger-radius-fit) top /\n      var(--_tabs-browser-corner-double) var(--_tabs-browser-corner-double) no-repeat,\n      linear-gradient(oklch(0 0 0) 0 0) center top / calc(100% - var(--_tabs-browser-corner-double) - var(--_tabs-browser-corner-double))\n      var(--_tabs-trigger-radius-fit) no-repeat,\n      linear-gradient(oklch(0 0 0) 0 0) center bottom / calc(100% - var(--_tabs-browser-corner-double))\n      calc(100% - var(--_tabs-trigger-radius-fit)) no-repeat,\n      radial-gradient(circle var(--_tabs-trigger-radius-fit) at top left, transparent calc(100% - 0.5px), oklch(0 0 0) 100%) left bottom /\n      var(--_tabs-trigger-radius-fit) var(--_tabs-trigger-radius-fit) no-repeat,\n      radial-gradient(circle var(--_tabs-trigger-radius-fit) at top right, transparent calc(100% - 0.5px), oklch(0 0 0) 100%) right bottom /\n      var(--_tabs-trigger-radius-fit) var(--_tabs-trigger-radius-fit) no-repeat;\n    mask:\n      radial-gradient(circle closest-side, oklch(0 0 0) calc(100% - 0.5px), transparent 100%) left var(--_tabs-trigger-radius-fit) top /\n      var(--_tabs-browser-corner-double) var(--_tabs-browser-corner-double) no-repeat,\n      radial-gradient(circle closest-side, oklch(0 0 0) calc(100% - 0.5px), transparent 100%) right var(--_tabs-trigger-radius-fit) top /\n      var(--_tabs-browser-corner-double) var(--_tabs-browser-corner-double) no-repeat,\n      linear-gradient(oklch(0 0 0) 0 0) center top / calc(100% - var(--_tabs-browser-corner-double) - var(--_tabs-browser-corner-double))\n      var(--_tabs-trigger-radius-fit) no-repeat,\n      linear-gradient(oklch(0 0 0) 0 0) center bottom / calc(100% - var(--_tabs-browser-corner-double))\n      calc(100% - var(--_tabs-trigger-radius-fit)) no-repeat,\n      radial-gradient(circle var(--_tabs-trigger-radius-fit) at top left, transparent calc(100% - 0.5px), oklch(0 0 0) 100%) left bottom /\n      var(--_tabs-trigger-radius-fit) var(--_tabs-trigger-radius-fit) no-repeat,\n      radial-gradient(circle var(--_tabs-trigger-radius-fit) at top right, transparent calc(100% - 0.5px), oklch(0 0 0) 100%) right bottom /\n      var(--_tabs-trigger-radius-fit) var(--_tabs-trigger-radius-fit) no-repeat;\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"][data-single=\"true\"]) {\n    min-height: auto;\n    gap: 0;\n    border-radius: 0;\n    background: transparent;\n    padding: 0;\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"][data-single=\"true\"] [data-slot=\"tab\"]) {\n    height: auto;\n    cursor: default;\n    border-radius: 0;\n    padding-inline: 0;\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"panel\"]) {\n    outline-style: none;\n  }\n\n  :where([data-control-family=\"tabs\"][data-slot=\"list\"][data-variant=\"browser\"] [data-control-family=\"tabs\"][data-slot=\"tab\"]) {\n    border-top-left-radius: var(--_tabs-trigger-radius-fit);\n    border-top-right-radius: var(--_tabs-trigger-radius-fit);\n    border-bottom-right-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n}\n\n@property --cui-tabs-trigger-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tabs-list-padding {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tabs-list-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tabs-list-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tabs-list-backdrop-filter {\n  syntax: \"*\";\n  inherits: true;\n  initial-value: none;\n}\n\n@property --cui-tabs-indicator-background {\n  syntax: \"*\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tabs-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tabs-active-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tabs-indicator-shadow {\n  syntax: \"*\";\n  inherits: true;\n  initial-value: none;\n}\n\n@property --cui-tabs-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/tabs.tsx",
      "target": "@components/control-ui/ui/tabs.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport type { ComponentProps, CSSProperties, ReactNode, Ref } from \"react\";\nimport { Children, createContext, Fragment, isValidElement, useContext, useRef, useState } from \"react\";\nimport type { ControlledChoice, RenderProp } from \"@/components/control-ui/control-props\";\nimport type { ControlSize } from \"@/components/control-ui/control-variants\";\nimport type { TabsKnobStyle } from \"@/components/control-ui/knob-contracts/tabs-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\n\nexport type TabsProps<TValue extends string = string> = Omit<ComponentProps<\"div\">, \"defaultValue\" | \"onChange\"> &\n  ControlledChoice<TValue> & { style?: CSSProperties & TabsKnobStyle };\n\nexport type TabsListVariant = \"default\" | \"browser\";\n\nexport type TabsListProps = Omit<ComponentProps<\"div\">, \"style\"> & { style?: CSSProperties & TabsKnobStyle } & {\n  size?: ControlSize;\n  variant?: TabsListVariant;\n};\n\nexport type TabsTabProps = Omit<Omit<ComponentProps<\"button\">, \"value\">, \"style\"> & { style?: CSSProperties & TabsKnobStyle } & {\n  value: string;\n  render?: RenderProp<ComponentProps<\"button\">, { active: boolean; disabled: boolean }>;\n  nativeButton?: boolean;\n};\n\nexport type TabsPanelProps = Omit<ComponentProps<\"div\">, \"style\"> & { style?: CSSProperties & TabsKnobStyle } & {\n  value: string;\n};\n\ntype RegisterTabsPanel = (value: string, node: HTMLDivElement | null) => (() => void) | undefined;\n\nconst TabsPanelsContext = createContext<RegisterTabsPanel | null>(null);\n\nfunction useRegisterTabsPanel() {\n  const registerPanel = useContext(TabsPanelsContext);\n  if (!registerPanel) throw new Error(\"TabsPanel must be rendered inside <Tabs>.\");\n  return registerPanel;\n}\n\nfunction setRef<T>(ref: Ref<T> | undefined, value: T | null) {\n  if (typeof ref === \"function\") return ref(value);\n  if (ref) ref.current = value;\n}\n\n// rides Base UI's --active-tab-width/--active-tab-left, so one transition slides it between tabs\nexport function Tabs<TValue extends string = string>({ className, onValueChange, children, ...props }: TabsProps<TValue>) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const panelsRef = useRef(new Map<string, HTMLDivElement>());\n  const clearPrevHeight = useRef(0);\n  const [registerPanel] = useState<RegisterTabsPanel>(() => (value: string, node: HTMLDivElement | null) => {\n    if (!node) return;\n    panelsRef.current.set(value, node);\n    return () => {\n      if (panelsRef.current.get(value) === node) panelsRef.current.delete(value);\n    };\n  });\n\n  // Captured height bridges CSS's auto-to-auto transition gap.\n  const handleValueChange = (value: TValue) => {\n    const root = rootRef.current;\n    if (root) {\n      for (const panel of panelsRef.current.values()) {\n        if (panel.inert || panel.hidden) continue;\n        root.style.setProperty(\"--aui-slide-prev-height\", `${panel.getBoundingClientRect().height}px`);\n        cancelAnimationFrame(clearPrevHeight.current);\n        clearPrevHeight.current = requestAnimationFrame(() => {\n          clearPrevHeight.current = requestAnimationFrame(() => root.style.removeProperty(\"--aui-slide-prev-height\"));\n        });\n        break;\n      }\n    }\n    onValueChange?.(value);\n  };\n\n  return (\n    <TabsPanelsContext.Provider value={registerPanel}>\n      <TabsPrimitive.Root\n        data-control-ui=\"tabs\"\n        data-control-family=\"tabs\"\n        data-slot=\"root\"\n        data-slide=\"scope\"\n        className={className}\n        onValueChange={handleValueChange}\n        {...props}\n        ref={rootRef}\n      >\n        {children}\n      </TabsPrimitive.Root>\n    </TabsPanelsContext.Provider>\n  );\n}\n\nconst controlHeights: Record<ControlSize, string> = {\n  xs: \"var(--control-h-xs)\",\n  sm: \"var(--control-h-sm)\",\n  md: \"var(--control-h-md)\",\n  lg: \"var(--control-h-lg)\",\n};\n\ntype TabsListStyle = CSSProperties & {\n  \"--_tabs-trigger-h\"?: string;\n};\n\nfunction countTabs(children: ReactNode): number {\n  return Children.toArray(children).reduce<number>((count, child) => {\n    if (!isValidElement<{ children?: ReactNode }>(child)) return count;\n    if (child.type === TabsTab) return count + 1;\n    if (child.type === Fragment) return count + countTabs(child.props.children);\n    return count;\n  }, 0);\n}\n\nexport function TabsList({ size = \"sm\", variant = \"default\", className, children, style, ...props }: TabsListProps) {\n  const isSingle = countTabs(children) === 1;\n  const controlStyle = {\n    \"--_tabs-trigger-h\": controlHeights[size],\n    ...style,\n  } satisfies TabsListStyle;\n\n  return (\n    <TabsPrimitive.List\n      data-control-ui=\"tabs\"\n      data-control-family=\"tabs\"\n      data-slot=\"list\"\n      data-size={size}\n      data-variant={variant}\n      data-single={isSingle ? \"true\" : undefined}\n      className={cn(variant === \"browser\" && \"gap-0 p-0 px-(--_tabs-trigger-radius-fit) pt-(--cui-tabs-list-padding)\", className)}\n      style={controlStyle}\n      {...props}\n    >\n      {children}\n      {isSingle ? null : (\n        <TabsPrimitive.Indicator\n          data-control-ui=\"tabs\"\n          data-control-family=\"tabs\"\n          data-slot=\"indicator\"\n          className={\n            variant === \"browser\"\n              ? \"top-auto bottom-0 z-0 h-(--_tabs-trigger-h) w-(--_tabs-browser-indicator-width) transform-[translateX(var(--_tabs-browser-indicator-x))]\"\n              : undefined\n          }\n        />\n      )}\n    </TabsPrimitive.List>\n  );\n}\n\nexport function TabsTab({ className, ...props }: TabsTabProps) {\n  return <TabsPrimitive.Tab data-control-ui=\"tabs\" data-control-family=\"tabs\" data-slot=\"tab\" className={className} {...props} />;\n}\n\nexport function TabsPanel({ className, value, ref, ...props }: TabsPanelProps) {\n  const registerPanel = useRegisterTabsPanel();\n  const panelRef = (node: HTMLDivElement | null) => {\n    const unregister = registerPanel(value, node);\n    const cleanup = setRef(ref, node);\n    if (!node) return;\n    return () => {\n      unregister?.();\n      if (typeof cleanup === \"function\") cleanup();\n      else setRef(ref, null);\n    };\n  };\n\n  return (\n    <TabsPrimitive.Panel\n      ref={panelRef}\n      value={value}\n      data-control-ui=\"tabs\"\n      data-control-family=\"tabs\"\n      data-slot=\"panel\"\n      data-slide=\"panel\"\n      className={cn(\"data-[hidden]:hidden [&[hidden]]:hidden\", className)}\n      {...props}\n    />\n  );\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/tabs.css\"": {}
  },
  "meta": {}
}
