{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs",
  "title": "Tabs",
  "description": "Segmented and underline tabs with a gliding active indicator.",
  "dependencies": [
    "class-variance-authority",
    "radix-ui"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/tabs.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Tabs as TabsPrimitive } from \"radix-ui\";\nimport { cva } from \"class-variance-authority\";\n\nimport { gsap, motionScale, useMergedRef } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\ntype TabsVariant = \"segmented\" | \"underline\";\n\nfunction Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {\n  return <TabsPrimitive.Root data-slot=\"tabs\" className={cn(\"flex flex-col gap-3 data-[orientation=vertical]:flex-row\", className)} {...props} />;\n}\n\nconst listVariants = cva(\"relative inline-flex w-fit max-w-full items-center text-muted-foreground\", {\n  variants: {\n    variant: {\n      segmented: \"h-ctl-md overflow-x-auto rounded-lg bg-muted p-[3px] shadow-well [scrollbar-width:none] data-[orientation=vertical]:h-auto data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n      underline:\n        \"h-ctl-md gap-5 border-b border-border data-[orientation=vertical]:h-auto data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-start data-[orientation=vertical]:gap-1 data-[orientation=vertical]:border-s data-[orientation=vertical]:border-b-0 data-[orientation=vertical]:ps-3\",\n    },\n  },\n  defaultVariants: { variant: \"segmented\" },\n});\n\nconst indicatorVariants = cva(\"pointer-events-none absolute opacity-0 [[data-ready]>&]:opacity-100\", {\n  variants: {\n    variant: {\n      segmented: \"rounded-md border border-border/60 bg-card shadow-control\",\n      underline: \"rounded-full bg-signal [[data-orientation=horizontal]>&]:-bottom-px [[data-orientation=horizontal]>&]:h-0.5 [[data-orientation=vertical]>&]:-start-px [[data-orientation=vertical]>&]:w-0.5\",\n    },\n  },\n  defaultVariants: { variant: \"segmented\" },\n});\n\n/**\n * One indicator travels between triggers with GSAP: it stretches to cover the old and new tab, then\n * its trailing edge catches up (a short \"caterpillar\" move). Before hydration the active trigger styles itself.\n */\nfunction TabsList({ className, variant = \"segmented\", children, ref, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & { variant?: TabsVariant }) {\n  const listRef = React.useRef<HTMLDivElement>(null);\n  const indicatorRef = React.useRef<HTMLSpanElement>(null);\n  const mergedRef = useMergedRef(listRef, ref);\n  useIndicator(listRef, indicatorRef, variant);\n\n  return (\n    <TabsPrimitive.List ref={mergedRef} data-slot=\"tabs-list\" data-variant={variant} className={cn(listVariants({ variant }), className)} {...props}>\n      <span ref={indicatorRef} aria-hidden=\"true\" data-slot=\"tabs-indicator\" className={indicatorVariants({ variant })} />\n      {children}\n    </TabsPrimitive.List>\n  );\n}\n\nfunction TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"relative z-10 inline-flex h-full items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        \"[[data-variant=segmented]_&]:flex-1 [[data-variant=segmented][data-orientation=vertical]_&]:h-ctl-sm [[data-variant=segmented][data-orientation=vertical]_&]:justify-start\",\n        \"[[data-variant=underline]_&]:px-0.5 [[data-variant=underline][data-orientation=vertical]_&]:h-ctl-sm\",\n        \"[[data-slot=tabs-list][data-variant=segmented]:not([data-ready])_&]:data-[state=active]:bg-card [[data-slot=tabs-list][data-variant=segmented]:not([data-ready])_&]:data-[state=active]:shadow-control\",\n        \"[[data-slot=tabs-list][data-variant=underline]:not([data-ready])_&]:data-[state=active]:shadow-[inset_0_-2px_0_var(--primary)]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"tabs-content\"\n      className={cn(\"flex-1 outline-none focus-visible:ring-3 focus-visible:ring-ring/50 data-[state=active]:animate-in data-[state=active]:fade-in-0 data-[state=active]:duration-200\", className)}\n      {...props}\n    />\n  );\n}\n\ntype Box = { left: number; top: number; width: number; height: number };\n\nfunction useIndicator(listRef: React.RefObject<HTMLDivElement | null>, indicatorRef: React.RefObject<HTMLSpanElement | null>, variant: TabsVariant) {\n  React.useLayoutEffect(() => {\n    const list = listRef.current;\n    const indicator = indicatorRef.current;\n    if (!list || !indicator) return;\n    let previous: Box | null = null;\n    let timeline: gsap.core.Timeline | null = null;\n\n    const place = (animate: boolean) => {\n      const active = list.querySelector<HTMLElement>(':scope > [data-slot=\"tabs-trigger\"][data-state=\"active\"]');\n      if (!active) return;\n      const box: Box = { left: active.offsetLeft, top: active.offsetTop, width: active.offsetWidth, height: active.offsetHeight };\n      const vertical = list.getAttribute(\"aria-orientation\") === \"vertical\";\n      const [pos, size] = vertical ? ([\"top\", \"height\"] as const) : ([\"left\", \"width\"] as const);\n      const cross = variant === \"segmented\" ? (vertical ? { left: box.left, width: box.width } : { top: box.top, height: box.height }) : {};\n      const scale = motionScale(list);\n      timeline?.kill();\n\n      if (!animate || !previous || scale === 0) {\n        gsap.set(indicator, { [pos]: box[pos], [size]: box[size], ...cross });\n      } else {\n        const from = previous;\n        const forward = box[pos] > from[pos];\n        timeline = gsap.timeline({ defaults: { ease: \"cue\" } });\n        gsap.set(indicator, cross);\n        if (forward) {\n          timeline.to(indicator, { [size]: box[pos] + box[size] - from[pos], duration: 0.16 }).to(indicator, { [pos]: box[pos], [size]: box[size], duration: 0.28 });\n        } else {\n          timeline.to(indicator, { [pos]: box[pos], [size]: from[pos] + from[size] - box[pos], duration: 0.16 }).to(indicator, { [size]: box[size], duration: 0.28 });\n        }\n      }\n      previous = box;\n      list.dataset.ready = \"\";\n    };\n\n    place(false);\n    const mutations = new MutationObserver(() => place(true));\n    mutations.observe(list, { subtree: true, attributes: true, attributeFilter: [\"data-state\"] });\n    const resize = new ResizeObserver(() => place(false));\n    resize.observe(list);\n    return () => {\n      timeline?.kill();\n      mutations.disconnect();\n      resize.disconnect();\n    };\n  }, [listRef, indicatorRef, variant]);\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\nexport type { TabsVariant };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/tabs",
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}