{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fluid-tabs",
  "type": "registry:ui",
  "title": "Fluid Tabs",
  "description": "An accessible Base UI tab selector with a shared active surface that travels fluidly between selections, with capsule and underline variants.",
  "files": [
    {
      "path": "registry/sonaui/fluid-tabs/fluid-tabs.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Tabs } from \"@base-ui/react/tabs\";\nimport { LayoutGroup, motion, useReducedMotion } from \"motion/react\";\nimport {\n  type CSSProperties,\n  type ReactNode,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/sona-utils\";\n\nexport interface FluidTabItem {\n  /** Stable value used to identify the tab. */\n  value: string;\n  /** Content displayed inside the tab trigger. */\n  title: ReactNode;\n  /** Whether the tab is unavailable. @default false */\n  disabled?: boolean;\n  /** ID of the external tab panel controlled by this tab. */\n  ariaControls?: string;\n}\n\nexport interface FluidTabsProps {\n  /** Tabs displayed in the horizontal tab list. */\n  tabs: FluidTabItem[];\n  /** Controlled active tab value. */\n  value?: string;\n  /** Initially active tab for uncontrolled usage. */\n  defaultValue?: string;\n  /** Called when the active tab changes. */\n  onValueChange?: (value: string) => void;\n  /** Visual treatment for the active surface. @default \"capsule\" */\n  variant?: \"capsule\" | \"underline\";\n  /** Size of the tab triggers. @default \"md\" */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** Accessible label for the tab list. @default \"Tabs\" */\n  ariaLabel?: string;\n  /**\n   * Styling hook for the active surface.\n   * @default Capsule uses the active surface token; underline uses foreground.\n   */\n  activeIndicatorClassName?: string;\n  /** Optional styling hook for the supporting hover cue. @default \"bg-[var(--fluid-tabs-hover)]\" */\n  hoverClassName?: string;\n  /** Additional classes for the root container. */\n  className?: string;\n  /** Additional classes for the tab list. */\n  listClassName?: string;\n}\n\nconst sizeClasses = {\n  sm: \"min-h-8 px-2.5 text-xs\",\n  md: \"min-h-9 px-3 text-sm\",\n  lg: \"min-h-11 px-4 text-base\",\n} as const;\n\nconst tokenStyle = {\n  \"--fluid-tabs-surface\": \"var(--tabs-surface)\",\n  \"--fluid-tabs-surface-active\": \"var(--tabs-indicator)\",\n  \"--fluid-tabs-label\": \"var(--muted-foreground)\",\n  \"--fluid-tabs-label-active\": \"var(--foreground)\",\n  \"--fluid-tabs-hover\": \"color-mix(in oklab, var(--accent) 35%, transparent)\",\n  \"--fluid-tabs-focus-ring\": \"var(--ring)\",\n  \"--fluid-tabs-border\": \"var(--border)\",\n  \"--fluid-tabs-capsule-border-radius\": \"var(--radius-lg)\",\n} as CSSProperties;\n\nexport default function FluidTabs({\n  tabs,\n  value,\n  defaultValue,\n  onValueChange,\n  variant = \"capsule\",\n  size = \"md\",\n  ariaLabel = \"Tabs\",\n  activeIndicatorClassName,\n  hoverClassName = \"bg-[var(--fluid-tabs-hover)]\",\n  className,\n  listClassName,\n}: FluidTabsProps) {\n  const layoutId = useId();\n  const keyboardSelectionRef = useRef(false);\n  const shouldReduceMotion = useReducedMotion();\n  const fallbackValue = tabs.find((tab) => !tab.disabled)?.value;\n  const [internalValue, setInternalValue] = useState(\n    defaultValue ?? fallbackValue,\n  );\n  const activeValue = value ?? internalValue;\n\n  useEffect(() => {\n    keyboardSelectionRef.current = false;\n  });\n\n  return (\n    <Tabs.Root\n      value={value}\n      defaultValue={defaultValue ?? fallbackValue}\n      orientation=\"horizontal\"\n      onValueChange={(nextValue) => {\n        if (typeof nextValue !== \"string\") return;\n        if (value === undefined) setInternalValue(nextValue);\n        onValueChange?.(nextValue);\n      }}\n      className={cn(\"relative w-fit max-w-full overflow-x-auto\", className)}\n      style={tokenStyle}\n    >\n      <LayoutGroup id={layoutId}>\n        <Tabs.List\n          aria-label={ariaLabel}\n          className={cn(\n            \"flex w-max gap-1 p-1\",\n            variant === \"capsule\" && \"rounded-xl bg-(--fluid-tabs-surface)\",\n            listClassName,\n          )}\n        >\n          {tabs.map((tab) => (\n            <Tabs.Tab\n              key={tab.value}\n              value={tab.value}\n              disabled={tab.disabled}\n              aria-controls={tab.ariaControls}\n              onKeyDown={() => {\n                keyboardSelectionRef.current = true;\n              }}\n              onPointerDown={() => {\n                keyboardSelectionRef.current = false;\n              }}\n              className={(state) =>\n                cn(\n                  \"group relative flex cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium text-(--fluid-tabs-label) transition-colors duration-150\",\n                  sizeClasses[size],\n                  \"hover:text-(--fluid-tabs-label-active) focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--fluid-tabs-focus-ring)\",\n                  \"disabled:pointer-events-none disabled:opacity-45\",\n                  variant === \"underline\" && \"rounded-none\",\n                  state.active && \"text-(--fluid-tabs-label-active)\",\n                )\n              }\n            >\n              <span\n                aria-hidden=\"true\"\n                className={cn(\n                  \"pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100\",\n                  hoverClassName,\n                  variant === \"underline\" &&\n                    \"inset-x-1 top-auto h-0.5 rounded-full\",\n                  variant === \"capsule\" &&\n                    \"rounded-(--fluid-tabs-capsule-border-radius)\",\n                )}\n              />\n              {activeValue === tab.value && (\n                <motion.span\n                  aria-hidden=\"true\"\n                  layoutId={`${layoutId}-active`}\n                  className={cn(\n                    \"pointer-events-none absolute\",\n                    variant === \"capsule\" &&\n                      \"inset-0 rounded-lg border border-(--tabs-indicator-border)\",\n                    variant === \"underline\" &&\n                      \"inset-x-1 bottom-0 h-0.5 rounded-full\",\n                    activeIndicatorClassName ??\n                      (variant === \"underline\"\n                        ? \"bg-foreground\"\n                        : \"bg-[var(--fluid-tabs-surface-active)]\"),\n                  )}\n                  transition={\n                    shouldReduceMotion || keyboardSelectionRef.current\n                      ? { duration: 0 }\n                      : {\n                          type: \"spring\",\n                          stiffness: 320,\n                          damping: 40,\n                          mass: 0.9,\n                        }\n                  }\n                />\n              )}\n              <span className=\"relative z-1\">{tab.title}</span>\n            </Tabs.Tab>\n          ))}\n        </Tabs.List>\n      </LayoutGroup>\n    </Tabs.Root>\n  );\n}\n",
      "target": "components/ui/fluid-tabs/fluid-tabs.tsx"
    }
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils"
  ]
}