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-18
08-18
08-14
08-18
08-18
08-18
08-18
File name
Commit message
Commit date
"use client";
import {
Children,
Fragment,
cloneElement,
isValidElement,
type ReactElement,
type ReactNode,
} from "react";
import { cx } from "../../utils";
export interface FoxTableRowProps {
children?: ReactNode;
/** 참이면 머리행이다. 안의 칸이 모두 `<th scope="col">`이 된다. */
head?: boolean;
/** 참이면 고른 줄이 된다 — 바탕과 글자가 파랗게 바뀐다(시안 list 표). */
selected?: boolean;
/**
* 주면 행 전체가 눌린다 — 시안이 관리 열 없이 행을 눌러 상세를 여는 경우다.
*
* `<tr>`에 `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<Record<string, unknown>>, { 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 (
<tr
className={cx(
"fox-table-row",
head && "fox-table-row--head",
selected && "fox-table-row--selected",
clickable && "fox-table-row--clickable",
className
)}
aria-selected={selected || undefined}
tabIndex={clickable ? 0 : undefined}
onClick={clickable ? onSelect : undefined}
onKeyDown={
clickable
? (event) => {
// Space는 기본 동작이 스크롤이라 막는다.
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onSelect?.();
}
}
: undefined
}
>
{head ? markCells(children) : children}
</tr>
);
}