"use client"; import { Children, Fragment, cloneElement, isValidElement, type ReactElement, type ReactNode, } from "react"; import { cx } from "../../utils"; export type FoxButtonGroupOrientation = "horizontal" | "vertical"; export type FoxButtonGroupAlign = "start" | "center" | "end"; export type FoxButtonGroupSize = "lg" | "md" | "sm" | "xsm"; export interface FoxButtonGroupProps { orientation?: FoxButtonGroupOrientation; /** 주축 정렬. 세로에서는 그룹 높이가 정해져 있을 때만 눈에 띈다. */ align?: FoxButtonGroupAlign; /** 자식 버튼의 `size`를 전부 이 값으로 덮어쓴다. 자식이 따로 준 값은 무시된다. */ size: FoxButtonGroupSize; wrap?: boolean; /** 배치 조정용. */ className?: string; children?: ReactNode; } const ORIENTATION_CLASS: Record = { horizontal: "fox-button-group--horizontal", vertical: "fox-button-group--vertical", }; const ALIGN_CLASS: Record = { start: "fox-button-group--start", center: "fox-button-group--center", end: "fox-button-group--end", }; type Overrides = { size: FoxButtonGroupSize; fullWidth?: true }; /** * 자식 버튼에 그룹 값을 강제로 덮어쓴다. * * - 호스트 요소(`div` 등)는 건드리지 않는다 — DOM에 없는 속성이 넘어가면 React가 경고한다. * - `Children.map`은 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면 * `<>…`로 감싼 버튼들이 덮어쓰기를 피해 가고 Fragment에 `size`가 붙어 경고가 난다. */ 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 버튼 묶음. 간격·방향·정렬만 책임지고 상태를 갖지 않는다. * * 크기는 **그룹이 단일 진실 공급원**이다 — 자식이 저마다 다른 `size`를 줘도 그룹 값으로 * 통일된다. 세로일 때는 자식이 항상 폭을 채운다. * * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` * (또는 개별 파티셜)로 한 번 불러와야 한다. */ export function FoxButtonGroup({ orientation = "horizontal", align = "start", size, wrap = false, className, children, }: FoxButtonGroupProps) { const overrides: Overrides = orientation === "vertical" ? { size, fullWidth: true } : { size }; return (
{overrideChildren(children, overrides)}
); }