{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "title": "Radio group",
  "author": "andongmin94",
  "description": "A set of mutually exclusive options for selecting one value.",
  "dependencies": [
    "@base-ui/react@^1.6.0"
  ],
  "files": [
    {
      "path": "src/components/ui/radio-group.tsx",
      "content": "\"use client\";\n\nimport { Radio as RadioPrimitive } from \"@base-ui/react/radio\";\nimport { RadioGroup as RadioGroupPrimitive } from \"@base-ui/react/radio-group\";\nimport { DirectionProvider } from \"@base-ui/react/direction-provider\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype RadioGroupProps = Omit<\n  RadioGroupPrimitive.Props<string>,\n  \"dir\" | \"onValueChange\" | \"value\"\n> & {\n  asChild?: boolean;\n  children?: React.ReactNode;\n  dir?: \"ltr\" | \"rtl\";\n  loop?: boolean;\n  onValueChange?: (value: string, eventDetails: RadioGroupPrimitive.ChangeEventDetails) => void;\n  orientation?: \"horizontal\" | \"vertical\";\n  value?: string | null;\n};\n\ntype RenderProps = React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> };\n\nfunction mergeRefs(...refs: (React.Ref<HTMLElement> | undefined)[]) {\n  return (element: HTMLElement | null) => {\n    refs.forEach((ref) => {\n      if (typeof ref === \"function\") {\n        ref(element);\n      } else if (ref) {\n        ref.current = element;\n      }\n    });\n  };\n}\n\nfunction preserveRadixEventCancellation(\n  child: React.ReactElement<{ [key: string]: unknown; children?: React.ReactNode }>,\n) {\n  const eventProps: Record<string, unknown> = {};\n\n  for (const [name, handler] of Object.entries(child.props)) {\n    if (/^on[A-Z]/.test(name) && typeof handler === \"function\") {\n      eventProps[name] = (...args: unknown[]) => {\n        (handler as (...handlerArgs: unknown[]) => void)(...args);\n        const event = args[0] as\n          | { defaultPrevented?: boolean; preventBaseUIHandler?: () => void }\n          | undefined;\n        if (event?.defaultPrevented) event.preventBaseUIHandler?.();\n      };\n    }\n  }\n\n  return React.cloneElement(child, eventProps);\n}\n\nfunction renderWithAliases<State>(\n  render:\n    | React.ReactElement\n    | ((props: RenderProps, state: State) => React.ReactElement)\n    | undefined,\n  fallback: React.ReactElement,\n  getAliases: (state: State) => Record<string, string | undefined>,\n) {\n  return (elementProps: RenderProps, state: State) => {\n    const aliasedProps = mergeProps(\n      elementProps as React.ComponentPropsWithRef<\"button\">,\n      getAliases(state) as React.ComponentPropsWithRef<\"button\">,\n    ) as RenderProps;\n\n    if (typeof render === \"function\") {\n      return render(aliasedProps, state);\n    }\n\n    const element = (render ?? fallback) as React.ReactElement<RenderProps>;\n    const mergedProps = mergeProps(\n      aliasedProps as React.ComponentPropsWithRef<\"button\">,\n      element.props as React.ComponentPropsWithRef<\"button\">,\n    ) as RenderProps;\n    mergedProps.ref = mergeRefs(aliasedProps.ref, element.props.ref);\n    return React.cloneElement(element, mergedProps);\n  };\n}\n\nfunction getChildElement(children: React.ReactNode, componentName: string) {\n  const child = React.Children.toArray(children).find(React.isValidElement);\n  if (child === undefined) {\n    throw new Error(`${componentName} with asChild requires a valid React element child.`);\n  }\n  return preserveRadixEventCancellation(\n    child as React.ReactElement<{ [key: string]: unknown; children?: React.ReactNode }>,\n  );\n}\n\nfunction RadioGroup({\n  asChild = false,\n  className,\n  children,\n  dir,\n  loop = true,\n  onKeyDown,\n  orientation,\n  render,\n  value,\n  ...props\n}: RadioGroupProps) {\n  const group = (\n    <RadioGroupPrimitive\n      data-slot=\"radio-group\"\n      data-orientation={orientation}\n      dir={dir}\n      className={cn(\"grid gap-2\", className)}\n      onKeyDown={(event) => {\n        onKeyDown?.(event);\n        if (event.defaultPrevented || event.baseUIHandlerPrevented) {\n          event.preventBaseUIHandler();\n          return;\n        }\n\n        const horizontalKey = event.key === \"ArrowLeft\" || event.key === \"ArrowRight\";\n        const verticalKey = event.key === \"ArrowUp\" || event.key === \"ArrowDown\";\n\n        if (\n          (orientation === \"horizontal\" && verticalKey) ||\n          (orientation === \"vertical\" && horizontalKey)\n        ) {\n          event.preventBaseUIHandler();\n          return;\n        }\n\n        if (loop || (!horizontalKey && !verticalKey)) {\n          return;\n        }\n\n        const radios = Array.from(\n          event.currentTarget.querySelectorAll<HTMLElement>('[role=\"radio\"]'),\n        ).filter(\n          (radio) =>\n            radio.closest('[role=\"radiogroup\"]') === event.currentTarget &&\n            radio.getAttribute(\"aria-disabled\") !== \"true\" &&\n            !radio.hasAttribute(\"data-disabled\"),\n        );\n        const currentRadio = (event.target as Element).closest<HTMLElement>('[role=\"radio\"]');\n        const currentIndex = currentRadio === null ? -1 : radios.indexOf(currentRadio);\n\n        if (currentIndex === -1) {\n          return;\n        }\n\n        const rtl = getComputedStyle(event.currentTarget).direction === \"rtl\";\n        const backward =\n          event.key === \"ArrowUp\" || event.key === (rtl ? \"ArrowRight\" : \"ArrowLeft\");\n        const forward =\n          event.key === \"ArrowDown\" || event.key === (rtl ? \"ArrowLeft\" : \"ArrowRight\");\n\n        if ((backward && currentIndex === 0) || (forward && currentIndex === radios.length - 1)) {\n          event.preventBaseUIHandler();\n        }\n      }}\n      render={renderWithAliases<RadioGroupPrimitive.State>(\n        asChild ? getChildElement(children, \"RadioGroup\") : render,\n        React.createElement(\"div\"),\n        (state) => ({\n          \"data-disabled\": state.disabled ? \"\" : undefined,\n          \"data-orientation\": orientation,\n        }),\n      )}\n      value={value === null ? \"\" : value}\n      {...props}\n    >\n      {asChild ? undefined : children}\n    </RadioGroupPrimitive>\n  );\n\n  return dir === undefined ? group : <DirectionProvider direction={dir}>{group}</DirectionProvider>;\n}\n\ntype RadioGroupItemProps = Omit<RadioPrimitive.Root.Props<string>, \"ref\" | \"value\"> & {\n  asChild?: boolean;\n  checked?: boolean;\n  children?: React.ReactNode;\n  form?: string;\n  forceMount?: true;\n  type?: \"button\" | \"reset\" | \"submit\";\n  value: string;\n};\n\nconst RadioGroupItem = React.forwardRef<HTMLButtonElement, RadioGroupItemProps>(\n  function RadioGroupItem(\n    {\n      asChild = false,\n      checked: _checked,\n      children,\n      className,\n      form,\n      forceMount,\n      nativeButton,\n      render,\n      type = \"button\",\n      value,\n      ...props\n    },\n    forwardedRef,\n  ) {\n    const renderElement = asChild\n      ? getChildElement(children, \"RadioGroupItem\")\n      : (render ?? React.createElement(\"button\", { form, type }));\n    const resolvedNativeButton =\n      nativeButton ??\n      (typeof renderElement !== \"function\" &&\n        typeof renderElement.type === \"string\" &&\n        renderElement.type === \"button\");\n\n    return (\n      <RadioPrimitive.Root\n        ref={forwardedRef}\n        data-slot=\"radio-group-item\"\n        className={cn(\n          \"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border-2 border-border text-black outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:text-white\",\n          className,\n        )}\n        nativeButton={resolvedNativeButton}\n        value={value}\n        render={renderWithAliases<RadioPrimitive.Root.State>(\n          renderElement,\n          React.createElement(\"button\"),\n          (state) => ({\n            \"data-disabled\": state.disabled ? \"\" : undefined,\n            \"data-state\": state.checked ? \"checked\" : \"unchecked\",\n            form,\n            type,\n            value,\n          }),\n        )}\n        {...props}\n      >\n        <RadioPrimitive.Indicator\n          data-slot=\"radio-group-indicator\"\n          className=\"flex size-4 items-center justify-center\"\n          keepMounted={forceMount}\n          render={renderWithAliases<RadioPrimitive.Indicator.State>(\n            undefined,\n            <span />,\n            (state) => ({\n              \"data-disabled\": state.disabled ? \"\" : undefined,\n              \"data-state\": state.checked ? \"checked\" : \"unchecked\",\n            }),\n          )}\n        >\n          <span className=\"absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-current\" />\n        </RadioPrimitive.Indicator>\n      </RadioPrimitive.Root>\n    );\n  },\n);\n\nexport { RadioGroup, RadioGroupItem };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "form",
    "selection"
  ],
  "type": "registry:ui"
}