import type React from "react"; import { cn } from "@/lib/utils"; import { PANEL, type PanelDensity, type PanelViewMode, } from "@/components/panel-layout"; export interface DataColumn { key: string; header: string; /** Any grid track: "1fr", "120px", "minmax(0,2fr)". */ width: string; cell: (item: T) => React.ReactNode; /** Dropped below md, for columns that are nice rather than necessary. */ hideBelow?: "md" | "lg"; align?: "start" | "end"; } export interface GridColumns { base?: number; md?: number; lg?: number; xl?: number; } // Spelled out so Tailwind can see every class. const COLS: Record = { 1: "grid-cols-1", 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4", }; const MD_COLS: Record = { 1: "md:grid-cols-1", 2: "md:grid-cols-2", 3: "md:grid-cols-3", 4: "md:grid-cols-4", }; const LG_COLS: Record = { 1: "lg:grid-cols-1", 2: "lg:grid-cols-2", 3: "lg:grid-cols-3", 4: "lg:grid-cols-4", }; const XL_COLS: Record = { 1: "xl:grid-cols-1", 2: "xl:grid-cols-2", 3: "xl:grid-cols-3", 4: "xl:grid-cols-4", }; function gridClass(columns: GridColumns): string { return cn( COLS[columns.base ?? 1], columns.md && MD_COLS[columns.md], columns.lg && LG_COLS[columns.lg], columns.xl && XL_COLS[columns.xl], ); } /** One collection as a card grid or a dense row table. */ export function DataView({ items, view, density, getKey, renderCard, columns, listColumns, onRowClick, isRowActive, empty, className, }: { items: T[]; view: PanelViewMode; density?: PanelDensity; getKey: (item: T) => string; renderCard: (item: T) => React.ReactNode; columns: GridColumns; listColumns: DataColumn[]; onRowClick?: (item: T) => void; isRowActive?: (item: T) => boolean; empty: React.ReactNode; className?: string; }) { if (items.length === 0) { return (
{empty}
); } if (view === "grid") { return (
{items.map((item) => (
{renderCard(item)}
))}
); } const template = listColumns.map((c) => c.width).join(" "); const rowHeight = density === "compact" ? "h-7" : "h-9"; return (
{listColumns.map((column) => ( {column.header} ))}
{items.map((item) => { const active = isRowActive?.(item) ?? false; return (
onRowClick(item) : undefined} className={cn( "grid items-center gap-x-2 border-b border-border/50 px-3 text-xs transition-colors last:border-0", rowHeight, onRowClick && "cursor-pointer", active ? "bg-accent-brand/10" : "hover:bg-muted/40", )} style={{ gridTemplateColumns: template }} > {listColumns.map((column) => (
{column.cell(item)}
))}
); })}
); }