import { createMemo, createSignal, For, Show, splitProps } from "solid-js"; import type { Accessor, Component, JSX } from "solid-js"; export type Column = { key: keyof T | string; header: string; sortable?: boolean; render?: (row: T, index: number) => JSX.Element; width?: string; }; type DataTableProps = { columns: Column[]; data: T[]; getRowId: (row: T) => string; selectable?: boolean; expandable?: (row: T) => JSX.Element | null; onSelectionChange?: (selectedIds: string[]) => void; class?: string; }; type SortDirection = "asc" | "desc" | null; const SortIcon: Component<{ direction: SortDirection }> = (props) => ( ); export function DataTable(props: DataTableProps): JSX.Element { const [local, _others] = splitProps(props, [ "columns", "data", "getRowId", "selectable", "expandable", "onSelectionChange", "class", ]); const [sortKey, setSortKey] = createSignal(null); const [sortDir, setSortDir] = createSignal(null); const [selected, setSelected] = createSignal>(new Set()); const [expanded, setExpanded] = createSignal>(new Set()); const sortedData: Accessor = createMemo(() => { const key = sortKey(); const dir = sortDir(); if (!key || !dir) return local.data; return [...local.data].sort((a, b) => { const aVal = (a as Record)[key]; const bVal = (b as Record)[key]; if (aVal === bVal) return 0; if (aVal == null) return 1; if (bVal == null) return -1; const cmp = aVal < bVal ? -1 : 1; return dir === "asc" ? cmp : -cmp; }); }); const handleSort = (key: string) => { if (sortKey() === key) { setSortDir((d) => (d === "asc" ? "desc" : d === "desc" ? null : "asc")); if (sortDir() === null) setSortKey(null); } else { setSortKey(key); setSortDir("asc"); } }; const toggleSelect = (id: string) => { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); local.onSelectionChange?.([...next]); return next; }); }; const toggleSelectAll = () => { if (selected().size === local.data.length) { setSelected(new Set()); local.onSelectionChange?.([]); } else { const all = new Set(local.data.map(local.getRowId)); setSelected(all); local.onSelectionChange?.([...all]); } }; const toggleExpand = (id: string) => { setExpanded((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const getCellValue = (row: T, col: Column, index: number): JSX.Element => { if (col.render) return col.render(row, index); const value = (row as Record)[col.key as string]; return <>{value != null ? String(value) : ""}; }; return (
{(col) => ( )} {(row, index) => { const id = local.getRowId(row); const isExpanded = () => expanded().has(id); const expandedContent = () => local.expandable?.(row); return ( <> {(col) => } ); }}
0} onChange={toggleSelectAll} class="w-4 h-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-blue-500" /> col.sortable && handleSort(col.key as string)}> {col.header}
toggleSelect(id)} class="w-4 h-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-blue-500" /> {getCellValue(row, col, index())}
{expandedContent()}
); }