{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-tabs",
  "type": "registry:component",
  "title": "Animated tabs",
  "description": "Tabs with a sliding pill indicator and panels that fade in the direction of travel. Full WAI-ARIA tabs keyboard support with Arrow keys, Home and End.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/animated-tabs.tsx",
      "type": "registry:ui",
      "target": "components/ui/animated-tabs.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Tabs with a pill that slides between triggers and panels that cross\n * fade in the direction of travel. Follows the WAI-ARIA tabs pattern:\n * roving focus, Arrow keys, Home and End, and automatic activation.\n */\ntype Tab = { id: string; label: string; content: React.ReactNode }\n\ntype AnimatedTabsProps = {\n  tabs?: Tab[]\n  defaultValue?: string\n  onValueChange?: (id: string) => void\n  className?: string\n}\n\nconst DEMO: Tab[] = [\n  { id: \"overview\", label: \"Overview\", content: <p>Revenue is up 12.4% this month, driven by annual upgrades on the Team plan.</p> },\n  { id: \"activity\", label: \"Activity\", content: <p>32 new signups, 4 upgrades and 1 cancellation in the last 24 hours.</p> },\n  { id: \"billing\", label: \"Billing\", content: <p>Next invoice on Oct 14 for $1,280. Visa ending 4242 is the default method.</p> },\n  { id: \"settings\", label: \"Settings\", content: <p>Workspace name, domain, SSO and the danger zone live here.</p> },\n]\n\nfunction AnimatedTabs({ tabs = DEMO, defaultValue, onValueChange, className }: AnimatedTabsProps) {\n  const reduce = useReducedMotion()\n  const id = React.useId()\n  const [active, setActive] = React.useState(defaultValue ?? tabs[0]?.id)\n  const [dir, setDir] = React.useState(1)\n  const refs = React.useRef<(HTMLButtonElement | null)[]>([])\n  const index = tabs.findIndex((t) => t.id === active)\n  const select = (i: number, focus = false) => {\n    const t = tabs[(i + tabs.length) % tabs.length]\n    setDir(tabs.findIndex((x) => x.id === t.id) > index ? 1 : -1)\n    setActive(t.id)\n    onValueChange?.(t.id)\n    if (focus) refs.current[(i + tabs.length) % tabs.length]?.focus()\n  }\n  const current = tabs[index]\n  return (\n    <div data-slot=\"animated-tabs\" className={cn(\"w-full max-w-lg\", className)}>\n      <div\n        role=\"tablist\"\n        aria-label=\"Sections\"\n        className=\"inline-flex gap-1 rounded-xl border border-border bg-sunken p-1\"\n        onKeyDown={(e) => {\n          const k = e.key\n          if (k === \"ArrowRight\") select(index + 1, true)\n          else if (k === \"ArrowLeft\") select(index - 1, true)\n          else if (k === \"Home\") select(0, true)\n          else if (k === \"End\") select(tabs.length - 1, true)\n          else return\n          e.preventDefault()\n        }}\n      >\n        {tabs.map((t, i) => {\n          const on = t.id === active\n          return (\n            <button\n              key={t.id}\n              ref={(el) => {\n                refs.current[i] = el\n              }}\n              role=\"tab\"\n              id={`${id}-tab-${t.id}`}\n              aria-selected={on}\n              aria-controls={`${id}-panel-${t.id}`}\n              tabIndex={on ? 0 : -1}\n              onClick={() => select(i)}\n              className={cn(\"relative h-8 rounded-lg px-3.5 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-accent\", on ? \"text-fg\" : \"text-fg-muted hover:text-fg\")}\n            >\n              {on ? (\n                <motion.span\n                  layoutId={`${id}-pill`}\n                  className=\"absolute inset-0 rounded-lg border border-border bg-raised shadow-raised\"\n                  transition={reduce ? { duration: 0 } : { type: \"spring\", bounce: 0.2, duration: 0.45 }}\n                />\n              ) : null}\n              <span className=\"relative\">{t.label}</span>\n            </button>\n          )\n        })}\n      </div>\n      <div className=\"relative mt-4 overflow-hidden\">\n        <AnimatePresence mode=\"popLayout\" initial={false} custom={dir}>\n          <motion.div\n            key={current?.id}\n            role=\"tabpanel\"\n            id={`${id}-panel-${current?.id}`}\n            aria-labelledby={`${id}-tab-${current?.id}`}\n            tabIndex={0}\n            custom={dir}\n            initial={reduce ? { opacity: 0 } : { opacity: 0, x: dir * 24, filter: \"blur(4px)\" }}\n            animate={{ opacity: 1, x: 0, filter: \"blur(0px)\" }}\n            exit={reduce ? { opacity: 0 } : { opacity: 0, x: dir * -24, filter: \"blur(4px)\" }}\n            transition={{ duration: 0.28, ease: [0.2, 0, 0, 1] }}\n            className=\"rounded-xl border border-border bg-surface p-5 text-sm leading-[1.6] text-fg-muted shadow-raised outline-none focus-visible:ring-2 focus-visible:ring-accent\"\n          >\n            {current?.content}\n          </motion.div>\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n\nexport { AnimatedTabs }\nexport type { AnimatedTabsProps }\n"
    }
  ]
}
