{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group-tone",
  "title": "Group-tone radio",
  "description": "Styled radio: the Group-tone set. 5 decorative radio variations (InfoRadio, SuccessRadio, WarningRadio, DangerRadio, MutedRadio) 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"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/radio-group-tone.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Tone radio-group family: 5 semantically toned selection groups (info, success,\n   warning, danger, muted). Each option is a row card; the selected one is marked\n   with the tone's border, soft fill and toned dot. role=\"radiogroup\" +\n   role=\"radio\", roving tabindex, arrow-key navigation and selection. The root\n   carries data-tone. Color comes ONLY from tokens, via alpha color-mix. Under\n   reduced-motion the dot appears instantly. A dot box under 24px carries an\n   invisible touch area. Renders with no props too. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype RadioOption = { value: string; label: React.ReactNode }\n\ninterface RadioGroupProps {\n  className?: string\n  size?: StyledSize\n  options?: RadioOption[]\n  value?: string\n  defaultValue?: string\n  onValueChange?: (v: string) => void\n}\n\nconst DEFAULT_OPTIONS: RadioOption[] = [\n  { value: \"a\", label: \"Option A\" },\n  { value: \"b\", label: \"Option B\" },\n  { value: \"c\", label: \"Option C\" },\n]\n\nconst textSize: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-base\",\n  xl: \"text-lg\",\n}\n\nconst padSize: Record<StyledSize, string> = {\n  sm: \"px-2.5 py-1.5\",\n  md: \"px-3 py-2\",\n  lg: \"px-4 py-2.5\",\n  xl: \"px-5 py-3\",\n}\n\nconst ringSize: Record<StyledSize, string> = {\n  sm: \"size-4\",\n  md: \"size-4\",\n  lg: \"size-5\",\n  xl: \"size-5\",\n}\n\nconst dotSize: Record<StyledSize, string> = {\n  sm: \"size-1.5\",\n  md: \"size-2\",\n  lg: \"size-2.5\",\n  xl: \"size-2.5\",\n}\n\nfunction useRadioGroup(props: Pick<RadioGroupProps, \"value\" | \"defaultValue\" | \"onValueChange\" | \"options\">) {\n  const { value, defaultValue, onValueChange, options } = props\n  const opts = options && options.length > 0 ? options : DEFAULT_OPTIONS\n  const reduce = useReducedMotion()\n  const [internal, setInternal] = React.useState(defaultValue ?? opts[0]?.value)\n  const selected = value ?? internal\n  const select = React.useCallback(\n    (v: string) => {\n      if (value === undefined) setInternal(v)\n      onValueChange?.(v)\n    },\n    [value, onValueChange]\n  )\n  const spring = reduce\n    ? { duration: 0 }\n    : ({ type: \"spring\" as const, stiffness: 500, damping: 34 })\n  return { opts, selected, select, reduce, spring }\n}\n\n/* Arrow-key navigation + selection (together with the roving tabindex). */\nfunction useRadioKeys(opts: RadioOption[], select: (v: string) => void) {\n  const refs = React.useRef<(HTMLButtonElement | null)[]>([])\n  const setRef = (i: number) => (el: HTMLButtonElement | null) => {\n    refs.current[i] = el\n  }\n  const onKeyDown = (i: number) => (e: React.KeyboardEvent) => {\n    const last = opts.length - 1\n    let next = -1\n    if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = i === last ? 0 : i + 1\n    else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = i === 0 ? last : i - 1\n    if (next < 0) return\n    e.preventDefault()\n    const target = opts[next]\n    if (!target) return\n    select(target.value)\n    refs.current[next]?.focus()\n  }\n  return { setRef, onKeyDown }\n}\n\nconst focusRing =\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n\ntype ToneSkin = {\n  /* Secili kartin kenarlik + dolgu + metin gorunumu. */\n  card: string\n  /* Secili nokta halkasi. */\n  ring: string\n  /* Secili nokta dolgusu. */\n  dot: string\n  /* Secili olmayan kart hover kenarligi. */\n  hover: string\n}\n\n/* Shared shell: a toned row-card list. */\nfunction ToneShell({ props, tone, skin }: { props: RadioGroupProps; tone: string; skin: ToneSkin }) {\n  const { className, size = \"md\", options, value, defaultValue, onValueChange } = props\n  const { opts, selected, select, spring } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  const activeIndex = opts.findIndex((o) => o.value === selected)\n\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      data-tone={tone}\n      role=\"radiogroup\"\n      aria-label=\"Choose an option\"\n      className={cn(\"flex w-full flex-col gap-2\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        const roving = active || (activeIndex < 0 && i === 0)\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={roving ? 0 : -1}\n            onKeyDown={onKeyDown(i)}\n            onClick={() => select(opt.value)}\n            data-slot=\"styled-radio-item\"\n            className={cn(\n              \"group flex cursor-pointer items-center gap-2.5 rounded-lg border text-left font-medium transition-colors\",\n              focusRing,\n              textSize[size],\n              padSize[size],\n              active ? skin.card : cn(\"border-field-border text-muted-foreground hover:text-foreground\", skin.hover)\n            )}\n          >\n            <span\n              className={cn(\n                \"relative flex shrink-0 items-center justify-center rounded-full border transition-colors after:absolute after:-inset-1.5\",\n                ringSize[size],\n                active ? skin.ring : \"border-field-border\"\n              )}\n            >\n              <motion.span\n                initial={false}\n                animate={{ scale: active ? 1 : 0 }}\n                transition={spring}\n                className={cn(\"rounded-full\", dotSize[size], skin.dot)}\n              />\n            </span>\n            <span>{opt.label}</span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Info: bilgi tonu. */\nexport function InfoRadio(props: RadioGroupProps) {\n  return (\n    <ToneShell\n      props={props}\n      tone=\"info\"\n      skin={{\n        card: \"border-info bg-info-soft text-info-soft-foreground\",\n        ring: \"border-info\",\n        dot: \"bg-info\",\n        hover: \"hover:border-[color-mix(in_oklab,var(--color-info)_45%,transparent)]\",\n      }}\n    />\n  )\n}\n\n/* Success: onay tonu. */\nexport function SuccessRadio(props: RadioGroupProps) {\n  return (\n    <ToneShell\n      props={props}\n      tone=\"success\"\n      skin={{\n        card: \"border-success bg-success-soft text-success-soft-foreground\",\n        ring: \"border-success\",\n        dot: \"bg-success\",\n        hover: \"hover:border-[color-mix(in_oklab,var(--color-success)_45%,transparent)]\",\n      }}\n    />\n  )\n}\n\n/* Warning: uyari tonu. */\nexport function WarningRadio(props: RadioGroupProps) {\n  return (\n    <ToneShell\n      props={props}\n      tone=\"warning\"\n      skin={{\n        card: \"border-warning bg-warning-soft text-warning-soft-foreground\",\n        ring: \"border-warning\",\n        dot: \"bg-warning\",\n        hover: \"hover:border-[color-mix(in_oklab,var(--color-warning)_45%,transparent)]\",\n      }}\n    />\n  )\n}\n\n/* Danger: the destructive-choice tone (ai2 danger, not destructive). */\nexport function DangerRadio(props: RadioGroupProps) {\n  return (\n    <ToneShell\n      props={props}\n      tone=\"danger\"\n      skin={{\n        card: \"border-danger bg-danger-soft text-danger-soft-foreground\",\n        ring: \"border-danger\",\n        dot: \"bg-danger\",\n        hover: \"hover:border-[color-mix(in_oklab,var(--color-danger)_45%,transparent)]\",\n      }}\n    />\n  )\n}\n\n/* Muted: notr, sessiz ton. */\nexport function MutedRadio(props: RadioGroupProps) {\n  return (\n    <ToneShell\n      props={props}\n      tone=\"neutral\"\n      skin={{\n        card: \"border-border bg-muted text-foreground\",\n        ring: \"border-muted-foreground\",\n        dot: \"bg-muted-foreground\",\n        hover: \"hover:border-[color-mix(in_oklab,var(--color-foreground)_25%,transparent)]\",\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/radio-group-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "radio"
  ],
  "type": "registry:component"
}