{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "title": "Radio Group",
  "description": "One choice out of a few, all visible at once, with keyboard arrows and a springy dot.",
  "dependencies": [
    "@base-ui/react@^1.9.0"
  ],
  "registryDependencies": [
    "https://sproutboat.com/ikada/r/theme.json",
    "https://sproutboat.com/ikada/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/ikada/ui/radio-group.tsx",
      "content": "\"use client\";\n\nimport { Radio } from \"@base-ui/react/radio\";\nimport { RadioGroup as BaseRadioGroup } from \"@base-ui/react/radio-group\";\nimport { useId } from \"react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { cn, focusRing } from \"@/components/ikada/lib/cn\";\n\nexport interface RadioOption {\n  value: string;\n  label: ReactNode;\n  description?: ReactNode;\n  disabled?: boolean;\n}\n\nexport interface RadioGroupProps extends Omit<ComponentProps<typeof BaseRadioGroup>, \"className\" | \"children\"> {\n  /** The choices, in order: value, label and an optional description. */\n  options: readonly RadioOption[];\n  /** The group's visible name. Without one, pass `aria-label`. */\n  legend?: ReactNode;\n  /** Marks the group invalid: danger rings and aria-invalid. */\n  invalid?: boolean;\n  /** `horizontal` lays the options in a wrapping row. */\n  orientation?: \"vertical\" | \"horizontal\";\n  className?: string;\n}\n\n/**\n * One choice out of a few, all visible. Arrow keys move and select; Tab leaves the group. The\n * chosen dot lands on the bob spring; the checked fill is solid green (3:1 in both themes).\n */\nexport function RadioGroup({\n  options,\n  legend,\n  invalid,\n  orientation = \"vertical\",\n  className,\n  ref,\n  ...props\n}: RadioGroupProps) {\n  const legendId = useId();\n  return (\n    <BaseRadioGroup\n      data-slot=\"radio-group\"\n      ref={ref}\n      aria-labelledby={legend ? legendId : undefined}\n      aria-invalid={invalid || undefined}\n      className={cn(\"flex flex-col gap-2\", className)}\n      {...props}\n    >\n      {legend ? (\n        <span id={legendId} className=\"text-sb-sm font-semibold text-fg\">\n          {legend}\n        </span>\n      ) : null}\n      <div className={cn(\"flex gap-x-5 gap-y-2.5\", orientation === \"vertical\" ? \"flex-col\" : \"flex-row flex-wrap\")}>\n        {options.map((option, index) => (\n          <label\n            key={option.value}\n            className={cn(\n              \"inline-flex max-w-full items-start gap-2.5 text-sb-base text-fg\",\n              option.disabled && \"cursor-not-allowed opacity-50\",\n            )}\n          >\n            <Radio.Root\n              value={option.value}\n              disabled={option.disabled}\n              // A <label> does not name a role=\"radio\" span, so name it by reference.\n              aria-labelledby={`${legendId}-label-${index}`}\n              aria-describedby={option.description ? `${legendId}-description-${index}` : undefined}\n              className={cn(\n                \"relative mt-px inline-flex size-4.5 shrink-0 items-center justify-center rounded-full bg-well\",\n                \"shadow-[inset_0_0_0_1.5px_var(--sb-control-edge)]\",\n                \"transition-[background-color,box-shadow] duration-(--sb-duration-hover) ease-hover\",\n                \"hover:shadow-[inset_0_0_0_1.5px_var(--sb-fg-muted)]\",\n                \"data-checked:bg-brand data-checked:shadow-none\",\n                \"data-disabled:cursor-not-allowed\",\n                \"before:absolute before:-inset-3 before:content-[''] pointer-fine:before:hidden\",\n                invalid && \"shadow-[inset_0_0_0_1.5px_var(--sb-danger-dot)]\",\n                focusRing,\n              )}\n            >\n              <Radio.Indicator\n                keepMounted={false}\n                className={cn(\n                  \"size-1.5 rounded-full bg-on-brand data-starting-style:scale-(--sb-scale-icon) data-starting-style:opacity-0\",\n                  // The dot lands on the bob spring: it grows past full size once, then settles.\n                  \"[transition:opacity_var(--sb-duration-hover)_var(--ease-out),scale_var(--sb-duration-spring-bob)_var(--sb-spring-bob)]\",\n                )}\n              />\n            </Radio.Root>\n            <span className=\"flex min-w-0 flex-col [overflow-wrap:anywhere]\">\n              <span id={`${legendId}-label-${index}`}>{option.label}</span>\n              {option.description ? (\n                <span id={`${legendId}-description-${index}`} className=\"text-pretty text-sb-sm text-fg-muted\">\n                  {option.description}\n                </span>\n              ) : null}\n            </span>\n          </label>\n        ))}\n      </div>\n    </BaseRadioGroup>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/ikada/radio-group.tsx"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}