"use client"; import clsx from "clsx"; import type { ReactNode } from "react"; import { Empty, Skeleton } from "./ui"; export interface Column { key: string; header: ReactNode; render?: (row: T, i: number) => ReactNode; num?: boolean; className?: string; /** hide below `sm` to save width on phones; still visible in the card view */ hideSm?: boolean; mono?: boolean; } interface Props { columns: Column[]; rows: T[] | null | undefined; rowKey: (row: T, i: number) => string | number; loading?: boolean; empty?: ReactNode; /** Card layout under 640px (default) or always horizontal scroll */ mobile?: "cards" | "scroll"; maxHeight?: string; onRowClick?: (row: T) => void; footer?: ReactNode; } function cell(c: Column, row: T, i: number): ReactNode { if (c.render) return c.render(row, i); const v = (row as Record)[c.key]; if (v === null || v === undefined) return —; if (typeof v === "object") return {JSON.stringify(v)}; return String(v); } export function DataTable({ columns, rows, rowKey, loading, empty, mobile = "cards", maxHeight, onRowClick, footer }: Props) { if (loading && !rows) return ; if (!rows || rows.length === 0) return {empty ?? "No rows."}; return (
{/* Cards (mobile) */} {mobile === "cards" && (
    {rows.map((r, i) => (
  • onRowClick(r) : undefined}>
    {columns.map((c) => (
    {c.header}
    {cell(c, r, i)}
    ))}
  • ))}
)} {/* Table */}
{columns.map((c) => ( ))} {rows.map((r, i) => ( onRowClick(r) : undefined}> {columns.map((c) => ( ))} ))}
{c.header}
{cell(c, r, i)}
{footer &&
{footer}
}
); }