"use client"; import { Children, Fragment, cloneElement, isValidElement, type ReactElement, type ReactNode, } from "react"; import { cx } from "../../utils"; export interface FoxTableRowProps { children?: ReactNode; /** 참이면 머리행이다. 안의 칸이 모두 ``이 된다. */ head?: boolean; /** 참이면 고른 줄이 된다 — 바탕과 글자가 파랗게 바뀐다(시안 list 표). */ selected?: boolean; /** * 주면 행 전체가 눌린다 — 시안이 관리 열 없이 행을 눌러 상세를 여는 경우다. * * ``에 `role="button"`을 씌우지 않는다 — 그러면 보조기술에서 행이 표의 일부가 아니게 * 되어 열 이름 읽기가 끊긴다. 대신 초점을 받게만 하고 Enter·Space를 직접 듣는다. 행의 * 이름은 셀 내용이 대신하므로 눌러야 할 행이 무엇인지 알 수 있다. */ onSelect?: () => void; /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ hidden?: boolean; /** 배치 조정용. */ className?: string; } /** 머리행이라는 사실을 칸에 내려 준다. 프래그먼트는 `Children.map`이 들어가지 않아 직접 내려간다. */ function markCells(children: ReactNode): ReactNode { return Children.map(children, (child) => { if (!isValidElement(child)) { return child; } if (child.type === Fragment) { const fragment = child as ReactElement<{ children?: ReactNode }>; return markCells(fragment.props.children); } if (typeof child.type === "string") { return child; } return cloneElement(child as ReactElement>, { inHead: true }); }); } /** * @fox 표의 한 줄. * * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` * (또는 개별 파티셜)로 한 번 불러와야 한다. */ export function FoxTableRow({ children, head = false, selected = false, onSelect, hidden = false, className, }: FoxTableRowProps) { if (hidden) { return null; } const clickable = Boolean(onSelect) && !head; return ( { // Space는 기본 동작이 스크롤이라 막는다. if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onSelect?.(); } } : undefined } > {head ? markCells(children) : children} ); }