"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; /** * The panel's data table. * * From md up it's an ordinary table. Long values (emails, URLs) may wrap * anywhere so the columns always fit; if they still can't, the table scrolls * sideways inside its card as a last resort. Labels inside cells should use * , never `truncate` (see clamp.tsx for why). * * On a phone (below md) each row becomes a stacked card: one line per cell, labelled * with its column heading. The labels are read from the table's own s * and stamped onto the cells as `data-label`, so pages need no extra markup * — every table in the panel gets the card layout for free. Cells under an * empty heading (an actions column) simply go unlabelled. * * `card="stacked"` puts each label above its value instead of beside it, so * long content (a feedback message) gets the card's full width. */ type CardLayout = "inline" | "stacked"; const CardLayoutContext = React.createContext("inline"); export function Table({ children, card = "inline", }: { children: React.ReactNode; /** Phone card layout: labels beside values (default) or above them. */ card?: CardLayout; }) { const ref = React.useRef(null); // Runs after every render: rows arrive and change with the data. React.useLayoutEffect(() => { const table = ref.current; if (!table) return; const heads = Array.from(table.querySelectorAll("thead th")).map( (th) => th.textContent?.trim() ?? "", ); table.querySelectorAll("tbody tr").forEach((tr) => { let col = 0; Array.from(tr.children).forEach((cell) => { const td = cell as HTMLTableCellElement; const label = td.colSpan > 1 ? "" : (heads[col] ?? ""); if (label) td.dataset.label = label; else delete td.dataset.label; col += td.colSpan || 1; }); }); }); return (
{children}
); } export function THead({ children }: { children: React.ReactNode }) { return ( {children} ); } export function TH({ children, className, }: { children?: React.ReactNode; className?: string; }) { return ( {children} ); } export function TR({ children, className, }: { children: React.ReactNode; className?: string; }) { return ( {children} ); } export function TD({ children, className, }: { children?: React.ReactNode; className?: string; }) { const card = React.useContext(CardLayoutContext); return ( {children} ); }