{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "b-tabs",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "b-tabs.tsx",
      "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { motion } from \"motion/react\";\nimport * as React 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\ntype TabsContextValue = {\n  activationMode: \"automatic\" | \"manual\";\n  baseId: string;\n  hoveredValue: string | null;\n  listRef: React.RefObject<HTMLDivElement | null>;\n  setHoveredValue: (value: string | null) => void;\n  triggerRefs: React.MutableRefObject<Record<string, HTMLElement | null>>;\n  value: string | undefined;\n};\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(componentName: string) {\n  const context = React.useContext(TabsContext);\n\n  if (!context) {\n    throw new Error(`${componentName} must be used within Tabs.`);\n  }\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 useControllableState<T>({\n  defaultProp,\n  onChange,\n  prop,\n}: {\n  defaultProp: T;\n  onChange?: (value: T) => void;\n  prop?: T;\n}) {\n  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultProp);\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolledValue;\n\n  const setValue = React.useCallback(\n    (nextValue: T) => {\n      if (!isControlled) {\n        setUncontrolledValue(nextValue);\n      }\n\n      onChange?.(nextValue);\n    },\n    [isControlled, onChange]\n  );\n\n  return [value, setValue] as const;\n}\n\nfunction useMeasure<T extends HTMLElement = HTMLElement>(): [\n  (node: T | null) => void,\n  { width: number; height: number },\n] {\n  const [element, setElement] = React.useState<T | null>(null);\n  const [bounds, setBounds] = React.useState({ width: 0, height: 0 });\n\n  const ref = React.useCallback((node: T | null) => {\n    setElement(node);\n  }, []);\n\n  React.useEffect(() => {\n    if (!(element && typeof ResizeObserver !== \"undefined\")) {\n      return;\n    }\n\n    const observer = new ResizeObserver(([entry]) => {\n      setBounds((current) => {\n        const next = {\n          width: entry.contentRect.width,\n          height: entry.contentRect.height,\n        };\n\n        if (current.width === next.width && current.height === next.height) {\n          return current;\n        }\n\n        return next;\n      });\n    });\n\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [element]);\n\n  return [ref, bounds];\n}\n\nexport interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {\n  value: string;\n}\n\ntype TabsContentElement = React.ReactElement<TabsContentProps> & {\n  ref?: React.Ref<HTMLDivElement>;\n};\n\nfunction isTabsContentElement(\n  node: React.ReactNode\n): node is React.ReactElement<TabsContentProps> {\n  if (!React.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 collectTabsContentElements(\n  node: React.ReactNode\n): TabsContentElement[] {\n  const elements: TabsContentElement[] = [];\n\n  const visit = (childNode: React.ReactNode) => {\n    React.Children.forEach(childNode, (child) => {\n      if (!React.isValidElement(child)) {\n        return;\n      }\n\n      if (isTabsContentElement(child)) {\n        elements.push(child as TabsContentElement);\n        return;\n      }\n\n      const element = child as React.ReactElement<{\n        children?: React.ReactNode;\n      }>;\n\n      if (element.props.children) {\n        visit(element.props.children);\n      }\n    });\n  };\n\n  visit(node);\n  return elements;\n}\n\nexport interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {\n  activationMode?: \"automatic\" | \"manual\";\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  value?: string;\n}\n\nexport function Tabs({\n  activationMode = \"manual\",\n  children,\n  className,\n  defaultValue,\n  onValueChange,\n  value: valueProp,\n  ...props\n}: TabsProps) {\n  const contentElements = React.useMemo(\n    () => collectTabsContentElements(children),\n    [children]\n  );\n  const contentValues = React.useMemo(\n    () => contentElements.map((element) => element.props.value),\n    [contentElements]\n  );\n  const contentValueSet = React.useMemo(\n    () => new Set(contentValues),\n    [contentValues]\n  );\n  const firstContentValue = contentValues[0];\n  const [contentRef, contentBounds] = useMeasure<HTMLDivElement>();\n  const [value, setValue] = useControllableState<string | undefined>({\n    defaultProp:\n      defaultValue === undefined || contentValueSet.has(defaultValue)\n        ? (defaultValue ?? firstContentValue)\n        : firstContentValue,\n    onChange: (nextValue) => {\n      if (nextValue !== undefined) {\n        onValueChange?.(nextValue);\n      }\n    },\n    prop: valueProp,\n  });\n  const [hoveredValue, setHoveredValue] = React.useState<string | null>(null);\n  const listRef = React.useRef<HTMLDivElement>(null);\n  const triggerRefs = React.useRef<Record<string, HTMLElement | null>>({});\n  const defaultValueWarningRef = React.useRef<string | undefined>(undefined);\n  const valuePropWarningRef = React.useRef<string | undefined>(undefined);\n  const hasMountedRef = React.useRef(false);\n  const baseId = React.useId();\n  const resolvedValue =\n    value !== undefined && contentValueSet.has(value)\n      ? value\n      : firstContentValue;\n\n  React.useEffect(() => {\n    hasMountedRef.current = true;\n  }, []);\n\n  React.useEffect(() => {\n    if (\n      valueProp === undefined &&\n      resolvedValue !== undefined &&\n      value !== resolvedValue\n    ) {\n      setValue(resolvedValue);\n    }\n  }, [resolvedValue, setValue, value, valueProp]);\n\n  React.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 activeContent =\n    resolvedValue === undefined\n      ? contentElements[0]\n      : contentElements.find(\n          (element) => element.props.value === resolvedValue\n        );\n\n  const activeContentRef = activeContent?.ref;\n  const {\n    children: activeContentChildren,\n    className: activeContentClassName,\n    value: _activeContentValue,\n    ...activePanelProps\n  } = activeContent?.props ?? {};\n\n  const contextValue = React.useMemo(\n    () => ({\n      activationMode,\n      baseId,\n      hoveredValue,\n      listRef,\n      setHoveredValue,\n      triggerRefs,\n      value: resolvedValue,\n    }),\n    [activationMode, baseId, hoveredValue, resolvedValue]\n  );\n\n  return (\n    <TabsPrimitive.Root\n      onValueChange={(nextValue) => setValue(String(nextValue))}\n      value={resolvedValue}\n    >\n      <TabsContext.Provider value={contextValue}>\n        <div\n          className={cn(componentThemeClassName, \"w-full\", className)}\n          {...props}\n        >\n          {children}\n          {activeContent ? (\n            <div className=\"relative mt-10\">\n              <motion.div\n                animate={{\n                  height:\n                    contentBounds.height > 0 ? contentBounds.height : \"auto\",\n                }}\n                className=\"overflow-hidden\"\n                initial={false}\n                transition={{\n                  damping: 30,\n                  mass: 0.9,\n                  stiffness: 260,\n                  type: \"spring\",\n                }}\n              >\n                <div ref={contentRef}>\n                  <motion.div\n                    animate={{\n                      filter: \"blur(0px)\",\n                      opacity: 1,\n                      scale: 1,\n                      y: 0,\n                    }}\n                    className=\"w-full\"\n                    initial={\n                      hasMountedRef.current\n                        ? {\n                            filter: \"blur(7px)\",\n                            opacity: 0.72,\n                            scale: 0.988,\n                            y: 8,\n                          }\n                        : false\n                    }\n                    key={activeContent.props.value}\n                    layout\n                    style={{ willChange: \"filter, opacity, transform\" }}\n                    transition={{\n                      damping: 24,\n                      mass: 0.85,\n                      stiffness: 220,\n                      type: \"spring\",\n                      filter: { duration: 0.24, ease: [0.22, 1, 0.36, 1] },\n                      opacity: { duration: 0.18, ease: \"easeOut\" },\n                    }}\n                  >\n                    <TabsPrimitive.Panel\n                      {...activePanelProps}\n                      className={activeContentClassName}\n                      id={getContentId(baseId, activeContent.props.value)}\n                      ref={(node) => setRefValue(activeContentRef, node)}\n                      value={activeContent.props.value}\n                    >\n                      {activeContentChildren}\n                    </TabsPrimitive.Panel>\n                  </motion.div>\n                </div>\n              </motion.div>\n            </div>\n          ) : null}\n        </div>\n      </TabsContext.Provider>\n    </TabsPrimitive.Root>\n  );\n}\n\nexport interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport const TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(\n  ({ children, className, onMouseLeave, ...props }, ref) => {\n    const { activationMode, listRef, setHoveredValue, triggerRefs, value } =\n      useTabsContext(\"TabsList\");\n    const [activeRect, setActiveRect] = React.useState({ left: 0, width: 0 });\n    const getTriggerElement = React.useCallback(\n      (tabValue: string | null) => {\n        if (!tabValue) {\n          return null;\n        }\n\n        return triggerRefs.current[tabValue] ?? null;\n      },\n      [triggerRefs]\n    );\n\n    const measure = React.useCallback(\n      (tabValue: string | null) => {\n        if (!tabValue) {\n          return null;\n        }\n\n        const element = getTriggerElement(tabValue);\n        const container = listRef.current;\n\n        if (!(element && container)) {\n          return null;\n        }\n\n        return {\n          left: element.offsetLeft,\n          width: element.offsetWidth,\n        };\n      },\n      [getTriggerElement, listRef]\n    );\n\n    React.useLayoutEffect(() => {\n      const rect = measure(value ?? null);\n      if (rect) {\n        setActiveRect(rect);\n      }\n    }, [measure, value]);\n\n    React.useLayoutEffect(() => {\n      const updateRects = () => {\n        setActiveRect((current) => {\n          const next = measure(value ?? null) ?? { left: 0, width: 0 };\n\n          if (current.left === next.left && current.width === next.width) {\n            return current;\n          }\n\n          return next;\n        });\n      };\n\n      updateRects();\n\n      const container = listRef.current;\n      const activeTrigger = getTriggerElement(value ?? null);\n\n      window.addEventListener(\"resize\", updateRects);\n\n      if (typeof ResizeObserver === \"undefined\") {\n        return () => window.removeEventListener(\"resize\", updateRects);\n      }\n\n      const observer = new ResizeObserver(updateRects);\n\n      if (container) {\n        observer.observe(container);\n      }\n\n      if (activeTrigger) {\n        observer.observe(activeTrigger);\n      }\n\n      return () => {\n        observer.disconnect();\n        window.removeEventListener(\"resize\", updateRects);\n      };\n    }, [getTriggerElement, listRef, measure, value]);\n\n    return (\n      <TabsPrimitive.List\n        {...props}\n        activateOnFocus={activationMode === \"automatic\"}\n        className={cn(\n          \"no-scrollbar relative isolate inline-flex max-w-full items-center overflow-x-auto overscroll-x-contain whitespace-nowrap 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        loopFocus\n        onMouseLeave={(event) => {\n          onMouseLeave?.(event);\n\n          if (!event.defaultPrevented) {\n            setHoveredValue(null);\n          }\n        }}\n        ref={(node) => {\n          listRef.current = node;\n          setRefValue(ref, node);\n        }}\n      >\n        {children}\n        <motion.div\n          animate={{ left: activeRect.left, width: activeRect.width }}\n          aria-hidden\n          className=\"pointer-events-none absolute bottom-0 z-10 h-px\"\n          style={{\n            backgroundColor:\n              \"color-mix(in oklab, var(--color-foreground) 88%, black)\",\n          }}\n          transition={{\n            damping: 34,\n            mass: 0.7,\n            stiffness: 360,\n            type: \"spring\",\n          }}\n        />\n      </TabsPrimitive.List>\n    );\n  }\n);\nTabsList.displayName = \"TabsList\";\n\nexport interface TabsTriggerProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  value: string;\n}\n\nexport const TabsTrigger = React.forwardRef<HTMLElement, TabsTriggerProps>(\n  (\n    {\n      children,\n      className,\n      onBlur,\n      onFocus,\n      onMouseEnter,\n      style,\n      value,\n      ...props\n    },\n    ref\n  ) => {\n    const {\n      baseId,\n      hoveredValue,\n      listRef,\n      setHoveredValue,\n      triggerRefs,\n      value: activeValue,\n    } = useTabsContext(\"TabsTrigger\");\n    const isActive = activeValue === value;\n    const isHover = hoveredValue === value;\n\n    return (\n      <TabsPrimitive.Tab\n        {...props}\n        aria-controls={getContentId(baseId, value)}\n        className={cn(\n          \"relative inline-flex min-h-11 min-w-11 touch-manipulation items-center justify-center rounded-md px-6 py-4 text-sm tracking-tight outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-inset\",\n          className\n        )}\n        data-state={isActive ? \"active\" : \"inactive\"}\n        data-value={value}\n        id={getTriggerId(baseId, value)}\n        onBlur={(event) => {\n          onBlur?.(event);\n\n          if (\n            !(\n              event.defaultPrevented ||\n              listRef.current?.contains(event.relatedTarget as Node | null)\n            )\n          ) {\n            setHoveredValue(null);\n          }\n        }}\n        onFocus={(event) => {\n          onFocus?.(event);\n\n          if (!event.defaultPrevented) {\n            setHoveredValue(value);\n          }\n        }}\n        onMouseEnter={(event) => {\n          onMouseEnter?.(event);\n\n          if (!event.defaultPrevented) {\n            setHoveredValue(value);\n          }\n        }}\n        ref={(node) => {\n          triggerRefs.current[value] = node;\n          setRefValue(ref, node);\n        }}\n        style={{\n          ...style,\n          color: isActive\n            ? \"var(--color-foreground)\"\n            : isHover\n              ? \"color-mix(in oklab, var(--color-foreground) 75%, transparent)\"\n              : \"color-mix(in oklab, var(--color-foreground) 40%, transparent)\",\n          transition: \"color 160ms cubic-bezier(0.32, 0.72, 0, 1)\",\n        }}\n        value={value}\n      >\n        <span className=\"relative z-10\">{children}</span>\n      </TabsPrimitive.Tab>\n    );\n  }\n);\nTabsTrigger.displayName = \"TabsTrigger\";\n\nexport const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(\n  (_props, _ref) => {\n    useTabsContext(\"TabsContent\");\n    return null;\n  }\n);\nTabsContent.displayName = \"TabsContent\";\n\nexport { Tabs as tabs };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Tabs (Base UI)",
  "description": "Tabs with the same Iconiq API layered over Base UI primitives, preserving the measured underline, keyboard flow, and motion-smoothed panel shell from the core tabs component."
}
