{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "type": "registry:ui",
  "title": "Radio Group",
  "description": "Radio group built on Base UI; the dot pops in with a spring.",
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "https://seamui.dev/r/utils.json",
    "https://seamui.dev/r/motion.json",
    "https://seamui.dev/r/haptics.json"
  ],
  "files": [
    {
      "path": "registry/seam/ui/radio-group.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { RadioGroup as BaseRadioGroup } from \"@base-ui/react/radio-group\"\nimport { Radio as BaseRadio } from \"@base-ui/react/radio\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  springs,\n  fades,\n  depth,\n  reduced,\n  useMounted,\n  useReducedMotion,\n} from \"@/lib/motion\"\nimport { useHaptics } from \"@/lib/haptics\"\n\nfunction RadioGroup({\n  className,\n  onValueChange,\n  ...props\n}: React.ComponentProps<typeof BaseRadioGroup>) {\n  const { trigger } = useHaptics()\n  return (\n    <BaseRadioGroup\n      data-slot=\"radio-group\"\n      className={cn(\"grid gap-2.5\", className)}\n      // tactile feedback: a tick as the selection commits (no-op sans provider).\n      onValueChange={(\n        ...args: Parameters<NonNullable<typeof onValueChange>>\n      ) => {\n        trigger(\"tick\")\n        onValueChange?.(...args)\n      }}\n      {...props}\n    />\n  )\n}\n\nfunction RadioGroupItem({\n  className,\n  disabled,\n  ...props\n}: React.ComponentProps<typeof BaseRadio.Root>) {\n  const reduceMotion = useReducedMotion()\n  const mounted = useMounted()\n\n  return (\n    <BaseRadio.Root\n      data-slot=\"radio-group-item\"\n      disabled={disabled}\n      className={cn(\n        \"group/radio flex aspect-square size-4.5 shrink-0 items-center justify-center rounded-full outline-none\",\n        \"focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        \"disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      {/* seam touch feedback: the ring recedes on press. RadioGroup is a Base\n          UI composite — replacing the item element breaks roving arrow-key\n          focus — so the visual ring is a child and the composite element stays\n          untouched. */}\n      <motion.span\n        whileTap={\n          disabled ? undefined : reduceMotion ? reduced.pressed : depth.pressed\n        }\n        transition={reduceMotion ? fades.fast : springs.press}\n        className={cn(\n          // debossed ring — the well is carved in; the checked dot rides in it.\n          \"flex size-full items-center justify-center rounded-full border border-border/60 bg-muted shadow-well\",\n          \"group-data-[checked]/radio:border-primary\"\n        )}\n      >\n        <BaseRadio.Indicator\n          data-slot=\"radio-group-indicator\"\n          className=\"flex items-center justify-center\"\n          render={\n            // seam motion: the dot pops in with a snappy spring.\n            <motion.span\n              // gate the pop behind mount so a defaultChecked dot doesn't\n              // hydration-mismatch on SSR; user-checked dots still pop.\n              initial={\n                mounted\n                  ? reduceMotion\n                    ? reduced.fadeIn.initial\n                    : { scale: 0 }\n                  : false\n              }\n              animate={{ scale: 1, opacity: 1 }}\n              transition={reduceMotion ? fades.fast : springs.snappy}\n            />\n          }\n        >\n          {/* embossed dot — a raised key sitting in the debossed ring. */}\n          <span className=\"bg-primary shadow-resting size-2 rounded-full\" />\n        </BaseRadio.Indicator>\n      </motion.span>\n    </BaseRadio.Root>\n  )\n}\n\nexport { RadioGroup, RadioGroupItem }\n",
      "type": "registry:ui",
      "target": "components/ui/radio-group.tsx"
    }
  ]
}