{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-tabs",
  "title": "Animated Tabs",
  "description": "Radix tabs with a pill or underline indicator that slides between triggers and panels that fade in.",
  "dependencies": [
    "motion",
    "@radix-ui/react-tabs"
  ],
  "files": [
    {
      "path": "registry/tealmac/animated-tabs.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst snappySpring = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 34,\n  mass: 0.8,\n} as const\nconst easeOut = [0.22, 1, 0.36, 1] as const\n\ntype AnimatedTabsVariant = \"pill\" | \"underline\"\n\ntype AnimatedTabsContextValue = {\n  value: string\n  indicatorId: string\n  variant: AnimatedTabsVariant\n}\n\nconst AnimatedTabsContext =\n  React.createContext<AnimatedTabsContextValue | null>(null)\n\nfunction useAnimatedTabs(part: string) {\n  const context = React.useContext(AnimatedTabsContext)\n  if (!context) throw new Error(`<${part}> must be used inside <AnimatedTabs>`)\n  return context\n}\n\ntype AnimatedTabsProps = React.ComponentProps<typeof TabsPrimitive.Root> & {\n  /** Indicator style: a sliding pill behind the label, or a sliding underline. */\n  variant?: AnimatedTabsVariant\n}\n\nfunction AnimatedTabs({\n  variant = \"pill\",\n  value: valueProp,\n  defaultValue = \"\",\n  onValueChange,\n  className,\n  ...props\n}: AnimatedTabsProps) {\n  const [internal, setInternal] = React.useState(defaultValue)\n  const value = valueProp ?? internal\n  const indicatorId = `animated-tabs-${React.useId()}`\n  const context = React.useMemo(\n    () => ({ value, indicatorId, variant }),\n    [value, indicatorId, variant]\n  )\n\n  return (\n    <AnimatedTabsContext.Provider value={context}>\n      <TabsPrimitive.Root\n        data-slot=\"animated-tabs\"\n        data-variant={variant}\n        value={value}\n        onValueChange={(next) => {\n          if (valueProp === undefined) setInternal(next)\n          onValueChange?.(next)\n        }}\n        className={cn(\"flex flex-col gap-4\", className)}\n        {...props}\n      />\n    </AnimatedTabsContext.Provider>\n  )\n}\n\nfunction AnimatedTabsList({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.List>) {\n  const { variant } = useAnimatedTabs(\"AnimatedTabsList\")\n  return (\n    <TabsPrimitive.List\n      data-slot=\"animated-tabs-list\"\n      className={cn(\n        \"relative inline-flex w-fit max-w-full [scrollbar-width:none] items-center overflow-x-auto\",\n        variant === \"pill\"\n          ? \"bg-muted h-10 gap-0.5 rounded-full p-1\"\n          : \"h-10 gap-5 border-b\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AnimatedTabsTrigger({\n  value,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  const {\n    value: selected,\n    indicatorId,\n    variant,\n  } = useAnimatedTabs(\"AnimatedTabsTrigger\")\n  const reduceMotion = useReducedMotion()\n  const active = selected === value\n\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"animated-tabs-trigger\"\n      value={value}\n      className={cn(\n        \"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 data-[state=active]:text-foreground relative inline-flex h-full shrink-0 items-center justify-center gap-1.5 text-sm font-medium whitespace-nowrap transition-colors duration-150 outline-none select-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        variant === \"pill\" ? \"rounded-full px-3.5\" : \"rounded-sm px-0.5\",\n        className\n      )}\n      {...props}\n    >\n      {active && (\n        <motion.span\n          aria-hidden=\"true\"\n          data-slot=\"animated-tabs-indicator\"\n          layoutId={indicatorId}\n          transition={reduceMotion ? { duration: 0 } : snappySpring}\n          className={cn(\n            \"pointer-events-none absolute\",\n            variant === \"pill\"\n              ? \"bg-background dark:bg-accent inset-0 rounded-full shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_color-mix(in_oklch,var(--foreground)_6%,transparent)]\"\n              : \"bg-foreground inset-x-0 bottom-0 h-0.5 rounded-full\"\n          )}\n        />\n      )}\n      <span className=\"relative z-10 inline-flex items-center gap-1.5\">\n        {children}\n      </span>\n    </TabsPrimitive.Trigger>\n  )\n}\n\nfunction AnimatedTabsContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  const reduceMotion = useReducedMotion()\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"animated-tabs-content\"\n      className={cn(\n        \"focus-visible:ring-ring/50 rounded-md outline-none focus-visible:ring-[3px]\",\n        className\n      )}\n      {...props}\n    >\n      {/* Radix mounts only the selected panel, so each panel fades in as it arrives. */}\n      <motion.div\n        initial={\n          reduceMotion ? false : { opacity: 0, y: 6, filter: \"blur(4px)\" }\n        }\n        animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n        transition={{ duration: 0.32, ease: easeOut }}\n      >\n        {children}\n      </motion.div>\n    </TabsPrimitive.Content>\n  )\n}\n\nexport {\n  AnimatedTabs,\n  AnimatedTabsList,\n  AnimatedTabsTrigger,\n  AnimatedTabsContent,\n  type AnimatedTabsProps,\n  type AnimatedTabsVariant,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}