{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group-styled",
  "title": "Group-styled radio",
  "description": "Styled radio: the Group-styled set. 5 decorative radio variations (SegmentedRadio, CardRadio, PillRadio, DotRadio, IconRadio) 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/radio-group-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Circle, Heart, Sparkles, Star, Zap } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Radio-group family: 5 decorative selection groups. role=\"radiogroup\" plus role=\"radio\". Colour comes ONLY from tokens. Size = the track scale. Used uncontrolled (defaultValue) or controlled (value/onValueChange). Keyboard navigation (arrow keys) plus roving tabindex. framer-motion switches instantly under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype RadioOption = { value: string; label: React.ReactNode }\n\ntype 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\",\n  md: \"px-3 py-1.5\",\n  lg: \"px-4 py-2\",\n  xl: \"px-5 py-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\", stiffness: 500, damping: 34 } as const)\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\n/* Segmented: yatay segment kontrolu; aktif secenegin altinda token pill kayar. */\nexport function SegmentedRadio({ className, size = \"md\", options, value, defaultValue, onValueChange }: RadioGroupProps) {\n  const { opts, selected, select, spring } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  const groupId = React.useId()\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      role=\"radiogroup\"\n      className={cn(\"inline-flex items-center gap-1 rounded-lg bg-muted p-1\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 0 : -1}\n            onKeyDown={onKeyDown(i)}\n            onClick={() => select(opt.value)}\n            data-slot=\"styled-radio-item\"\n            className={cn(\n              \"relative z-10 cursor-pointer rounded-md font-medium transition-colors\",\n              focusRing,\n              textSize[size],\n              padSize[size],\n              active ? \"text-primary-foreground\" : \"text-muted-foreground hover:text-foreground\"\n            )}\n          >\n            {active && (\n              <motion.span\n                layoutId={`segmented-pill-${groupId}`}\n                transition={spring}\n                className=\"absolute inset-0 -z-10 rounded-md bg-primary shadow-sm\"\n              />\n            )}\n            <span className=\"relative\">{opt.label}</span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Card: every option is a selectable card; when selected it gets a token border + a\n   marker. */\nexport function CardRadio({ className, size = \"md\", options, value, defaultValue, onValueChange }: RadioGroupProps) {\n  const { opts, selected, select, spring } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      role=\"radiogroup\"\n      className={cn(\"flex flex-col gap-2\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 0 : -1}\n            onKeyDown={onKeyDown(i)}\n            onClick={() => select(opt.value)}\n            data-slot=\"styled-radio-item\"\n            className={cn(\n              \"relative flex cursor-pointer items-center justify-between rounded-lg border text-left transition-colors\",\n              focusRing,\n              textSize[size],\n              padSize[size],\n              active\n                ? \"border-primary bg-primary/10 text-foreground\"\n                : \"border-field-border text-muted-foreground hover:border-primary/40 hover:text-foreground\"\n            )}\n          >\n            <span className=\"font-medium\">{opt.label}</span>\n            <span\n              className={cn(\n                \"flex size-4 shrink-0 items-center justify-center rounded-full border transition-colors\",\n                active ? \"border-primary bg-primary text-primary-foreground\" : \"border-field-border text-transparent\"\n              )}\n            >\n              <motion.span\n                initial={false}\n                animate={{ scale: active ? 1 : 0 }}\n                transition={spring}\n                className=\"size-1.5 rounded-full bg-current\"\n              />\n            </span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Pill: every option is a pill; the selected one fills with the token. */\nexport function PillRadio({ className, size = \"md\", options, value, defaultValue, onValueChange }: RadioGroupProps) {\n  const { opts, selected, select } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      role=\"radiogroup\"\n      className={cn(\"flex flex-wrap items-center gap-2\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 0 : -1}\n            onKeyDown={onKeyDown(i)}\n            onClick={() => select(opt.value)}\n            data-slot=\"styled-radio-item\"\n            className={cn(\n              \"cursor-pointer rounded-full border font-medium transition-colors\",\n              focusRing,\n              textSize[size],\n              padSize[size],\n              active\n                ? \"border-primary bg-primary text-primary-foreground\"\n                : \"border-field-border text-muted-foreground hover:border-primary/40 hover:text-foreground\"\n            )}\n          >\n            {opt.label}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Dot: klasik radio noktalari; secili ic nokta token dolgu + yay ile buyur. */\nexport function DotRadio({ className, size = \"md\", options, value, defaultValue, onValueChange }: RadioGroupProps) {\n  const { opts, selected, select, spring } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  const ringSize: Record<StyledSize, string> = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n    xl: \"size-7\",\n  }\n  const dotSize: Record<StyledSize, string> = {\n    sm: \"size-1.5\",\n    md: \"size-2\",\n    lg: \"size-2.5\",\n    xl: \"size-3\",\n  }\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      role=\"radiogroup\"\n      className={cn(\"flex flex-col gap-2.5\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 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-md text-left transition-colors\",\n              focusRing,\n              textSize[size],\n              active ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\"\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 ? \"border-primary\" : \"border-field-border group-hover:border-primary/40\"\n              )}\n            >\n              <motion.span\n                initial={false}\n                animate={{ scale: active ? 1 : 0 }}\n                transition={spring}\n                className={cn(\"rounded-full bg-primary\", dotSize[size])}\n              />\n            </span>\n            <span className=\"font-medium\">{opt.label}</span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\nconst RADIO_ICONS = [Star, Heart, Zap, Sparkles, Circle]\n\n/* Icon: ikon-onculu secenekler; secili olan token ring + tint alir. */\nexport function IconRadio({ className, size = \"md\", options, value, defaultValue, onValueChange }: RadioGroupProps) {\n  const { opts, selected, select } = useRadioGroup({ options, value, defaultValue, onValueChange })\n  const { setRef, onKeyDown } = useRadioKeys(opts, select)\n  const iconBox: Record<StyledSize, string> = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n    xl: \"size-7\",\n  }\n  return (\n    <div\n      data-slot=\"styled-radio-group\"\n      role=\"radiogroup\"\n      className={cn(\"flex flex-wrap items-stretch gap-2\", className)}\n    >\n      {opts.map((opt, i) => {\n        const active = opt.value === selected\n        const Icon = RADIO_ICONS[i % RADIO_ICONS.length]\n        return (\n          <button\n            key={opt.value}\n            ref={setRef(i)}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 0 : -1}\n            onKeyDown={onKeyDown(i)}\n            onClick={() => select(opt.value)}\n            data-slot=\"styled-radio-item\"\n            className={cn(\n              \"flex cursor-pointer flex-col items-center gap-1.5 rounded-lg border font-medium transition-colors\",\n              focusRing,\n              textSize[size],\n              padSize[size],\n              active\n                ? \"border-primary bg-primary/10 text-primary ring-1 ring-primary\"\n                : \"border-field-border text-muted-foreground hover:border-primary/40 hover:text-foreground\"\n            )}\n          >\n            {Icon ? (\n              <Icon\n                className={cn(iconBox[size], active ? \"text-primary\" : \"text-muted-foreground\")}\n                aria-hidden=\"true\"\n              />\n            ) : null}\n            <span>{opt.label}</span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/radio-group-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "radio"
  ],
  "type": "registry:component"
}