Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| import { | |
| type ColumnFiltersState, | |
| type ColumnDef, | |
| flexRender, | |
| getCoreRowModel, | |
| getFilteredRowModel, | |
| getSortedRowModel, | |
| type SortingState, | |
| useReactTable, | |
| } from "@tanstack/react-table"; | |
| import { ArrowDown, ArrowUp, ChevronsUpDown, X } from "lucide-react"; | |
| import { useState } from "react"; | |
| import { cn } from "../lib"; | |
| import { Empty } from "../ui"; | |
| function columnClass(meta: unknown): string | undefined { | |
| if (!meta || typeof meta !== "object" || !("className" in meta)) return undefined; | |
| const className = meta.className; | |
| return typeof className === "string" ? className : undefined; | |
| } | |
| export function DataTable<T>({ | |
| columns, | |
| data, | |
| empty = "No records found", | |
| rowClassName, | |
| }: { | |
| columns: ColumnDef<T>[]; | |
| data: T[]; | |
| empty?: string; | |
| rowClassName?(row: T): string | undefined; | |
| }) { | |
| const [sorting, setSorting] = useState<SortingState>([]); | |
| const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); | |
| const table = useReactTable({ | |
| data, | |
| columns, | |
| defaultColumn: { filterFn: "includesString" }, | |
| state: { columnFilters, sorting }, | |
| onColumnFiltersChange: setColumnFilters, | |
| onSortingChange: setSorting, | |
| getCoreRowModel: getCoreRowModel(), | |
| getFilteredRowModel: getFilteredRowModel(), | |
| getSortedRowModel: getSortedRowModel(), | |
| }); | |
| if (data.length === 0) return <Empty>{empty}</Empty>; | |
| const rows = table.getRowModel().rows; | |
| return ( | |
| <div className="min-w-0 rounded-xl border border-slate-800"> | |
| <div className="flex min-h-9 items-center justify-end gap-3 border-b border-slate-800 bg-slate-950/80 px-3 py-2 text-xs text-slate-500"> | |
| <span aria-live="polite"> | |
| {rows.length === data.length | |
| ? `${data.length} ${data.length === 1 ? "row" : "rows"}` | |
| : `${rows.length} of ${data.length} rows`} | |
| </span> | |
| {columnFilters.length > 0 ? ( | |
| <button | |
| className="inline-flex items-center gap-1 text-slate-300 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400" | |
| type="button" | |
| onClick={() => setColumnFilters([])} | |
| > | |
| <X size={13} aria-hidden="true" /> | |
| Clear filters | |
| </button> | |
| ) : null} | |
| </div> | |
| <div className="max-h-[70vh] overflow-auto"> | |
| <table className="w-full table-fixed border-collapse text-left text-sm"> | |
| <thead className="sticky top-0 z-20 bg-slate-900 text-xs uppercase tracking-wider text-slate-400 shadow-[0_1px_0_rgb(30_41_59)]"> | |
| {table.getHeaderGroups().map((group) => ( | |
| <tr key={group.id}> | |
| {group.headers.map((header) => ( | |
| <th | |
| className={cn( | |
| "px-3 py-2 font-medium", | |
| columnClass(header.column.columnDef.meta), | |
| )} | |
| key={header.id} | |
| > | |
| {header.isPlaceholder ? null : ( | |
| <div className="space-y-2"> | |
| <button | |
| className="inline-flex max-w-full items-center gap-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400" | |
| type="button" | |
| onClick={header.column.getToggleSortingHandler()} | |
| > | |
| {flexRender( | |
| header.column.columnDef.header, | |
| header.getContext(), | |
| )} | |
| {header.column.getIsSorted() === "asc" ? ( | |
| <ArrowUp size={13} /> | |
| ) : header.column.getIsSorted() === "desc" ? ( | |
| <ArrowDown size={13} /> | |
| ) : header.column.getCanSort() ? ( | |
| <ChevronsUpDown size={13} /> | |
| ) : null} | |
| </button> | |
| {header.column.getCanFilter() ? ( | |
| <input | |
| aria-label={`Filter ${header.column.id.replaceAll("_", " ")}`} | |
| className="block h-7 w-full rounded border border-slate-700 bg-slate-950 px-2 text-xs font-normal normal-case tracking-normal text-slate-200 placeholder:text-slate-600 focus:border-cyan-500 focus:outline-none" | |
| placeholder="Filter…" | |
| type="search" | |
| value={String(header.column.getFilterValue() ?? "")} | |
| onChange={(event) => | |
| header.column.setFilterValue(event.target.value) | |
| } | |
| onClick={(event) => event.stopPropagation()} | |
| /> | |
| ) : null} | |
| </div> | |
| )} | |
| </th> | |
| ))} | |
| </tr> | |
| ))} | |
| </thead> | |
| <tbody> | |
| {rows.length > 0 ? ( | |
| rows.map((row) => ( | |
| <tr | |
| className={cn( | |
| "border-t border-slate-800/80 hover:bg-slate-900/60", | |
| rowClassName?.(row.original), | |
| )} | |
| key={row.id} | |
| > | |
| {row.getVisibleCells().map((cell) => ( | |
| <td | |
| className={cn( | |
| "overflow-hidden px-3 py-3 align-top text-slate-200", | |
| columnClass(cell.column.columnDef.meta), | |
| )} | |
| key={cell.id} | |
| > | |
| {flexRender(cell.column.columnDef.cell, cell.getContext())} | |
| </td> | |
| ))} | |
| </tr> | |
| )) | |
| ) : ( | |
| <tr> | |
| <td | |
| className="px-3 py-8 text-center text-sm text-slate-500" | |
| colSpan={table.getVisibleLeafColumns().length} | |
| > | |
| No rows match these filters | |
| </td> | |
| </tr> | |
| )} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| ); | |
| } | |