'use client'; // D6 §5.1 — 고밀도 "선/테이블 클래식" DataTable // 시맨틱 필수(div 그리드 금지). sticky , 행 구분 하단 1px 선만(세로선·zebra 없음), // 숫자열 우측 정렬 + tabular-nums, 밀도 3단(html[data-density] 변수 소비 — data-table.scss), // 우선순위 낮은 열은 mobile drop(data-priority), 정렬 열 aria-sort,
필수(sr-only 허용). import '@/app/styles/data-table.scss'; import { cn } from '@/lib/utils/client'; export type ColumnAlign = 'left'|'right'|'center'; export type ColumnPriority = 'high'|'medium'|'low'; export type SortDirection = 'asc'|'desc'; export interface DataTableColumn { // 열 식별자 key: string; // 헤더 라벨 header: React.ReactNode; // 셀 렌더러 cell: (_row: T, _index: number) => React.ReactNode; // 정렬 (기본 left, 숫자열은 right) align?: ColumnAlign; // 우선순위 — low 는 mobile(<768px)에서 숨김 (data-priority) priority?: ColumnPriority; // 정렬 가능 열이면 방향(없으면 미정렬), 클릭 핸들러는 onSort sort?: SortDirection|null; // 헤더 셀 추가 className headerClassName?: string; // 바디 셀 추가 className cellClassName?: string; } interface DataTableProps { // sr-only caption (접근성 — 표 요약). 화면 표시하려면 captionVisible caption: string; captionVisible?: boolean; columns: DataTableColumn[]; rows: T[]; rowKey: (_row: T, _index: number) => string|number; // 행 클릭/링크 — 반환값(href)이 있으면 첫 셀에 링크 래핑은 컬럼 cell 이 담당. 여기선 tr onClick 만. onRowClick?: (_row: T, _index: number) => void; // 정렬 헤더 클릭 onSort?: (_key: string) => void; // 빈 상태 메시지 emptyMessage?: string; className?: string; } function ariaSort(sort: SortDirection|null|undefined): 'ascending'|'descending'|'none'|undefined { if (sort === 'asc') { return 'ascending'; } if (sort === 'desc') { return 'descending'; } // 정렬 가능 열이지만 현재 미적용(null)은 'none', 정렬 불가 열(undefined)은 aria-sort 미부여 return sort === null ? 'none' : undefined; } export default function DataTable({ caption, captionVisible = false, columns, rows, rowKey, onRowClick, onSort, emptyMessage = '표시할 항목이 없습니다.', className, }: DataTableProps) { return ( {columns.map(col => { const sortable = col.sort !== undefined; return ( ); })} {rows.length === 0 ? ( ) : ( rows.map((row, index) => ( onRowClick(row, index) : undefined} className={onRowClick ? 'data-table__row--clickable' : undefined} > {columns.map(col => ( ))} )) )}
{caption}
{sortable && onSort ? ( ) : ( col.header )}
{emptyMessage}
{col.cell(row, index)}
); }