{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable-tabs",
  "type": "registry:ui",
  "title": "Expandable Tabs",
  "description": "An accessible Base UI tab selector that expands the active icon into a labeled pill with input-aware layout motion.",
  "files": [
    {
      "path": "registry/sonaui/expandable-tabs/expandable-tabs.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Tabs } from \"@base-ui/react/tabs\";\nimport {\n  AnimatePresence,\n  MotionConfig,\n  type MotionConfigProps,\n  motion,\n  useReducedMotion,\n} from \"motion/react\";\nimport { useState } from \"react\";\nimport type { IconType } from \"react-icons\";\n\nimport { motionTransition } from \"@/lib/sona-motion\";\nimport { cn } from \"@/lib/sona-utils\";\n\nexport interface ExpandableTabItem {\n  /** Stable value used to identify the tab. */\n  value: string;\n  /** Text revealed when the tab is active. */\n  title: string;\n  /** Icon displayed for the tab. */\n  icon: IconType;\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 ExpandableTabsProps {\n  /** Tabs displayed in the expandable horizontal tab list. */\n  tabs: ExpandableTabItem[];\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  /** Accessible label for the tab list. @default \"Expandable tabs\" */\n  ariaLabel?: string;\n  /** Additional classes for the root container. */\n  className?: string;\n  /** Additional classes for the tab list. */\n  listClassName?: string;\n  /** Additional classes for the active tab. */\n  activeTabClassName?: string;\n  /** Motion configuration applied to the layout and label transitions. */\n  motionConfig?: MotionConfigProps;\n}\n\nexport default function ExpandableTabs({\n  tabs,\n  value,\n  defaultValue,\n  onValueChange,\n  ariaLabel = \"Expandable tabs\",\n  className,\n  listClassName,\n  activeTabClassName = \"bg-accent text-accent-foreground\",\n  motionConfig,\n}: ExpandableTabsProps) {\n  const fallbackValue = tabs.find((tab) => !tab.disabled)?.value;\n  const [internalValue, setInternalValue] = useState(\n    defaultValue ?? fallbackValue,\n  );\n  const [inputModality, setInputModality] = useState<\"keyboard\" | \"pointer\">(\n    \"pointer\",\n  );\n  const shouldReduceMotion = useReducedMotion();\n  const activeValue = value ?? internalValue;\n  const transition =\n    shouldReduceMotion || inputModality === \"keyboard\"\n      ? motionTransition.instant\n      : (motionConfig?.transition ?? motionTransition.spatial);\n\n  return (\n    <Tabs.Root\n      value={activeValue}\n      orientation=\"horizontal\"\n      onValueChange={(nextValue) => {\n        if (typeof nextValue !== \"string\") return;\n        if (value === undefined) setInternalValue(nextValue);\n        onValueChange?.(nextValue);\n      }}\n      className={className}\n    >\n      <MotionConfig {...motionConfig} transition={transition}>\n        <Tabs.List\n          aria-label={ariaLabel}\n          className={cn(\n            \"flex gap-2 rounded-full border bg-transparent p-2\",\n            listClassName,\n          )}\n          onKeyDownCapture={() => setInputModality(\"keyboard\")}\n          onPointerDownCapture={() => setInputModality(\"pointer\")}\n        >\n          {tabs.map((tab) => {\n            const isActive = activeValue === tab.value;\n            return (\n              <Tabs.Tab\n                key={tab.value}\n                value={tab.value}\n                disabled={tab.disabled}\n                aria-label={tab.title}\n                aria-controls={tab.ariaControls}\n                render={<motion.button layout />}\n                className={cn(\n                  \"flex cursor-pointer items-center gap-2 overflow-clip rounded-full p-2\",\n                  \"transition-colors duration-200\",\n                  \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n                  \"disabled:pointer-events-none disabled:opacity-50\",\n                  isActive && activeTabClassName,\n                )}\n              >\n                <motion.span\n                  layout\n                  className=\"flex h-full grow items-center justify-center\"\n                >\n                  <tab.icon className=\"text-lg\" aria-hidden=\"true\" />\n                </motion.span>\n                <AnimatePresence initial={false}>\n                  {isActive && (\n                    <motion.span\n                      layout\n                      className=\"overflow-hidden whitespace-nowrap text-sm leading-none\"\n                      initial={{ opacity: 0, width: 0 }}\n                      animate={{ opacity: 1, width: \"auto\" }}\n                      exit={{ opacity: 0, width: 0 }}\n                    >\n                      {tab.title}\n                    </motion.span>\n                  )}\n                </AnimatePresence>\n              </Tabs.Tab>\n            );\n          })}\n        </Tabs.List>\n      </MotionConfig>\n    </Tabs.Root>\n  );\n}\n",
      "target": "components/ui/expandable-tabs/expandable-tabs.tsx"
    }
  ],
  "dependencies": [
    "@base-ui/react",
    "motion",
    "react-icons"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils",
    "@sona-ui/sona-motion"
  ]
}