import type { ReactNode } from "react"; import "@shared/components/Table.css"; export interface TableColumn { /** Stable column id. */ key: string; header: ReactNode; /** Cell renderer for a row. */ render: (row: T) => ReactNode; align?: "left" | "right" | "center"; /** Optional fixed/min width (any CSS length). */ width?: string; } export interface TableProps { columns: TableColumn[]; rows: T[]; /** Stable key per row. */ rowKey: (row: T) => string; /** Makes rows interactive (hover + click + keyboard). */ onRowClick?: (row: T) => void; /** Rendered in place of the body when there are no rows. */ empty?: ReactNode; className?: string; } /** * Minimal data table primitive. Columns own their own cell renderers, so the * table stays presentational — callers pre-sort/filter and pass the rows they * want shown. Rows become focusable buttons-in-disguise when `onRowClick` is * set. */ export function Table({ columns, rows, rowKey, onRowClick, empty, className, }: TableProps) { const interactive = Boolean(onRowClick); return (
{columns.map((c) => ( ))} {rows.length === 0 ? ( ) : ( rows.map((row) => ( onRowClick(row) : undefined} tabIndex={interactive ? 0 : undefined} role={interactive ? "button" : undefined} onKeyDown={ interactive ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onRowClick?.(row); } } : undefined } > {columns.map((c) => ( ))} )) )}
{c.header}
{empty ?? "No data"}
{c.render(row)}
); }