129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@repo/shadcn-ui/components/table";
|
|
import type { Table as TableModel } from "@tanstack/react-table";
|
|
|
|
import type { EntityColumn } from "./entity-list";
|
|
|
|
export function EntityTableContent<T extends { id: string }>({
|
|
table,
|
|
columns,
|
|
title,
|
|
loading,
|
|
unavailable,
|
|
message,
|
|
rowClassName,
|
|
onSort,
|
|
}: {
|
|
table: TableModel<T>;
|
|
columns: readonly EntityColumn<T>[];
|
|
title: string;
|
|
loading: boolean;
|
|
unavailable: boolean;
|
|
message: string;
|
|
rowClassName: ((item: T) => string) | undefined;
|
|
onSort: () => void;
|
|
}) {
|
|
const visibleColumns = [
|
|
...table.getCenterVisibleLeafColumns(),
|
|
...table.getRightVisibleLeafColumns(),
|
|
];
|
|
const total = table.getFilteredRowModel().rows.length;
|
|
|
|
return (
|
|
<Table aria-label={title}>
|
|
<TableHeader>
|
|
<TableRow>
|
|
{visibleColumns.map((column) => {
|
|
const direction = column.getIsSorted();
|
|
|
|
return (
|
|
<TableHead
|
|
aria-sort={
|
|
direction === "asc"
|
|
? "ascending"
|
|
: direction === "desc"
|
|
? "descending"
|
|
: undefined
|
|
}
|
|
className={entityColumnClass(column.getIsPinned() === "right", true)}
|
|
data-pinned={column.getIsPinned() || undefined}
|
|
key={column.id}
|
|
scope="col"
|
|
>
|
|
{column.getCanSort() ? (
|
|
<button
|
|
className="inline-flex items-center gap-2 rounded py-1 text-left focus-visible:outline-2 focus-visible:outline-ring"
|
|
onClick={() => {
|
|
column.toggleSorting();
|
|
onSort();
|
|
}}
|
|
type="button"
|
|
>
|
|
{String(column.columnDef.header)}
|
|
<span aria-hidden="true">
|
|
{direction === "asc" ? "↑" : direction === "desc" ? "↓" : "↕"}
|
|
</span>
|
|
</button>
|
|
) : (
|
|
String(column.columnDef.header)
|
|
)}
|
|
</TableHead>
|
|
);
|
|
})}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{loading || unavailable ? (
|
|
<TableRow>
|
|
<TableCell colSpan={visibleColumns.length}>
|
|
<span className="text-muted-foreground">{message}</span>
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
table.getRowModel().rows.map((row) => (
|
|
<TableRow
|
|
className={`group/entity-row ${rowClassName?.(row.original) ?? ""}`}
|
|
key={row.id}
|
|
>
|
|
{visibleColumns.map((column) => (
|
|
<TableCell
|
|
className={entityColumnClass(column.getIsPinned() === "right", false)}
|
|
data-pinned={column.getIsPinned() || undefined}
|
|
key={column.id}
|
|
>
|
|
{columns.find((item) => item.id === column.id)?.render(row.original)}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))
|
|
)}
|
|
{!(loading || unavailable || total) && (
|
|
<TableRow>
|
|
<TableCell className="p-6 text-muted-foreground" colSpan={visibleColumns.length}>
|
|
{message}
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
);
|
|
}
|
|
|
|
function entityColumnClass(pinned: boolean, header: boolean) {
|
|
if (pinned) {
|
|
return header
|
|
? "sticky right-0 z-20 w-20 min-w-20 bg-card pr-4 text-right"
|
|
: "sticky right-0 z-10 h-10 w-20 min-w-20 bg-card px-3 py-3 pr-4 text-right whitespace-nowrap group-hover/entity-row:bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))] group-has-aria-expanded/entity-row:bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))]";
|
|
}
|
|
|
|
return header
|
|
? "first:min-w-40 bg-background font-medium"
|
|
: "h-14 first:min-w-40 max-w-lg whitespace-normal break-words px-3 py-3";
|
|
}
|