{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-group-glass",
  "title": "Group-glass toggle",
  "description": "Styled toggle: the Group-glass set. 5 decorative toggle variations (FrostToggleGroup, TintToggleGroup, SmokeToggleGroup, CrystalToggleGroup, DepthToggleGroup) 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",
    "@ai2/glass"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/toggle-group-glass.tsx",
      "content": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { Cloud, Moon, Sun } from \"lucide-react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\nimport { glassDepth } from \"@/registry/ai2/ui/glass\"\r\n\r\n/* Glass toggle group family: 5 decorative frosted-glass single-select controls. The\n   glass is NO LONGER hand-written: the shell derives from the glassDepth scale\n   inside @ai2/glass, so the ai2 signature (top inset highlight + ambient shadow) is\n   the same language in every variant.\n\n   Choosing the depth (a bar-shaped control): a segmented group is wide but SHORT\n   (h-8..h-11) and sits in the page flow; behind a short bar a heavy blur brings no\n   gain, so the scale starts low and only the genuinely floating variant reaches the\n   top.\n\n   NO GLASS ON GLASS: the glass is ONLY on the shell. The active indicator used to be\n   described as \"a bubble of glass\"; it is now an OPAQUE highlight on top of the bar -\n   a second translucent layer would have turned to mud, and it was blurring the glass\n   itself rather than the content behind it anyway. Since the indicator does not\n   carry glassDepth, using shadow-* on it is safe.\n\n   The variants change the tone and the depth of the glass (neutral ice, info tint,\n   smoky dark, sharp crystal, layered depth). Color comes ONLY from semantic tokens;\n   alpha via color-mix or the tailwind opacity modifier. The framer layoutId\n   indicator moves instantly while useReducedMotion is on; the layoutId is unique per\n   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\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 select-none items-center justify-center 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 THEME_OPTIONS: Option[] = [\r\n  { value: \"light\", label: \"Light\", icon: <Sun /> },\r\n  { value: \"dim\", label: \"Dim\", icon: <Cloud /> },\r\n  { value: \"dark\", label: \"Dark\", icon: <Moon /> },\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\ntype Spec = {\r\n  /** The glass shell class. */\r\n  root: string\r\n  /** Aktif dugme metin rengi. */\r\n  onText: string\r\n  /** Kapali dugme metin rengi. */\r\n  offText: string\r\n  /** The glass 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 tone and the depth of the glass change. */\r\nfunction GlassGroup({ props, spec }: { props: ToggleGroupProps; spec: Spec }) {\r\n  const { className, size = \"md\" } = props\r\n  const { list, isOn, toggle } = useToggleGroup({ ...props, type: \"single\" }, THEME_OPTIONS)\r\n  const id = React.useId()\r\n  const reduce = useReducedMotion()\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-viewport\"\r\n      className=\"-m-1 flex max-w-full overflow-x-auto p-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\r\n    >\r\n      <div\r\n        data-slot=\"styled-toggle-group\"\r\n        role=\"group\"\r\n        aria-label={spec.label}\r\n        className={cn(\"inline-flex items-center gap-0.5 rounded-xl border p-1\", 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], \"rounded-lg\", on ? spec.onText : spec.offText)}\r\n            >\r\n              {on && (\r\n                <motion.span\r\n                  layoutId={`${id}-glass-indicator`}\r\n                  transition={transition}\r\n                  className={cn(\"absolute inset-0 -z-10 rounded-lg\", 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    </div>\r\n  )\r\n}\r\n\r\n/* Frost: neutral ice. DEPTH sm (4px): the plainest shell; behind a short segmented\n   bar anything more is an invisible cost.\n   The indicator: an opaque background chip (the old supports-[...]:bg-background/70\n   translucency was removed - a highlight on glass must be opaque). */\r\nexport function FrostToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <GlassGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Theme, frosted glass\",\r\n        root: cn(glassDepth.sm, \"border-border/60\"),\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator: \"border border-border/70 bg-background shadow-sm\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Tint: info-toned glass. DEPTH md (8px, the canonical default): the character comes\n   from the TONE, not the blur. Tint overrides both the bg-* and the supports- step.\n   The indicator: an OPAQUE mix of info + background; text-primary (the semantic role\n   of the active state) reads clearly on it. */\r\nexport function TintToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <GlassGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Theme, info tinted glass\",\r\n        root: cn(glassDepth.md, \"border-info/25 bg-info/10 supports-[backdrop-filter]:bg-info/10\"),\r\n        onText: \"text-primary\",\r\n        offText: \"text-muted-foreground hover:text-primary\",\r\n        indicator:\r\n          \"border border-info/30 bg-[color-mix(in_oklab,var(--color-info)_18%,var(--color-background))] shadow-sm\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Smoke: smoky dark glass. DEPTH md (8px): the same step as Tint DELIBERATELY -\n   glass at the same distance, differing only in tone (a foreground smoke vs info).\n   The indicator: a bright opaque chip cutting through the smoke. */\r\nexport function SmokeToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <GlassGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Theme, smoked glass\",\r\n        root: cn(\r\n          glassDepth.md,\r\n          \"border-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\",\r\n          \"bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\",\r\n          \"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\"\r\n        ),\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator:\r\n          \"bg-background shadow-sm ring-1 ring-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Crystal: a clear pane. DEPTH lg (16px): here the diffusion is the SUBJECT, not\n   decoration.\n   Note: the `shadow-sm` on the root was removed - it would have overridden the same\n   CSS property as the glassDepth signature; the bright top edge is drawn with\n   border-t. */\r\nexport function CrystalToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <GlassGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Theme, crystal glass\",\r\n        root: cn(\r\n          glassDepth.lg,\r\n          \"border-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\",\r\n          \"border-t-[color-mix(in_oklab,var(--color-background)_85%,transparent)]\",\r\n          \"supports-[backdrop-filter]:backdrop-saturate-150\"\r\n        ),\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator:\r\n          \"bg-background shadow-sm ring-1 ring-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)] after:absolute after:inset-x-2 after:top-0 after:h-px after:rounded-full after:bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Depth: layered depth. DEPTH xl (24px): the ONLY variant that earns the top of the\n   scale - it stands like a lifted overlay bar, and the further it moves from the\n   surface the more what is behind it diffuses.\n   Note: the `shadow-sm` on the root was removed (it would have overridden the\n   signature), and the separation comes from a ring instead. A gradient removes the\n   bg-* step but does NOT remove the supports-* step, so it is explicitly pulled back\n   to bg-transparent. Since the indicator does not carry glassDepth, shadow-md is\n   safe there - it is what carries the sense of depth. */\r\nexport function DepthToggleGroup(props: ToggleGroupProps) {\r\n  return (\r\n    <GlassGroup\r\n      props={props}\r\n      spec={{\r\n        label: \"Theme, layered depth glass\",\r\n        root: cn(\r\n          glassDepth.xl,\r\n          \"border-border/50\",\r\n          \"bg-gradient-to-b supports-[backdrop-filter]:bg-transparent\",\r\n          \"from-[color-mix(in_oklab,var(--color-background)_55%,transparent)]\",\r\n          \"to-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\",\r\n          \"ring-1 ring-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\"\r\n        ),\r\n        onText: \"text-foreground\",\r\n        offText: \"text-muted-foreground hover:text-foreground\",\r\n        indicator: \"border border-border/60 bg-background shadow-md\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n",
      "type": "registry:component",
      "target": "components/ui/toggle-group-glass.tsx"
    }
  ],
  "categories": [
    "styled",
    "toggle"
  ],
  "type": "registry:component"
}