{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-group-vertical",
  "title": "Group-vertical toggle",
  "description": "Styled toggle: the Group-vertical set. 5 decorative toggle variations (StackToggleGroup, RailToggleGroup, CardToggleGroup, CompactToggleGroup, ListToggleGroup) 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/toggle-group-vertical.tsx",
      "content": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { Bell, Inbox, Star } from \"lucide-react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\n\r\n/* Vertical toggle group family: 5 decorative VERTICAL single-select controls. The\n   shared idea is turning the horizontal segmented bar onto the vertical axis: the\n   options are stacked, the active indicator slides up and down, and the Up/Down\n   arrow keys move between options on the keyboard (Home/End go to the ends). The\n   variants change the vertical surface (a stack, an edge rail, a card, a tight\n   list, a plain list). Color comes ONLY from semantic tokens; alpha via color-mix.\n   The framer layoutId indicator moves instantly while useReducedMotion is on; the\n   layoutId is unique per instance via React.useId(). */\r\n\r\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\r\n\r\ntype Option = { value: string; label?: React.ReactNode; icon?: React.ReactNode }\r\n\r\ntype ToggleGroupProps = {\r\n  className?: string\r\n  size?: StyledSize\r\n  type?: \"single\" | \"multiple\"\r\n  options?: Option[]\r\n  value?: string | string[]\r\n  defaultValue?: string | string[]\r\n  onValueChange?: (v: string | string[]) => void\r\n}\r\n\r\n/* In the vertical case the height stays fixed and the width comes from the shell;\n   the alignment is flush left. */\r\nconst sizeBtn: Record<StyledSize, string> = {\r\n  sm: \"h-8 px-3 text-xs\",\r\n  md: \"h-9 px-4 text-sm\",\r\n  lg: \"h-10 px-5 text-sm\",\r\n  xl: \"h-11 px-6 text-base\",\r\n}\r\n\r\nconst btnBase =\r\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 disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\r\n\r\nconst VIEW_OPTIONS: Option[] = [\r\n  { value: \"inbox\", label: \"Inbox\", icon: <Inbox /> },\r\n  { value: \"starred\", label: \"Starred\", icon: <Star /> },\r\n  { value: \"alerts\", label: \"Alerts\", icon: <Bell /> },\r\n]\r\n\r\nfunction toArray(v: string | string[] | undefined): string[] {\r\n  if (v === undefined) return []\r\n  return Array.isArray(v) ? v : [v]\r\n}\r\n\r\n/* Kontrollu/kontrolsuz secim durumu. Tek-secimde varsayilan ilk secenektir. */\r\nfunction useToggleGroup(props: ToggleGroupProps, fallback: Option[]) {\r\n  const { type = \"single\", options, value, defaultValue, onValueChange } = props\r\n  const list = React.useMemo(() => (options && options.length ? options : fallback), [options, fallback])\r\n  const controlled = value !== undefined\r\n  const [internal, setInternal] = React.useState<string[]>(() => {\r\n    const init = toArray(defaultValue)\r\n    if (init.length === 0 && type === \"single\" && defaultValue === undefined) {\r\n      return list[0] ? [list[0].value] : []\r\n    }\r\n    return init\r\n  })\r\n  const selected = controlled ? toArray(value) : internal\r\n\r\n  const emit = React.useCallback(\r\n    (next: string[]) => {\r\n      if (!controlled) setInternal(next)\r\n      onValueChange?.(type === \"single\" ? (next[0] ?? \"\") : next)\r\n    },\r\n    [controlled, onValueChange, type]\r\n  )\r\n\r\n  const toggle = React.useCallback(\r\n    (v: string) => {\r\n      const on = selected.includes(v)\r\n      if (type === \"single\") {\r\n        emit(on ? [] : [v])\r\n      } else {\r\n        emit(on ? selected.filter((s) => s !== v) : [...selected, v])\r\n      }\r\n    },\r\n    [selected, type, emit]\r\n  )\r\n\r\n  const isOn = React.useCallback((v: string) => selected.includes(v), [selected])\r\n  return { list, isOn, toggle }\r\n}\r\n\r\n/* Vertical keyboard navigation: Up/Down moves to the neighboring button and wraps;\n   Home/End go to the ends. Focus moves in DOM order. */\r\nfunction useVerticalKeys() {\r\n  return React.useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {\r\n    const keys = [\"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"]\r\n    if (!keys.includes(e.key)) return\r\n    const root = e.currentTarget\r\n    const items = Array.from(\r\n      root.querySelectorAll<HTMLButtonElement>('button[data-slot=\"styled-toggle-group-item\"]')\r\n    )\r\n    if (items.length === 0) return\r\n    const current = items.indexOf(document.activeElement as HTMLButtonElement)\r\n    if (current === -1) return\r\n    e.preventDefault()\r\n    let next = current\r\n    if (e.key === \"ArrowDown\") next = (current + 1) % items.length\r\n    if (e.key === \"ArrowUp\") next = (current - 1 + items.length) % items.length\r\n    if (e.key === \"Home\") next = 0\r\n    if (e.key === \"End\") next = items.length - 1\r\n    items[next]?.focus()\r\n  }, [])\r\n}\r\n\r\ntype Spec = {\r\n  /** Root shell class. */\r\n  root: string\r\n  /** Button class. */\r\n  item: string\r\n  /** Aktif dugme metin rengi. */\r\n  onText: string\r\n  /** Kapali dugme metin rengi. */\r\n  offText: string\r\n  /** Indicator class. */\r\n  indicator: string\r\n  /** Erisilebilir ad. */\r\n  label: string\r\n}\r\n\r\n/* The shared shell: only the vertical surface changes. */\r\nfunction VerticalGroup({ props, spec }: { props: ToggleGroupProps; spec: Spec }) {\r\n  const { className, size = \"md\" } = props\r\n  const { list, isOn, toggle } = useToggleGroup({ ...props, type: \"single\" }, VIEW_OPTIONS)\r\n  const id = React.useId()\r\n  const reduce = useReducedMotion()\r\n  const onKeyDown = useVerticalKeys()\r\n  const transition = reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 420, damping: 34 }\r\n\r\n  return (\r\n    <div\r\n      data-slot=\"styled-toggle-group\"\r\n      role=\"group\"\r\n      aria-label={spec.label}\r\n      aria-orientation=\"vertical\"\r\n      onKeyDown={onKeyDown}\r\n      className={cn(\"inline-flex flex-col items-stretch\", spec.root, className)}\r\n    >\r\n      {list.map((o) => {\r\n        const on = isOn(o.value)\r\n        return (\r\n          <button\r\n            key={o.value}\r\n            type=\"button\"\r\n            aria-pressed={on}\r\n            data-slot=\"styled-toggle-group-item\"\r\n            onClick={() => toggle(o.value)}\r\n            className={cn(btnBase, sizeBtn[size], spec.item, on ? spec.onText : spec.offText)}\r\n          >\r\n            {on && (\r\n              <motion.span\r\n                layoutId={`${id}-vertical-indicator`}\r\n                transition={transition}\r\n                className={cn(\"absolute inset-0 -z-10\", spec.indicator)}\r\n              />\r\n            )}\r\n            {o.icon}\r\n            {o.label}\r\n          </button>\r\n        )\r\n      })}\r\n    </div>\r\n  )\r\n}\r\n\r\n/* Stack: dikey segment yigini; kutulu gosterge secenekler arasinda kayar. */\r\nexport function StackToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <VerticalGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Mailbox view, vertical stack\",\r\n        root: \"gap-0.5 rounded-lg border border-border bg-muted p-1\",\r\n        item: \"rounded-md\",\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator: \"rounded-md border border-border bg-background shadow-sm\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Rail: sol kenarda dikey ray; aktif secenegi kalin bir cubuk isaretler. */\r\nexport function RailToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <VerticalGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Mailbox view, side rail\",\r\n        root: \"gap-0.5 border-l border-border pl-1\",\r\n        item: \"rounded-r-md rounded-l-none\",\r\n        onText: \"text-primary\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator:\r\n          \"rounded-r-md bg-[color-mix(in_oklab,var(--color-primary)_10%,transparent)] before:absolute before:inset-y-0 before:-left-1 before:w-0.5 before:rounded-full before:bg-primary\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Card: every option is a separate card; the active card gets a primary ring and\n   elevation. */\r\nexport function CardToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <VerticalGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Mailbox view, card list\",\r\n        root: \"gap-2\",\r\n        item: \"rounded-lg\",\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator:\r\n          \"rounded-lg bg-card shadow-sm ring-2 ring-[color-mix(in_oklab,var(--color-primary)_45%,transparent)]\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Compact: sifir bosluklu sik liste; bolucu cizgiler dugmeleri ayirir. */\r\nexport function CompactToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <VerticalGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Mailbox view, compact list\",\r\n        root: \"overflow-hidden rounded-lg border border-border bg-background\",\r\n        item: \"rounded-none [&:not(:first-child)]:border-t [&:not(:first-child)]:border-border\",\r\n        onText: \"text-primary-foreground\",\r\n        offText: \"text-muted-foreground hover:bg-muted hover:text-foreground\",\r\n        indicator: \"bg-primary\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* List: a plain borderless list; the active row takes a soft token background. */\r\nexport function ListToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <VerticalGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Mailbox view, plain list\",\r\n        root: \"gap-1\",\r\n        item: \"rounded-md\",\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:bg-muted hover:text-foreground\",\r\n        indicator: \"rounded-md bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n",
      "type": "registry:component",
      "target": "components/ui/toggle-group-vertical.tsx"
    }
  ],
  "categories": [
    "styled",
    "toggle"
  ],
  "type": "registry:component"
}