{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "title": "RadioGroup",
  "description": "유니버설 라디오 그룹 — context 단일 선택(value/onValueChange), 항목 role=radio + 8px 선택 점, 그룹 disabled 일괄 전파.",
  "dependencies": [
    "@unikit-bridge/ui",
    "class-variance-authority",
    "clsx",
    "tailwind-merge",
    "tailwindcss"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/radio-group.tsx",
      "content": "// RadioGroup — shadcn/ui new-york-v4 radio-group을 UniKit UI 브리지로 이식.\n// 포팅 판단:\n// - radix RadioGroup（Root/Item/Indicator） 제거 — 선택 상태는 React context로\n//   직접 관리하고, 원본의 data-state=checked 셀렉터 대신 선택 점 조건부 렌더.\n// - CircleIcon（lucide） 제거 — 원본 Indicator（fill-primary, size-2=8px）를\n//   동일 크기의 점 View（bg-primary）로 대체（문자 글리프보다 치수가 정확하다）.\n// - RadioGroup 컨테이너는 role=\"radiogroup\"（RN 0.88 표준 역할 — 스크린리더가\n//   라디오 묶음으로 발표）을 지정한다（사이클 30 G4 반영）.\n// - 원본 컨테이너 \"grid gap-3\"에서 grid는 웹 전용이라 제거 — View 기본 flex-col\n//   + gap-3으로 세로 배치를 양쪽에서 동일하게 재현.\n// - aria-invalid:* 클래스는 v0.1 API에 invalid 상태가 없어 제외.\nimport {\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\nimport {\n  Pressable,\n  View,\n  type BridgePressableRef,\n  type BridgeViewRef,\n  type PressableProps,\n  type ViewProps,\n} from \"@unikit-bridge/ui/primitives\";\n\nimport { cn } from \"@/lib/utils\";\n\n// 그룹 상태 — RadioGroup이 공급하고 RadioGroupItem이 소비.\ntype RadioGroupContextValue = {\n  /** 현재 선택값（선택 없음 \"\"） */\n  value: string;\n  /** 그룹 차단이 항목에 전파된다 */\n  disabled: boolean;\n  /** 항목 눌림 — 항목이 자기 value를 전달해 그룹이 정리한다 */\n  onItemPress: (value: string) => void;\n};\n\nconst RadioGroupContext = createContext<RadioGroupContextValue>({\n  value: \"\",\n  disabled: false,\n  onItemPress: () => {},\n});\n\nexport type RadioGroupProps = ViewProps & {\n  /** 제어형 선택값 */\n  value?: string;\n  /** 비제어형 초기 선택값 */\n  defaultValue?: string;\n  /** 선택 변경 콜백 */\n  onValueChange?: (value: string) => void;\n  /** 그룹 전체 차단 — 항목에 일괄 전파 */\n  disabled?: boolean;\n};\n\nconst RadioGroup = forwardRef<BridgeViewRef, RadioGroupProps>(\n  (\n    {\n      className,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      disabled = false,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    // 제어/비제어 통합 — 라디오는 항상 단일 선택이라 문자열 state면 충분하다.\n    const [uncontrolled, setUncontrolled] = useState(defaultValue);\n    const isControlled = value !== undefined;\n    const selectedValue = isControlled ? value : uncontrolled;\n\n    const handleItemPress = useCallback(\n      (itemValue: string) => {\n        if (!isControlled) setUncontrolled(itemValue);\n        onValueChange?.(itemValue);\n      },\n      [isControlled, onValueChange],\n    );\n\n    const contextValue = useMemo(\n      () => ({\n        value: selectedValue,\n        disabled,\n        onItemPress: handleItemPress,\n      }),\n      [selectedValue, disabled, handleItemPress],\n    );\n\n    return (\n      <View\n        ref={ref}\n        accessibilityRole=\"radiogroup\"\n        className={cn(\"gap-3\", className)}\n        {...props}\n      >\n        <RadioGroupContext.Provider value={contextValue}>\n          {children}\n        </RadioGroupContext.Provider>\n      </View>\n    );\n  },\n);\nRadioGroup.displayName = \"RadioGroup\";\n\n// 터치 타깃 44px 보정: 16px 원 → hitSlop = （44 − 16） / 2 = 14px.\nconst HIT_SLOP = 14;\n\nexport type RadioGroupItemProps = Omit<\n  PressableProps,\n  \"onPress\" | \"aria-checked\"\n> & {\n  /** 이 항목의 값 — 그룹 value와의 일치 여부가 선택 상태가 된다 */\n  value: string;\n};\n\nconst RadioGroupItem = forwardRef<BridgePressableRef, RadioGroupItemProps>(\n  ({ className, value, disabled, hitSlop, ...props }, ref) => {\n    const group = useContext(RadioGroupContext);\n    const isChecked = group.value === value;\n    const isDisabled = disabled || group.disabled;\n\n    return (\n      <Pressable\n        ref={ref}\n        accessibilityRole=\"radio\"\n        aria-checked={isChecked}\n        aria-disabled={isDisabled ? true : undefined}\n        disabled={isDisabled}\n        hitSlop={hitSlop ?? HIT_SLOP}\n        onPress={() => group.onItemPress(value)}\n        className={cn(\n          // 링은 border-input（라이트 1.27:1）이 배경에 묻혀 컨트롤 경계가 안\n          // 보이므로 foreground/50（양 모드 ≥3:1, G4）로 조정.\n          \"flex size-4 shrink-0 flex-row items-center justify-center rounded-full border border-foreground/50 web:transition-all web:outline-none web:focus-visible:border-ring web:focus-visible:ring-3 web:focus-visible:ring-ring/50\",\n          isDisabled &&\n            \"web:pointer-events-none web:cursor-not-allowed opacity-50\",\n          className,\n        )}\n        {...props}\n      >\n        {/* 선택 점 — 원본 Indicator（fill-primary size-2 = 8px） 대응.\n            선택 시에만 렌더, 항목 테두리는 선택과 무관하게 유지된다. */}\n        {isChecked ? (\n          <View aria-hidden className=\"size-2 rounded-full bg-primary\" />\n        ) : null}\n      </Pressable>\n    );\n  },\n);\nRadioGroupItem.displayName = \"RadioGroupItem\";\n\nexport { RadioGroup, RadioGroupItem };\n",
      "type": "registry:ui",
      "target": "components/unikit/radio-group.tsx"
    }
  ],
  "type": "registry:ui"
}
