"use client"; import { Children, Fragment, cloneElement, isValidElement, type ReactElement, type ReactNode, type Ref, } from "react"; import { cx } from "../../utils"; import type { FoxCheckboxSize } from "../fox-checkbox"; export type FoxCheckboxGroupOrientation = "horizontal" | "vertical"; export interface FoxCheckboxGroupProps { orientation?: FoxCheckboxGroupOrientation; /** * 자식 체크박스의 `name`을 전부 이 값으로 덮어쓴다. 라디오와 달리 `name`이 같아도 서로를 * 끄지 않는다 — 폼에서 같은 이름의 값 여러 개로 함께 전송될 뿐이다. */ name?: string; /** 자식 체크박스의 `size`를 전부 이 값으로 덮어쓴다. 넘기지 않으면 자식이 각자 정한다. */ size?: FoxCheckboxSize; /** 그룹을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */ label?: string; /** 그룹 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */ labelledBy?: string; /** `FoxCheckbox`들. */ children?: ReactNode; /** 배치 조정용. */ className?: string; ref?: Ref; } const ORIENTATION_CLASS: Record = { horizontal: "fox-checkbox-group--horizontal", vertical: "fox-checkbox-group--vertical", }; type Overrides = { name?: string; size?: FoxCheckboxSize }; /** * 자식 체크박스에 그룹 값을 강제로 덮어쓴다. * * - 호스트 요소(`div` 등)는 건드리지 않는다 — DOM에 없는 속성이 넘어가면 React가 경고한다. * - `Children.map`은 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면 * `<>…`로 감싼 체크박스들이 덮어쓰기를 피해 가고 Fragment에 `name`이 붙어 경고가 난다. */ function overrideChildren(children: ReactNode, overrides: Overrides): ReactNode { return Children.map(children, (child) => { if (!isValidElement(child)) { return child; } if (child.type === Fragment) { const fragment = child as ReactElement<{ children?: ReactNode }>; return overrideChildren(fragment.props.children, overrides); } if (typeof child.type === "string") { return child; } return cloneElement(child as ReactElement>, overrides); }); } /** * @fox 체크박스 묶음. 배치와 `name`·`size` 통일만 책임지고 상태를 갖지 않는다. * * `role="radiogroup"`이 아니라 `role="group"`이다 — 체크박스는 서로를 끄지 않아 "하나만 * 고르는 묶음"이 아니고, 화살표 이동으로 옮겨 다니지도 않는다. 각 칸이 독립적으로 Tab을 받는다. * * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` * (또는 개별 파티셜)로 한 번 불러와야 한다. */ export function FoxCheckboxGroup({ orientation = "vertical", name, size, label, labelledBy, children, className, ref, }: FoxCheckboxGroupProps) { // 값을 준 것만 넣는다 — `undefined`를 그대로 넘기면 자식이 스스로 정한 값을 지운다. const overrides: Overrides = {}; if (name !== undefined) { overrides.name = name; } if (size !== undefined) { overrides.size = size; } return (
{overrideChildren(children, overrides)}
); }