{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs-subtle",
  "title": "Tabs Subtle",
  "description": "A pill-style tab list where the selected indicator springs between tabs, built on the WAI-ARIA tabs pattern with full roving-focus keyboard support.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/tabs-subtle.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\nimport {\n  Children,\n  createContext,\n  forwardRef,\n  isValidElement,\n  useContext,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type ReactElement,\n  type ReactNode,\n} from 'react'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { cn } from '@/lib/utils'\nimport { springs } from '@/lib/motion-tokens'\n\ninterface TabsSubtleContextValue {\n  selectedValue: string\n  onSelect: (value: string) => void\n  idPrefix: string\n  layoutId: string\n}\n\nconst TabsSubtleContext = createContext<TabsSubtleContextValue | null>(null)\n\nfunction useTabsSubtleContext() {\n  const ctx = useContext(TabsSubtleContext)\n  if (!ctx) throw new Error('TabsSubtleItem/TabsSubtlePanel must be used within TabsSubtle')\n  return ctx\n}\n\nexport interface TabsSubtleProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n  children: ReactNode\n  value?: string\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n  idPrefix?: string\n}\n\nexport const TabsSubtle = forwardRef<HTMLDivElement, TabsSubtleProps>(\n  (\n    { children, value, defaultValue, onValueChange, idPrefix: idPrefixProp, className, ...props },\n    ref,\n  ) => {\n    const containerRef = useRef<HTMLDivElement>(null)\n    const generatedId = useId()\n    const layoutId = useId()\n    const idPrefix = idPrefixProp ?? generatedId\n\n    const items = useMemo(\n      () =>\n        Children.toArray(children).filter(\n          (child): child is ReactElement<{ value: string }> =>\n            isValidElement(child) &&\n            typeof child.props === 'object' &&\n            child.props !== null &&\n            'value' in child.props,\n        ),\n      [children],\n    )\n\n    const isControlled = value !== undefined\n    const [internal, setInternal] = useState(defaultValue ?? items[0]?.props.value ?? '')\n    const selectedValue = isControlled ? value : internal\n\n    const onSelect = (next: string) => {\n      if (!isControlled) setInternal(next)\n      onValueChange?.(next)\n    }\n\n    const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n      const els = Array.from(\n        containerRef.current?.querySelectorAll('[role=\"tab\"]') ?? [],\n      ) as HTMLElement[]\n      const currentIdx = els.findIndex((el) => el === document.activeElement)\n      if (currentIdx === -1) return\n\n      if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {\n        e.preventDefault()\n        const dir = ['ArrowRight', 'ArrowDown'].includes(e.key) ? 1 : -1\n        const next = (currentIdx + dir + els.length) % els.length\n        els[next]?.focus()\n        els[next]?.click()\n      } else if (e.key === 'Home') {\n        e.preventDefault()\n        els[0]?.focus()\n        els[0]?.click()\n      } else if (e.key === 'End') {\n        e.preventDefault()\n        els[els.length - 1]?.focus()\n        els[els.length - 1]?.click()\n      }\n    }\n\n    return (\n      <TabsSubtleContext.Provider value={{ selectedValue, onSelect, idPrefix, layoutId }}>\n        <div\n          ref={(node) => {\n            ;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node\n            if (typeof ref === 'function') ref(node)\n            else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n          }}\n          role=\"tablist\"\n          aria-orientation=\"horizontal\"\n          onKeyDown={handleKeyDown}\n          className={cn(\n            'relative inline-flex items-center gap-0.5 rounded-lg squircle-corners bg-muted p-1 select-none overflow-x-auto max-w-full',\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TabsSubtleContext.Provider>\n    )\n  },\n)\nTabsSubtle.displayName = 'TabsSubtle'\n\nexport interface TabsSubtleItemProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'value'> {\n  value: string\n  icon?: ReactNode\n  label: string\n}\n\nexport const TabsSubtleItem = forwardRef<HTMLButtonElement, TabsSubtleItemProps>(\n  ({ value, icon, label, className, ...props }, ref) => {\n    const { selectedValue, onSelect, idPrefix, layoutId } = useTabsSubtleContext()\n    const reduceMotion = useReducedMotion()\n    const isSelected = selectedValue === value\n\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        id={`${idPrefix}-tab-${value}`}\n        role=\"tab\"\n        aria-selected={isSelected}\n        aria-controls={`${idPrefix}-panel-${value}`}\n        tabIndex={isSelected ? 0 : -1}\n        onPointerDown={(e) => e.preventDefault()}\n        onClick={() => onSelect(value)}\n        className={cn(\n          'relative z-10 flex items-center gap-1.5 rounded-md squircle-corners px-3 py-1.5 text-[13px] font-medium cursor-pointer bg-transparent border-none outline-none whitespace-nowrap',\n          'transition-colors duration-(--motion-dur-fast) motion-reduce:transition-none',\n          isSelected ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',\n          'focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 focus-visible:ring-offset-background',\n          className,\n        )}\n        {...props}\n      >\n        {isSelected && (\n          <motion.span\n            layoutId={reduceMotion ? undefined : `${layoutId}-pill`}\n            className=\"absolute inset-0 -z-10 rounded-md squircle-corners bg-background shadow-sm\"\n            transition={reduceMotion ? { duration: 0 } : springs.settle}\n          />\n        )}\n        {icon && (\n          <span aria-hidden=\"true\" className=\"shrink-0 [&_svg]:size-4\">\n            {icon}\n          </span>\n        )}\n        <span>{label}</span>\n      </button>\n    )\n  },\n)\nTabsSubtleItem.displayName = 'TabsSubtleItem'\n\nexport interface TabsSubtlePanelProps extends Omit<HTMLAttributes<HTMLDivElement>, 'value'> {\n  value: string\n  children: ReactNode\n}\n\nexport const TabsSubtlePanel = forwardRef<HTMLDivElement, TabsSubtlePanelProps>(\n  ({ value, children, className, ...props }, ref) => {\n    const { selectedValue, idPrefix } = useTabsSubtleContext()\n    const isSelected = selectedValue === value\n\n    return (\n      <div\n        ref={ref}\n        id={`${idPrefix}-panel-${value}`}\n        role=\"tabpanel\"\n        aria-labelledby={`${idPrefix}-tab-${value}`}\n        hidden={!isSelected}\n        tabIndex={0}\n        className={cn('outline-none', className)}\n        {...props}\n      >\n        {isSelected && children}\n      </div>\n    )\n  },\n)\nTabsSubtlePanel.displayName = 'TabsSubtlePanel'\n\nexport function TabsSubtlePreview() {\n  return (\n    <div\n      className=\"w-full h-full min-h-50 rounded-lg overflow-hidden flex flex-col items-center justify-center gap-4 p-4\"\n      style={{ backgroundColor: 'var(--color-surface)' }}\n    >\n      <TabsSubtle defaultValue=\"overview\">\n        <TabsSubtleItem value=\"overview\" label=\"Overview\" />\n        <TabsSubtleItem value=\"activity\" label=\"Activity\" />\n        <TabsSubtleItem value=\"settings\" label=\"Settings\" />\n      </TabsSubtle>\n    </div>\n  )\n}\n"
    },
    {
      "path": "lib/motion-tokens.ts",
      "type": "registry:lib",
      "content": "export const durations = {\n  instant: '50ms',\n  fast: '120ms',\n  base: '200ms',\n  slow: '320ms',\n} as const\n\nexport const easings = {\n  out: 'cubic-bezier(0.22, 1, 0.36, 1)',\n  inOut: 'cubic-bezier(0.65, 0, 0.35, 1)',\n} as const\n\nexport const springs = {\n  fast: { type: 'spring', stiffness: 400, damping: 30, mass: 0.8 },\n  press: { type: 'spring', stiffness: 700, damping: 32, mass: 1 },\n  moderate: { type: 'spring', stiffness: 300, damping: 24, mass: 1 },\n  settle: { type: 'spring', stiffness: 260, damping: 26, mass: 1 },\n} as const\n"
    }
  ],
  "type": "registry:ui"
}
