{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "type": "registry:ui",
  "title": "RadioGroup",
  "description": "Native-input-backed RadioGroup and RadioGroupItem primitives with token-backed checked, focus, invalid, disabled, read-only, density, orientation, dark mode, and FormField-compatible styling.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/radio-group/radio-group.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/radio-group/radio-group.tsx",
      "content": "import {\n  createContext,\n  forwardRef,\n  useContext,\n  useId,\n  useRef,\n  useState,\n  type ChangeEvent,\n  type ChangeEventHandler,\n  type HTMLAttributes,\n  type InputHTMLAttributes,\n  type MouseEventHandler,\n  type ReactNode,\n  type Ref,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type RadioGroupControlSize = \"sm\" | \"md\" | \"lg\";\nexport type RadioGroupOrientation = \"vertical\" | \"horizontal\";\n\nexport interface RadioGroupProps extends HTMLAttributes<HTMLDivElement> {\n  children?: ReactNode;\n  controlSize?: RadioGroupControlSize;\n  defaultValue?: string;\n  disabled?: boolean;\n  invalid?: boolean;\n  name?: string;\n  onValueChange?: (value: string) => void;\n  orientation?: RadioGroupOrientation;\n  readOnly?: boolean;\n  required?: boolean;\n  value?: string;\n}\n\nexport interface RadioGroupItemProps extends Omit<\n  InputHTMLAttributes<HTMLInputElement>,\n  \"checked\" | \"defaultChecked\" | \"onChange\" | \"size\" | \"type\" | \"value\"\n> {\n  \"data-slot\"?: string;\n  checked?: boolean;\n  controlSize?: RadioGroupControlSize;\n  defaultChecked?: boolean;\n  invalid?: boolean;\n  onChange?: ChangeEventHandler<HTMLInputElement>;\n  value: string;\n}\n\ntype RadioGroupContextValue = {\n  controlSize: RadioGroupControlSize;\n  disabled: boolean;\n  invalid: boolean;\n  name: string;\n  onItemChange: (value: string, event: ChangeEvent<HTMLInputElement>) => void;\n  orientation: RadioGroupOrientation;\n  readOnly: boolean;\n  required: boolean;\n  value: string | undefined;\n};\n\nconst RadioGroupContext = createContext<RadioGroupContextValue | null>(null);\n\nconst radioGroupBaseClasses = \"grid min-w-0 gap-[var(--dt-space-2)]\";\n\nconst radioGroupOrientationClasses: Record<RadioGroupOrientation, string> = {\n  vertical: \"grid-cols-1\",\n  horizontal:\n    \"grid-cols-1 sm:flex sm:flex-wrap sm:items-center sm:gap-x-[var(--dt-space-4)] sm:gap-y-[var(--dt-space-2)]\",\n};\n\nconst radioGroupItemRootBaseClasses =\n  \"group/radio relative inline-flex shrink-0 items-center justify-center align-middle\";\n\nconst radioGroupControlSizeClasses: Record<RadioGroupControlSize, string> = {\n  sm: \"[--choice-control-size:calc(var(--dt-density-control)*0.4)] size-[var(--choice-control-size)]\",\n  md: \"[--choice-control-size:calc(var(--dt-density-control)*0.5)] size-[var(--choice-control-size)]\",\n  lg: \"[--choice-control-size:calc(var(--dt-density-control)*0.6)] size-[var(--choice-control-size)]\",\n};\n\nconst radioGroupItemInputClasses =\n  \"peer absolute inset-0 z-10 m-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed\";\n\nconst radioGroupItemIndicatorBaseClasses =\n  \"pointer-events-none flex size-full items-center justify-center rounded-full border border-input bg-background text-transparent shadow-sm outline-none motion-safe:transition-[background-color,border-color,box-shadow,color,opacity] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control peer-disabled:opacity-60 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-background group-disabled/field-set:opacity-60 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[invalid=true]:border-destructive data-[invalid=true]:ring-2 data-[invalid=true]:ring-destructive/15 data-[disabled=true]:opacity-60 data-[readonly=true]:bg-muted/40 data-[readonly=true]:text-muted-foreground\";\n\nconst radioGroupItemDotClasses = \"size-2 rounded-full bg-current\";\n\nfunction useRadioGroupContext() {\n  return useContext(RadioGroupContext);\n}\n\nfunction isAriaInvalid(value: RadioGroupItemProps[\"aria-invalid\"]) {\n  return (\n    value === true ||\n    value === \"true\" ||\n    value === \"grammar\" ||\n    value === \"spelling\"\n  );\n}\n\nfunction setRef<T>(ref: Ref<T> | undefined, node: T | null) {\n  if (typeof ref === \"function\") {\n    ref(node);\n    return;\n  }\n\n  if (ref) {\n    ref.current = node;\n  }\n}\n\nfunction composeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      setRef(ref, node);\n    }\n  };\n}\n\nexport function radioGroupClassNames({\n  className,\n  orientation = \"vertical\",\n}: Pick<RadioGroupProps, \"className\" | \"orientation\"> = {}) {\n  return cn(\n    radioGroupBaseClasses,\n    radioGroupOrientationClasses[orientation],\n    className,\n  );\n}\n\nexport function radioGroupItemClassNames({\n  className,\n  controlSize = \"md\",\n}: Pick<RadioGroupItemProps, \"className\" | \"controlSize\"> = {}) {\n  return cn(\n    radioGroupItemRootBaseClasses,\n    radioGroupControlSizeClasses[controlSize],\n    className,\n  );\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(\n  (\n    {\n      \"aria-invalid\": ariaInvalid,\n      \"aria-label\": ariaLabel,\n      \"aria-labelledby\": ariaLabelledBy,\n      \"aria-orientation\": ariaOrientation,\n      \"aria-required\": ariaRequired,\n      children,\n      className,\n      controlSize = \"md\",\n      defaultValue,\n      disabled = false,\n      invalid = false,\n      name,\n      onValueChange,\n      orientation = \"vertical\",\n      readOnly = false,\n      required = false,\n      role,\n      value,\n      ...props\n    },\n    ref,\n  ) => {\n    const generatedName = useId();\n    const inputName = name ?? `radio-group-${generatedName}`;\n    const isControlled = value !== undefined;\n    const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n    const selectedValue = isControlled ? value : uncontrolledValue;\n    const hasAccessibleName =\n      ariaLabel !== undefined || ariaLabelledBy !== undefined;\n    const resolvedRole = role ?? (hasAccessibleName ? \"radiogroup\" : undefined);\n    const resolvedAriaInvalid = invalid ? true : ariaInvalid;\n    const resolvedAriaRequired = required ? true : ariaRequired;\n    const resolvedAriaOrientation =\n      ariaOrientation ??\n      (resolvedRole === \"radiogroup\" ? orientation : undefined);\n\n    const handleItemChange = (\n      nextValue: string,\n      event: ChangeEvent<HTMLInputElement>,\n    ) => {\n      if (readOnly || event.defaultPrevented) {\n        return;\n      }\n\n      if (!isControlled) {\n        setUncontrolledValue(nextValue);\n      }\n\n      onValueChange?.(nextValue);\n    };\n\n    return (\n      <RadioGroupContext.Provider\n        value={{\n          controlSize,\n          disabled,\n          invalid,\n          name: inputName,\n          onItemChange: handleItemChange,\n          orientation,\n          readOnly,\n          required,\n          value: selectedValue,\n        }}\n      >\n        <div\n          {...props}\n          ref={ref}\n          aria-invalid={resolvedRole ? resolvedAriaInvalid : ariaInvalid}\n          aria-label={ariaLabel}\n          aria-labelledby={ariaLabelledBy}\n          aria-orientation={resolvedAriaOrientation}\n          aria-readonly={\n            resolvedRole === \"radiogroup\" && readOnly\n              ? true\n              : props[\"aria-readonly\"]\n          }\n          aria-required={\n            resolvedRole === \"radiogroup\" ? resolvedAriaRequired : ariaRequired\n          }\n          data-slot=\"radio-group\"\n          data-disabled={disabled ? \"true\" : undefined}\n          data-invalid={invalid ? \"true\" : undefined}\n          data-orientation={orientation}\n          data-readonly={readOnly ? \"true\" : undefined}\n          data-required={required ? \"true\" : undefined}\n          data-size={controlSize}\n          role={resolvedRole}\n          className={radioGroupClassNames({ className, orientation })}\n        >\n          {children}\n        </div>\n      </RadioGroupContext.Provider>\n    );\n  },\n);\n\nRadioGroup.displayName = \"RadioGroup\";\n\nexport const RadioGroupItem = forwardRef<HTMLInputElement, RadioGroupItemProps>(\n  (\n    {\n      \"aria-invalid\": ariaInvalid,\n      \"data-slot\": dataSlot,\n      checked,\n      className,\n      controlSize,\n      defaultChecked = false,\n      disabled,\n      invalid = false,\n      name,\n      onChange,\n      onClick,\n      readOnly,\n      required,\n      value,\n      ...props\n    },\n    ref,\n  ) => {\n    const group = useRadioGroupContext();\n    const inputRef = useRef<HTMLInputElement | null>(null);\n    const groupValue = group?.value;\n    const itemControlSize = controlSize ?? group?.controlSize ?? \"md\";\n    const itemDisabled = disabled ?? group?.disabled ?? false;\n    const itemInvalid =\n      invalid || Boolean(group?.invalid) || isAriaInvalid(ariaInvalid);\n    const itemAriaInvalid = invalid || group?.invalid ? true : ariaInvalid;\n    const itemName = name ?? group?.name;\n    const itemReadOnly = readOnly ?? group?.readOnly ?? false;\n    const itemRequired = required ?? group?.required ?? false;\n    const isGrouped = group !== null;\n    const isControlled = checked !== undefined;\n    const [uncontrolledChecked, setUncontrolledChecked] =\n      useState(defaultChecked);\n    const selected = isGrouped\n      ? groupValue === value\n      : isControlled\n        ? checked\n        : uncontrolledChecked;\n    const state = selected ? \"checked\" : \"unchecked\";\n\n    const restoreReadOnlyState = () => {\n      if (inputRef.current) {\n        inputRef.current.checked = selected;\n      }\n    };\n\n    const handleClick: MouseEventHandler<HTMLInputElement> = (event) => {\n      if (itemReadOnly) {\n        event.preventDefault();\n        restoreReadOnlyState();\n        queueMicrotask(restoreReadOnlyState);\n        return;\n      }\n\n      onClick?.(event);\n    };\n\n    const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n      if (itemReadOnly) {\n        event.preventDefault();\n        restoreReadOnlyState();\n        queueMicrotask(restoreReadOnlyState);\n        return;\n      }\n\n      onChange?.(event);\n\n      if (event.defaultPrevented) {\n        return;\n      }\n\n      if (isGrouped) {\n        group.onItemChange(value, event);\n        return;\n      }\n\n      if (!isControlled) {\n        setUncontrolledChecked(event.currentTarget.checked);\n      }\n    };\n\n    return (\n      <span\n        data-slot={dataSlot ?? \"radio-group-item\"}\n        data-size={itemControlSize}\n        data-state={state}\n        data-checked={selected ? \"true\" : undefined}\n        data-disabled={itemDisabled ? \"true\" : undefined}\n        data-invalid={itemInvalid ? \"true\" : undefined}\n        data-readonly={itemReadOnly ? \"true\" : undefined}\n        data-required={itemRequired ? \"true\" : undefined}\n        data-value={value}\n        className={radioGroupItemClassNames({\n          className,\n          controlSize: itemControlSize,\n        })}\n      >\n        {/* eslint-disable-next-line jsx-a11y/role-supports-aria-props -- aria-invalid remains a global ARIA attribute for native radio error announcements and Field compatibility. */}\n        <input\n          {...props}\n          ref={composeRefs(inputRef, ref)}\n          aria-invalid={itemAriaInvalid}\n          aria-disabled={itemReadOnly ? true : props[\"aria-disabled\"]}\n          checked={selected}\n          className={radioGroupItemInputClasses}\n          data-slot=\"radio-group-item-input\"\n          data-size={itemControlSize}\n          data-state={state}\n          data-checked={selected ? \"true\" : undefined}\n          data-disabled={itemDisabled ? \"true\" : undefined}\n          data-invalid={itemInvalid ? \"true\" : undefined}\n          data-readonly={itemReadOnly ? \"true\" : undefined}\n          data-required={itemRequired ? \"true\" : undefined}\n          data-value={value}\n          disabled={itemDisabled}\n          name={itemName}\n          onChange={handleChange}\n          onClick={handleClick}\n          readOnly={itemReadOnly}\n          required={itemRequired}\n          type=\"radio\"\n          value={value}\n        />\n        <span\n          aria-hidden=\"true\"\n          data-slot=\"radio-group-item-indicator\"\n          data-size={itemControlSize}\n          data-state={state}\n          data-checked={selected ? \"true\" : undefined}\n          data-disabled={itemDisabled ? \"true\" : undefined}\n          data-invalid={itemInvalid ? \"true\" : undefined}\n          data-readonly={itemReadOnly ? \"true\" : undefined}\n          data-required={itemRequired ? \"true\" : undefined}\n          data-value={value}\n          className={radioGroupItemIndicatorBaseClasses}\n        >\n          <span\n            data-slot=\"radio-group-item-dot\"\n            className={cn(\n              radioGroupItemDotClasses,\n              \"motion-safe:ease-control motion-safe:transition-[opacity,scale] motion-safe:duration-[var(--dt-motion-fast)]\",\n              selected\n                ? \"scale-100 opacity-100\"\n                : \"opacity-0 motion-safe:scale-50\",\n            )}\n          />\n        </span>\n      </span>\n    );\n  },\n);\n\nRadioGroupItem.displayName = \"RadioGroupItem\";\n"
    },
    {
      "path": "packages/components/src/components/radio-group/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/radio-group/index.ts",
      "content": "export {\n  RadioGroup,\n  RadioGroupItem,\n  radioGroupClassNames,\n  radioGroupItemClassNames,\n  type RadioGroupControlSize,\n  type RadioGroupItemProps,\n  type RadioGroupOrientation,\n  type RadioGroupProps,\n} from \"./radio-group\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
