{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-tabs",
  "title": "Animated Tabs",
  "description": "A controlled tab strip with a sliding active indicator — underline and pill variants animated via motion layout animation.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/animated-tabs.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion, type Transition } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedTab {\n  id: string\n  label: string\n}\n\nexport interface AnimatedTabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n  /** Tab items, rendered in order. */\n  tabs: AnimatedTab[]\n  /** Controlled: id of the selected tab. */\n  value: string\n  /** Controlled: called with the id of the tab to select. */\n  onChange: (id: string) => void\n  variant?: \"underline\" | \"pill\"\n}\n\n/**\n * Tab strip only — panels belong to the consumer. The active indicator glides\n * between tabs via motion's shared layout animation (one `layoutId` per\n * instance, scoped with useId so multiple strips never cross-animate).\n */\nexport const AnimatedTabs = React.forwardRef<HTMLDivElement, AnimatedTabsProps>(\n  ({ tabs, value, onChange, variant = \"underline\", className, onKeyDown, ...props }, ref) => {\n    const uid = React.useId()\n    const tabRefs = React.useRef<(HTMLButtonElement | null)[]>([])\n    const reducedMotion = useReducedMotion()\n    const transition: Transition = reducedMotion\n      ? { duration: 0 }\n      : { type: \"spring\", stiffness: 500, damping: 40 }\n\n    const activeIndex = tabs.findIndex(t => t.id === value)\n    // roving tabindex:活动 tab 可聚焦;value 不匹配时兜底到第一个,保证键盘可达\n    const focusIndex = activeIndex === -1 ? 0 : activeIndex\n\n    const selectAndFocus = (index: number) => {\n      const tab = tabs[index]\n      if (!tab) return\n      onChange(tab.id)\n      tabRefs.current[index]?.focus()\n    }\n\n    const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n      onKeyDown?.(e)\n      if (e.defaultPrevented || tabs.length === 0) return\n      if (e.key === \"ArrowRight\") {\n        e.preventDefault()\n        selectAndFocus((focusIndex + 1) % tabs.length)\n      } else if (e.key === \"ArrowLeft\") {\n        e.preventDefault()\n        selectAndFocus((focusIndex - 1 + tabs.length) % tabs.length)\n      } else if (e.key === \"Home\") {\n        e.preventDefault()\n        selectAndFocus(0)\n      } else if (e.key === \"End\") {\n        e.preventDefault()\n        selectAndFocus(tabs.length - 1)\n      }\n    }\n\n    return (\n      <div\n        className={cn(\n          \"inline-flex max-w-full items-center overflow-x-auto\",\n          variant === \"underline\" ? \"gap-1 border-b\" : \"gap-1 rounded-lg bg-muted p-1\",\n          className,\n        )}\n        onKeyDown={handleKeyDown}\n        ref={ref}\n        role=\"tablist\"\n        {...props}\n      >\n        {tabs.map((tab, i) => {\n          const active = tab.id === value\n          return (\n            <button\n              aria-selected={active}\n              className={cn(\n                \"relative shrink-0 cursor-pointer whitespace-nowrap text-sm font-medium transition-colors\",\n                \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\",\n                variant === \"underline\" ? \"rounded-t-md px-3 py-2\" : \"rounded-md px-3 py-1.5\",\n                active ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\",\n              )}\n              key={tab.id}\n              onClick={() => onChange(tab.id)}\n              ref={el => {\n                tabRefs.current[i] = el\n              }}\n              role=\"tab\"\n              tabIndex={i === focusIndex ? 0 : -1}\n              type=\"button\"\n            >\n              {active && (\n                <motion.span\n                  aria-hidden=\"true\"\n                  className={cn(\n                    \"pointer-events-none absolute\",\n                    variant === \"underline\"\n                      ? \"inset-x-0 bottom-0 h-0.5 bg-primary\"\n                      : \"inset-0 rounded-md bg-background shadow-sm\",\n                  )}\n                  initial={false}\n                  layoutId={`${uid}-indicator`}\n                  transition={transition}\n                />\n              )}\n              <span className=\"relative z-10\">{tab.label}</span>\n            </button>\n          )\n        })}\n      </div>\n    )\n  },\n)\n\nAnimatedTabs.displayName = \"AnimatedTabs\"\n\nexport default AnimatedTabs\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}