"use client"; import { Children, Fragment, cloneElement, isValidElement, type ReactElement, type ReactNode, type Ref, } from "react"; import { cx } from "../../utils"; import type { FoxSegmentedControlItemSize } from "../fox-segmented-control-item"; export type FoxSegmentedControlSize = FoxSegmentedControlItemSize; export interface FoxSegmentedControlProps { /** 자식 칸의 `size`를 전부 이 값으로 덮어쓴다. 묶음의 모서리 크기도 이 값이 정한다. */ size?: FoxSegmentedControlSize; /** * 자식 칸의 `name`을 전부 이 값으로 덮어쓴다. 묶음 안에서 `name`이 어긋나면 브라우저가 * 다른 묶음으로 보고 여러 칸이 동시에 켜지므로, 묶음이 단일 진실 공급원이다. */ name?: string; /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */ label?: string; /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */ labelledBy?: string; /** `FoxSegmentedControlItem`들. */ children?: ReactNode; /** 배치 조정용. */ className?: string; ref?: Ref; } const SIZE_CLASS: Record = { lg: "fox-segmented-control--lg", md: "fox-segmented-control--md", sm: "fox-segmented-control--sm", xsm: "fox-segmented-control--xsm", }; type Overrides = { name?: string; size?: FoxSegmentedControlSize }; /** * 자식 칸에 묶음 값을 강제로 덮어쓴다. * * - 호스트 요소(`div` 등)는 건드리지 않는다 — DOM에 없는 속성이 넘어가면 React가 경고한다. * - `Children.map`은 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면 * `<>…`로 감싼 칸들이 덮어쓰기를 피해 가고 Fragment에 `name`이 붙어 경고가 난다. * * `FoxRadioGroup`의 같은 함수와 규칙이 같다. */ 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`를 통일한다. 상태를 * 갖지 않는다 — 어느 칸이 켜졌는지는 각 칸의 네이티브 ``가 소유한다. * * `size`는 두 곳에 쓰인다: 묶음의 모서리 크기(`form-radius-*`)와 자식 칸의 크기다. 둘이 * 어긋나면 칸 배경이 둥근 모서리와 맞지 않으므로 값을 하나로 묶어 둔다. * * 방향 수식어가 없다 — 칸들은 가로로 붙어 늘어서는 것이 유일한 배치다. * * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` * (또는 개별 파티셜)로 한 번 불러와야 한다. */ export function FoxSegmentedControl({ size = "lg", name, label, labelledBy, children, className, ref, }: FoxSegmentedControlProps) { // 크기는 묶음이 언제나 정한다 — 모서리와 칸 높이가 갈라지면 안 된다. `name`은 값을 준 // 때만 넣는다(`undefined`를 그대로 넘기면 자식이 스스로 정한 값을 지운다). const overrides: Overrides = { size }; if (name !== undefined) { overrides.name = name; } return (
{overrideChildren(children, overrides)}
); }