File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
"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<FoxButtonGroupOrientation, string> = {
horizontal: "fox-button-group--horizontal",
vertical: "fox-button-group--vertical",
};
const ALIGN_CLASS: Record<FoxButtonGroupAlign, string> = {
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<Record<string, unknown>>, 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 (
<div
className={cx(
"fox-button-group",
ORIENTATION_CLASS[orientation],
ALIGN_CLASS[align],
wrap && "fox-button-group--wrap",
className
)}
>
{overrideChildren(children, overrides)}
</div>
);
}