{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-group-tone",
  "title": "Group-tone toggle",
  "description": "Styled toggle: the Group-tone set. 5 decorative toggle variations (InfoToggleGroup, SuccessToggleGroup, WarningToggleGroup, DangerToggleGroup, MutedToggleGroup) 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-tone.tsx",
      "content": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { CircleAlert, CircleCheck, CircleMinus, Info, TriangleAlert } from \"lucide-react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\n\r\n/* Tone toggle group family: 5 decorative single-select controls. The shared idea is\n   the SEMANTIC TONE: each variant is painted with a single meaning color (info,\n   success, warning, danger, muted); the active option carries that tone's fill and\n   the inactive ones carry neutral text. The structure is the same in every variant,\n   so the only difference is color. The root carries `data-tone`.\n   Color comes ONLY from semantic tokens; alpha via color-mix. The active indicator\n   slides with a framer layoutId and moves instantly while useReducedMotion is on;\n   the layoutId is unique per instance via React.useId(). */\r\n\r\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\r\n\r\nexport type StyledTone = \"info\" | \"success\" | \"warning\" | \"danger\" | \"muted\"\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 LEVEL_OPTIONS: Option[] = [\r\n  { value: \"low\", label: \"Low\" },\r\n  { value: \"normal\", label: \"Normal\" },\r\n  { value: \"high\", label: \"High\" },\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/* Aktif gosterge: dolu ton zemini. */\r\nconst toneIndicator: Record<StyledTone, string> = {\r\n  info: \"bg-info\",\r\n  success: \"bg-success\",\r\n  warning: \"bg-warning\",\r\n  danger: \"bg-danger\",\r\n  muted: \"bg-foreground\",\r\n}\r\n\r\n/* Active text: the -foreground matching the tone, so the contrast passes in every\n   theme. */\r\nconst toneOnText: Record<StyledTone, string> = {\r\n  info: \"text-info-foreground\",\r\n  success: \"text-success-foreground\",\r\n  warning: \"text-warning-foreground\",\r\n  danger: \"text-danger-foreground\",\r\n  muted: \"text-background\",\r\n}\r\n\r\n/* Inactive text: neutral; it moves toward the tone on hover. */\r\nconst toneOffText: Record<StyledTone, string> = {\r\n  info: \"text-muted-foreground hover:text-info\",\r\n  success: \"text-muted-foreground hover:text-success\",\r\n  warning: \"text-muted-foreground hover:text-warning\",\r\n  danger: \"text-muted-foreground hover:text-danger\",\r\n  muted: \"text-muted-foreground hover:text-foreground\",\r\n}\r\n\r\n/* Kabuk: ton renkli ince kenarlik + cok hafif ton zemini. */\r\nconst toneRoot: Record<StyledTone, string> = {\r\n  info: \"border-[color-mix(in_oklab,var(--color-info)_35%,transparent)] bg-[color-mix(in_oklab,var(--color-info)_8%,transparent)]\",\r\n  success:\r\n    \"border-[color-mix(in_oklab,var(--color-success)_35%,transparent)] bg-[color-mix(in_oklab,var(--color-success)_8%,transparent)]\",\r\n  warning:\r\n    \"border-[color-mix(in_oklab,var(--color-warning)_35%,transparent)] bg-[color-mix(in_oklab,var(--color-warning)_8%,transparent)]\",\r\n  danger:\r\n    \"border-[color-mix(in_oklab,var(--color-danger)_35%,transparent)] bg-[color-mix(in_oklab,var(--color-danger)_8%,transparent)]\",\r\n  muted: \"border-border bg-muted\",\r\n}\r\n\r\n/* Tone -> the default lucide icon; it sits in front of the first option. */\r\nconst toneIcon: Record<StyledTone, React.ReactNode> = {\r\n  info: <Info />,\r\n  success: <CircleCheck />,\r\n  warning: <TriangleAlert />,\r\n  danger: <CircleAlert />,\r\n  muted: <CircleMinus />,\r\n}\r\n\r\n/* The shared shell: only the tone changes. */\r\nfunction ToneGroup({ props, tone, label }: { props: ToggleGroupProps; tone: StyledTone; label: string }) {\r\n  const { className, size = \"md\" } = props\r\n  const { list, isOn, toggle } = useToggleGroup({ ...props, type: \"single\" }, LEVEL_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        data-tone={tone}\r\n        role=\"group\"\r\n        aria-label={label}\r\n        className={cn(\"inline-flex items-center gap-0.5 rounded-lg border p-1\", toneRoot[tone], className)}\r\n      >\r\n        {list.map((o, i) => {\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-md\", on ? toneOnText[tone] : toneOffText[tone])}\r\n            >\r\n              {on && (\r\n                <motion.span\r\n                  layoutId={`${id}-tone-indicator`}\r\n                  transition={transition}\r\n                  className={cn(\"absolute inset-0 -z-10 rounded-md shadow-sm\", toneIndicator[tone])}\r\n                />\r\n              )}\r\n              {o.icon ?? (i === 0 ? toneIcon[tone] : null)}\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/* Info: an informational tone; for a neutral series of choices. */\r\nexport function InfoToggleGroup(props: ToggleGroupProps) {\r\n  return <ToneGroup props={props} tone=\"info\" label=\"Priority level, info tone\" />\r\n}\r\n\r\n/* Success: olumlu ton; onaylanan bir secimi isaretler. */\r\nexport function SuccessToggleGroup(props: ToggleGroupProps) {\r\n  return <ToneGroup props={props} tone=\"success\" label=\"Priority level, success tone\" />\r\n}\r\n\r\n/* Warning: a caution tone; for a risky series of choices. */\r\nexport function WarningToggleGroup(props: ToggleGroupProps) {\r\n  return <ToneGroup props={props} tone=\"warning\" label=\"Priority level, warning tone\" />\r\n}\r\n\r\n/* Danger: yikici ton; geri alinamaz secimleri isaretler. */\r\nexport function DangerToggleGroup(props: ToggleGroupProps) {\r\n  return <ToneGroup props={props} tone=\"danger\" label=\"Priority level, danger tone\" />\r\n}\r\n\r\n/* Muted: a colorless tone; for choices that should not stand out in the interface. */\r\nexport function MutedToggleGroup(props: ToggleGroupProps) {\r\n  return <ToneGroup props={props} tone=\"muted\" label=\"Priority level, muted tone\" />\r\n}\r\n",
      "type": "registry:component",
      "target": "components/ui/toggle-group-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "toggle"
  ],
  "type": "registry:component"
}