{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs-vertical",
  "title": "Vertical tabs",
  "description": "Styled tabs: the Vertical set. 5 decorative tabs variations (LeftTabs, RightTabs, CardTabs, RailTabs, CompactTabs) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/tabs-vertical.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Activity, LayoutGrid, Settings, User } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Vertical tabs family: 5 vertical tab strips. The tab list stands beside the panel;\n   the left, right, card, rail and compact variants change the layout and the\n   indicator language. role=\"tablist\" + aria-orientation=\"vertical\", navigation with\n   ArrowUp / ArrowDown, a roving tabIndex. Each export is a complete tabs component -\n   internal active-tab state and a role=\"tabpanel\" panel. The ids are produced with\n   React.useId(). Color comes ONLY from tokens, via alpha color-mix. The indicator\n   slides with a framer-motion layoutId; it switches instantly under reduced\n   motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype TabItem = { value: string; label: React.ReactNode; panel: React.ReactNode }\n\ntype TabsProps = {\n  className?: string\n  size?: StyledSize\n  tabs?: TabItem[]\n  value?: string\n  defaultValue?: string\n  onValueChange?: (v: string) => void\n}\n\nconst DEFAULT_TABS: TabItem[] = [\n  { value: \"overview\", label: \"Overview\", panel: \"A summary of the workspace: recent activity, open items and the team.\" },\n  { value: \"activity\", label: \"Activity\", panel: \"Every event from the last seven days, newest first.\" },\n  { value: \"settings\", label: \"Settings\", panel: \"Workspace name, members and notification preferences.\" },\n]\n\nconst ICON_TABS: TabItem[] = [\n  { value: \"overview\", label: <><LayoutGrid /> Overview</>, panel: \"A summary of the workspace: recent activity, open items and the team.\" },\n  { value: \"activity\", label: <><Activity /> Activity</>, panel: \"Every event from the last seven days, newest first.\" },\n  { value: \"account\", label: <><User /> Account</>, panel: \"Your profile, email address and connected accounts.\" },\n  { value: \"settings\", label: <><Settings /> Settings</>, panel: \"Workspace name, members and notification preferences.\" },\n]\n\nconst sizeTab: Record<StyledSize, string> = {\n  sm: \"h-8 px-3 text-xs\",\n  md: \"h-9 px-4 text-sm\",\n  lg: \"h-10 px-5 text-sm\",\n  xl: \"h-11 px-6 text-base\",\n}\n\nconst tabBase =\n  \"relative z-10 inline-flex w-full select-none items-center justify-start gap-2 whitespace-nowrap font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&>svg]:shrink-0 [&_i]:text-base [&_i]:leading-none [&>i]:shrink-0\"\n\nconst panelBase =\n  \"min-w-0 flex-1 rounded-lg border border-border bg-card p-4 text-sm text-muted-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\n/* Vertical navigation: ArrowUp / ArrowDown move the selection, and focus travels with it. */\nfunction useTabsState(props: TabsProps, fallback: TabItem[]) {\n  const { tabs, value, defaultValue, onValueChange } = props\n  const list = React.useMemo(() => (tabs && tabs.length ? tabs : fallback), [tabs, fallback])\n  const [internal, setInternal] = React.useState<string>(() => defaultValue ?? list[0]?.value ?? \"\")\n  const active = value ?? internal\n  const refs = React.useRef<(HTMLButtonElement | null)[]>([])\n  const select = React.useCallback(\n    (v: string) => {\n      if (value === undefined) setInternal(v)\n      onValueChange?.(v)\n    },\n    [value, onValueChange]\n  )\n  const onKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key !== \"ArrowUp\" && e.key !== \"ArrowDown\") return\n    e.preventDefault()\n    const idx = list.findIndex((t) => t.value === active)\n    const dir = e.key === \"ArrowDown\" ? 1 : -1\n    const ni = (idx + dir + list.length) % list.length\n    select(list[ni].value)\n    refs.current[ni]?.focus()\n  }\n  return { list, active, select, refs, onKeyDown }\n}\n\nfunction useSlide() {\n  const reduce = useReducedMotion()\n  return reduce\n    ? { duration: 0 }\n    : { type: \"spring\" as const, stiffness: 420, damping: 34 }\n}\n\ntype VerticalConfig = {\n  fallback: TabItem[]\n  /** Panelin listeye gore yeri. */\n  side: \"left\" | \"right\"\n  root?: string\n  bar: string\n  tab: string\n  indicator: string\n  active: string\n  inactive: string\n}\n\n/* Shared shell: a vertical list plus panel. side=\"right\" moves the list to the right. */\nfunction VerticalTabsShell({ props, config }: { props: TabsProps; config: VerticalConfig }) {\n  const { className, size = \"md\" } = props\n  const { list, active, select, refs, onKeyDown } = useTabsState(props, config.fallback)\n  const uid = React.useId()\n  const transition = useSlide()\n  const current = list.find((t) => t.value === active) ?? list[0]\n\n  const bar = (\n    <div\n      role=\"tablist\"\n      aria-orientation=\"vertical\"\n      onKeyDown={onKeyDown}\n      className={cn(\"flex w-44 shrink-0 flex-col gap-1\", config.bar)}\n    >\n      {list.map((tab, i) => {\n        const on = tab.value === active\n        return (\n          <button\n            key={tab.value}\n            ref={(el) => {\n              refs.current[i] = el\n            }}\n            type=\"button\"\n            role=\"tab\"\n            id={`${uid}-tab-${tab.value}`}\n            aria-selected={on}\n            aria-controls={`${uid}-panel-${tab.value}`}\n            tabIndex={on ? 0 : -1}\n            onClick={() => select(tab.value)}\n            className={cn(tabBase, sizeTab[size], config.tab, on ? config.active : config.inactive)}\n          >\n            {on ? (\n              <motion.span\n                layoutId={`${uid}-vertical-indicator`}\n                transition={transition}\n                className={cn(\"absolute -z-10\", config.indicator)}\n              />\n            ) : null}\n            {tab.label}\n          </button>\n        )\n      })}\n    </div>\n  )\n\n  const panel = current ? (\n    <div\n      role=\"tabpanel\"\n      id={`${uid}-panel-${current.value}`}\n      aria-labelledby={`${uid}-tab-${current.value}`}\n      tabIndex={0}\n      className={panelBase}\n    >\n      {current.panel}\n    </div>\n  ) : null\n\n  return (\n    <div data-slot=\"styled-tabs\" className={cn(\"flex w-full items-stretch gap-4\", config.root, className)}>\n      {config.side === \"right\" ? (\n        <>\n          {panel}\n          {bar}\n        </>\n      ) : (\n        <>\n          {bar}\n          {panel}\n        </>\n      )}\n    </div>\n  )\n}\n\n/* Left: liste solda, aktif sekmede sol kenar cizgisi. */\nexport function LeftTabs(props: TabsProps) {\n  return (\n    <VerticalTabsShell\n      props={props}\n      config={{\n        fallback: DEFAULT_TABS,\n        side: \"left\",\n        bar: \"border-l border-border pl-1\",\n        tab: \"rounded-md\",\n        indicator: \"inset-y-1 -left-1 w-0.5 rounded-full bg-primary\",\n        active: \"text-foreground\",\n        inactive: \"text-muted-foreground hover:text-foreground\",\n      }}\n    />\n  )\n}\n\n/* Right: liste sagda, aktif sekmede sag kenar cizgisi. */\nexport function RightTabs(props: TabsProps) {\n  return (\n    <VerticalTabsShell\n      props={props}\n      config={{\n        fallback: DEFAULT_TABS,\n        side: \"right\",\n        bar: \"border-r border-border pr-1\",\n        tab: \"justify-end rounded-md\",\n        indicator: \"inset-y-1 -right-1 w-0.5 rounded-full bg-primary\",\n        active: \"text-foreground\",\n        inactive: \"text-muted-foreground hover:text-foreground\",\n      }}\n    />\n  )\n}\n\n/* Card: liste kart yuzeyinde, aktif sekme dolgulu kart satiri. */\nexport function CardTabs(props: TabsProps) {\n  return (\n    <VerticalTabsShell\n      props={props}\n      config={{\n        fallback: ICON_TABS,\n        side: \"left\",\n        bar: \"rounded-lg border border-border bg-card p-1.5\",\n        tab: \"rounded-md\",\n        indicator: \"inset-0 rounded-md border border-border bg-background shadow-sm\",\n        active: \"text-foreground\",\n        inactive: \"text-muted-foreground hover:text-foreground\",\n      }}\n    />\n  )\n}\n\n/* Rail: dar ray, aktif sekme primary tinti ve kalin sol cubuk. */\nexport function RailTabs(props: TabsProps) {\n  return (\n    <VerticalTabsShell\n      props={props}\n      config={{\n        fallback: ICON_TABS,\n        side: \"left\",\n        bar: \"rounded-lg bg-muted p-1\",\n        tab: \"rounded-md\",\n        indicator: \"inset-0 rounded-md border-l-2 border-primary bg-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]\",\n        active: \"text-primary\",\n        inactive: \"text-muted-foreground hover:text-foreground\",\n      }}\n    />\n  )\n}\n\n/* Compact: dar ve yogun liste, aktif sekme yumusak dolgu. */\nexport function CompactTabs(props: TabsProps) {\n  return (\n    <VerticalTabsShell\n      props={props}\n      config={{\n        fallback: DEFAULT_TABS,\n        side: \"left\",\n        root: \"gap-3\",\n        bar: \"w-36 gap-0.5\",\n        tab: \"rounded-sm px-2\",\n        indicator: \"inset-0 rounded-sm bg-[color-mix(in_oklab,var(--color-foreground)_7%,transparent)]\",\n        active: \"text-foreground\",\n        inactive: \"text-muted-foreground hover:text-foreground\",\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/tabs-vertical.tsx"
    }
  ],
  "categories": [
    "styled",
    "tabs"
  ],
  "type": "registry:component"
}