forked from forthfate/openorbit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdata-table.tsx
More file actions
79 lines (79 loc) · 3.1 KB
/
Copy pathdata-table.tsx
File metadata and controls
79 lines (79 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
import type { ReactNode } from "react";
import { useMemo, useState } from "react";
export type Column<Row> = {
id: string;
header: ReactNode;
render: (row: Row) => ReactNode;
sortValue?: (row: Row) => string | number | boolean | null | undefined;
};
import { locales, resolveLocale } from "../../locales";
export function DataTable<Row extends { id: string }>({
columns,
rows,
empty,
className = "",
gridTemplateColumns,
onRowClick,
}: {
columns: Column<Row>[];
rows: Row[];
empty?: string;
className?: string;
gridTemplateColumns?: string;
onRowClick?: (row: Row) => void;
}) {
const template =
gridTemplateColumns ?? `repeat(${columns.length},minmax(120px,1fr))`,
locale = resolveLocale(localStorage.getItem("orbit.locale")),
[sort, setSort] = useState<{ id: string; direction: "asc" | "desc" } | null>(null),
sortedRows = useMemo(() => {
if (!sort) return rows;
const column = columns.find((item) => item.id === sort.id);
if (!column?.sortValue) return rows;
return [...rows].sort((left, right) => {
const a = column.sortValue!(left), b = column.sortValue!(right);
if (a == null) return b == null ? 0 : 1;
if (b == null) return -1;
const comparison = typeof a === "number" && typeof b === "number"
? a - b
: String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: "base" });
return sort.direction === "asc" ? comparison : -comparison;
});
}, [columns, rows, sort]);
return (
<div className={`table ${className}`}>
<div className="tr th" style={{ gridTemplateColumns: template }}>
{columns.map((c) => {
const active = sort?.id === c.id;
if (!c.sortValue) return <span key={c.id}>{c.header}</span>;
const Icon = active ? (sort.direction === "asc" ? ChevronUp : ChevronDown) : ChevronsUpDown;
return <span key={c.id}><button className="table-sort" type="button" onClick={() => setSort(current => current?.id === c.id ? { id: c.id, direction: current.direction === "asc" ? "desc" : "asc" } : { id: c.id, direction: "asc" })} aria-label={`Sort by ${String(c.header)}`} aria-sort={active ? (sort.direction === "asc" ? "ascending" : "descending") : "none"}>{c.header}<Icon size={13} aria-hidden="true" /></button></span>;
})}
</div>
{sortedRows.length ? (
sortedRows.map((row) => (
<div
className={`tr${onRowClick ? " tr--interactive" : ""}`}
style={{ gridTemplateColumns: template }}
key={row.id}
onClick={(event) => {
if (
!(event.target as HTMLElement).closest(
"button,input,select,textarea",
)
)
onRowClick?.(row);
}}
>
{columns.map((c) => (
<span key={c.id}>{c.render(row)}</span>
))}
</div>
))
) : (
<div className="empty">{empty ?? locales[locale].ui.emptyEntries}</div>
)}
</div>
);
}