File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-14
08-18
08-18
File name
Commit message
Commit date
"use client";
import {
Children,
Fragment,
isValidElement,
type ReactElement,
type ReactNode,
} from "react";
import { cx } from "../../utils";
import type { FoxTableRowProps } from "../fox-table-row";
export type FoxTableType = "basic" | "list" | "form";
export interface FoxTableProps {
/** `FoxTableRow`들. 머리행은 `head`를 주면 되고 순서는 신경 쓰지 않아도 된다. */
children?: ReactNode;
/**
* `basic`은 제목 열이 있는 상세표, `list`는 목록표, `form`은 레이블 + 입력표다.
* 여백·글자·기본 정렬이 이 값에 따라 달라진다.
*/
type?: FoxTableType;
/** 표가 무엇을 담는지. `<caption>`으로 나가며 화면에는 보이지 않는다. */
caption?: string;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
/** 배치 조정용. */
className?: string;
}
type RowElement = ReactElement<FoxTableRowProps>;
/** 프래그먼트 안까지 훑어 행을 모은다. */
function collectRows(children: ReactNode): RowElement[] {
const rows: RowElement[] = [];
Children.forEach(children, (child) => {
if (!isValidElement(child)) {
return;
}
if (child.type === Fragment) {
const fragment = child as ReactElement<{ children?: ReactNode }>;
rows.push(...collectRows(fragment.props.children));
return;
}
rows.push(child as RowElement);
});
return rows;
}
/**
* @fox 표. 진짜 `<table>`이라 스크린리더가 몇 행 몇 열인지 읽어 주고 `colSpan`·`rowSpan`이
* 그대로 동작한다.
*
* 호출부는 행을 늘어놓기만 하면 된다 — 머리행(`head`)은 `<thead>`로, 나머지는 `<tbody>`로
* 알아서 갈린다.
*
* 좁은 화면에서는 `list`가 한 줄씩 카드로 접히고 `form`은 레이블이 입력 위로 올라간다
* (시안의 device 축). 그 전환은 CSS가 화면 폭을 보고 하므로 프롭이 없다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxTable({
children,
type = "basic",
caption,
hidden = false,
className,
}: FoxTableProps) {
if (hidden) {
return null;
}
const rows = collectRows(children);
const headRows = rows.filter((row) => row.props.head);
const bodyRows = rows.filter((row) => !row.props.head);
return (
<table className={cx("fox-table", `fox-table--${type}`, className)}>
{caption && <caption className="fox-table__caption">{caption}</caption>}
{headRows.length > 0 && <thead className="fox-table__head">{headRows}</thead>}
{bodyRows.length > 0 && <tbody className="fox-table__body">{bodyRows}</tbody>}
</table>
);
}