@nexub/react 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accessibility.json +1 -1
- package/dist/components/DataTable/DataTable.d.ts +51 -2
- package/dist/components/DataTable/DataTable.js +391 -55
- package/dist/components/DataTable/index.d.ts +1 -1
- package/dist/components/DataTableVirtual/DataTableVirtual.d.ts +19 -0
- package/dist/components/DataTableVirtual/DataTableVirtual.js +37 -0
- package/dist/components/DataTableVirtual/index.d.ts +2 -0
- package/dist/components/DataTableVirtual/index.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +13 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.
|
|
2
|
+
"version": "5.4.0",
|
|
3
3
|
"standard": "WCAG 2.2 Level AA",
|
|
4
4
|
"method": {
|
|
5
5
|
"automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark on every pull request; a new violation fails the build.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type ColumnDef, type RowData } from '@tanstack/react-table';
|
|
2
|
+
import { type ColumnDef, type RowSelectionState, type RowData, type VisibilityState, type ColumnSizingState, type ColumnOrderState, type ColumnPinningState } from '@tanstack/react-table';
|
|
3
3
|
declare module '@tanstack/react-table' {
|
|
4
4
|
interface ColumnMeta<TData extends RowData, TValue> {
|
|
5
5
|
/** Horizontal alignment of the header and cells. Use 'end' for numbers. @default 'start' */
|
|
@@ -8,6 +8,19 @@ declare module '@tanstack/react-table' {
|
|
|
8
8
|
}
|
|
9
9
|
/** A DataTable column: TanStack's `ColumnDef`, with `meta.align` for numeric columns. */
|
|
10
10
|
export type DataTableColumn<TData> = ColumnDef<TData, unknown>;
|
|
11
|
+
export type DataTableDensity = 'comfortable' | 'compact';
|
|
12
|
+
/** Which rows to render, and the space to leave above and below them. */
|
|
13
|
+
export interface DataTableRowWindow {
|
|
14
|
+
indexes: number[];
|
|
15
|
+
before: number;
|
|
16
|
+
after: number;
|
|
17
|
+
/** Ref for each rendered row, to measure its real height. */
|
|
18
|
+
measure?: (el: HTMLElement | null) => void;
|
|
19
|
+
/** True when rows are windowed: rows get aria-rowindex, the table aria-rowcount. */
|
|
20
|
+
virtual?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** A hook choosing the rendered rows. DataTableVirtual passes one; the default renders all. */
|
|
23
|
+
export type DataTableRowWindowHook = (count: number, getScrollElement: () => HTMLElement | null) => DataTableRowWindow;
|
|
11
24
|
export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivElement> {
|
|
12
25
|
columns: DataTableColumn<TData>[];
|
|
13
26
|
/** Names the table for assistive tech (and is shown below it unless `captionHidden`). */
|
|
@@ -17,8 +30,44 @@ export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivEleme
|
|
|
17
30
|
data: TData[];
|
|
18
31
|
enableSorting?: boolean;
|
|
19
32
|
enableRowSelection?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* With `enableRowSelection`: a checkbox column with select-page in its
|
|
35
|
+
* header, and "Select all N" across pages. On by default from 6.0.
|
|
36
|
+
*/
|
|
37
|
+
selectionColumn?: boolean;
|
|
38
|
+
/** Controlled selection, keyed by row id (see `getRowId`). */
|
|
39
|
+
rowSelection?: RowSelectionState;
|
|
40
|
+
onRowSelectionChange?: (selection: RowSelectionState) => void;
|
|
41
|
+
/** Stable row ids for selection; defaults to the row index. */
|
|
42
|
+
getRowId?: (row: TData, index: number) => string;
|
|
43
|
+
/** A floating bar while rows are selected: render your actions for the selected rows. */
|
|
44
|
+
bulkActions?: (rows: TData[], clear: () => void) => React.ReactNode;
|
|
45
|
+
density?: DataTableDensity;
|
|
46
|
+
defaultDensity?: DataTableDensity;
|
|
47
|
+
onDensityChange?: (density: DataTableDensity) => void;
|
|
48
|
+
columnVisibility?: VisibilityState;
|
|
49
|
+
onColumnVisibilityChange?: (visibility: VisibilityState) => void;
|
|
50
|
+
/** A "View" menu above the table: density and which columns show. */
|
|
51
|
+
viewOptions?: boolean;
|
|
52
|
+
/** Content at the start of the row above the table (filters, a title). */
|
|
53
|
+
toolbar?: React.ReactNode;
|
|
54
|
+
/** Drag a header's edge, or focus it and press Arrow keys, to resize columns. */
|
|
55
|
+
enableColumnResizing?: boolean;
|
|
56
|
+
columnSizing?: ColumnSizingState;
|
|
57
|
+
onColumnSizingChange?: (sizing: ColumnSizingState) => void;
|
|
58
|
+
/** Drag headers to reorder columns, or press Alt+Arrow on a focused header. */
|
|
59
|
+
enableColumnReordering?: boolean;
|
|
60
|
+
columnOrder?: ColumnOrderState;
|
|
61
|
+
onColumnOrderChange?: (order: ColumnOrderState) => void;
|
|
62
|
+
/** Columns that stay in view while the rest scroll sideways: `{ left: ['name'], right: ['actions'] }`. */
|
|
63
|
+
columnPinning?: ColumnPinningState;
|
|
64
|
+
onColumnPinningChange?: (pinning: ColumnPinningState) => void;
|
|
65
|
+
/** Cap the table's height. Rows scroll and the header stays in view. */
|
|
66
|
+
maxHeight?: number | string;
|
|
67
|
+
/** Rows to render; set by DataTableVirtual. */
|
|
68
|
+
rowWindow?: DataTableRowWindowHook;
|
|
20
69
|
pagination?: {
|
|
21
70
|
pageSize?: number;
|
|
22
71
|
};
|
|
23
72
|
}
|
|
24
|
-
export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, pagination, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
73
|
+
export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, selectionColumn, rowSelection: rowSelectionProp, onRowSelectionChange, getRowId, bulkActions, density: densityProp, defaultDensity, onDensityChange, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, viewOptions, toolbar, enableColumnResizing, columnSizing: columnSizingProp, onColumnSizingChange, enableColumnReordering, columnOrder: columnOrderProp, onColumnOrderChange, columnPinning: columnPinningProp, onColumnPinningChange, maxHeight, rowWindow, pagination, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import {
|
|
5
5
|
useReactTable,
|
|
@@ -9,11 +9,17 @@ import {
|
|
|
9
9
|
getPaginationRowModel,
|
|
10
10
|
flexRender
|
|
11
11
|
} from "@tanstack/react-table";
|
|
12
|
-
import { ArrowUp, ArrowDown, ChevronsUpDown } from "@nexub/icons";
|
|
12
|
+
import { ArrowUp, ArrowDown, ChevronsUpDown, SlidersHorizontal, X } from "@nexub/icons";
|
|
13
13
|
import { cn } from "../../utils/cn.js";
|
|
14
14
|
import { disabledContainer, pressable } from "../../utils/control.js";
|
|
15
|
-
import {
|
|
15
|
+
import { floatingSurface } from "../../utils/surface.js";
|
|
16
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
17
|
+
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
18
|
+
import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
16
19
|
import { Table } from "../Table/Table.js";
|
|
20
|
+
import { Checkbox } from "../Checkbox/Checkbox.js";
|
|
21
|
+
import { Button } from "../Button/Button.js";
|
|
22
|
+
import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
17
23
|
const alignText = {
|
|
18
24
|
start: "text-start",
|
|
19
25
|
end: "text-end",
|
|
@@ -47,7 +53,7 @@ const TableRow = React.forwardRef(
|
|
|
47
53
|
{
|
|
48
54
|
ref,
|
|
49
55
|
className: cn(
|
|
50
|
-
"border-b border-[var(--color-outline-variant)] transition-colors hover:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)] data-[state=selected]:bg-[var(--color-
|
|
56
|
+
"border-b border-[var(--color-outline-variant)] transition-colors hover:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)] data-[state=selected]:bg-[color-mix(in_srgb,var(--color-primary)_10%,transparent)]",
|
|
51
57
|
className
|
|
52
58
|
),
|
|
53
59
|
...props
|
|
@@ -79,11 +85,52 @@ const TableCell = React.forwardRef(({ className, ...props }, ref) => /* @__PURE_
|
|
|
79
85
|
}
|
|
80
86
|
));
|
|
81
87
|
TableCell.displayName = "DataTable.TableCell";
|
|
88
|
+
function Spacer({ height }) {
|
|
89
|
+
return /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", className: "border-0", children: /* @__PURE__ */ jsx(
|
|
90
|
+
"td",
|
|
91
|
+
{
|
|
92
|
+
ref: (el) => {
|
|
93
|
+
if (el) el.style.height = px(height);
|
|
94
|
+
},
|
|
95
|
+
className: "p-0"
|
|
96
|
+
}
|
|
97
|
+
) });
|
|
98
|
+
}
|
|
99
|
+
const useAllRows = (count) => ({
|
|
100
|
+
indexes: Array.from({ length: count }, (_, i) => i),
|
|
101
|
+
before: 0,
|
|
102
|
+
after: 0
|
|
103
|
+
});
|
|
104
|
+
const px = (n) => typeof n === "number" ? `${n}px` : n;
|
|
105
|
+
const apply = (u, prev) => typeof u === "function" ? u(prev) : u;
|
|
106
|
+
const columnLabel = (c) => typeof c.columnDef.header === "string" ? c.columnDef.header : c.id;
|
|
82
107
|
function DataTable({
|
|
83
108
|
columns,
|
|
84
109
|
data,
|
|
85
110
|
enableSorting = false,
|
|
86
111
|
enableRowSelection = false,
|
|
112
|
+
selectionColumn = false,
|
|
113
|
+
rowSelection: rowSelectionProp,
|
|
114
|
+
onRowSelectionChange,
|
|
115
|
+
getRowId,
|
|
116
|
+
bulkActions,
|
|
117
|
+
density: densityProp,
|
|
118
|
+
defaultDensity = "comfortable",
|
|
119
|
+
onDensityChange,
|
|
120
|
+
columnVisibility: columnVisibilityProp,
|
|
121
|
+
onColumnVisibilityChange,
|
|
122
|
+
viewOptions = false,
|
|
123
|
+
toolbar,
|
|
124
|
+
enableColumnResizing = false,
|
|
125
|
+
columnSizing: columnSizingProp,
|
|
126
|
+
onColumnSizingChange,
|
|
127
|
+
enableColumnReordering = false,
|
|
128
|
+
columnOrder: columnOrderProp,
|
|
129
|
+
onColumnOrderChange,
|
|
130
|
+
columnPinning: columnPinningProp,
|
|
131
|
+
onColumnPinningChange,
|
|
132
|
+
maxHeight,
|
|
133
|
+
rowWindow,
|
|
87
134
|
pagination,
|
|
88
135
|
caption,
|
|
89
136
|
captionHidden = false,
|
|
@@ -93,26 +140,115 @@ function DataTable({
|
|
|
93
140
|
...props
|
|
94
141
|
}) {
|
|
95
142
|
const messages = useNexubMessages();
|
|
143
|
+
const { locale, dir } = useLocale();
|
|
96
144
|
const [sorting, setSorting] = React.useState([]);
|
|
97
|
-
const [rowSelection, setRowSelection] =
|
|
145
|
+
const [rowSelection = {}, setRowSelection] = useControllableState({
|
|
146
|
+
value: rowSelectionProp,
|
|
147
|
+
defaultValue: {},
|
|
148
|
+
onChange: onRowSelectionChange
|
|
149
|
+
});
|
|
150
|
+
const [density = "comfortable", setDensity] = useControllableState({
|
|
151
|
+
value: densityProp,
|
|
152
|
+
defaultValue: defaultDensity,
|
|
153
|
+
onChange: onDensityChange
|
|
154
|
+
});
|
|
155
|
+
const [columnVisibility = {}, setColumnVisibility] = useControllableState({
|
|
156
|
+
value: columnVisibilityProp,
|
|
157
|
+
defaultValue: {},
|
|
158
|
+
onChange: onColumnVisibilityChange
|
|
159
|
+
});
|
|
160
|
+
const [columnSizing = {}, setColumnSizing] = useControllableState({
|
|
161
|
+
value: columnSizingProp,
|
|
162
|
+
defaultValue: {},
|
|
163
|
+
onChange: onColumnSizingChange
|
|
164
|
+
});
|
|
165
|
+
const [columnOrder = [], setColumnOrder] = useControllableState({
|
|
166
|
+
value: columnOrderProp,
|
|
167
|
+
defaultValue: [],
|
|
168
|
+
onChange: onColumnOrderChange
|
|
169
|
+
});
|
|
170
|
+
const [pinningIn = {}, setColumnPinning] = useControllableState({
|
|
171
|
+
value: columnPinningProp,
|
|
172
|
+
defaultValue: {},
|
|
173
|
+
onChange: onColumnPinningChange
|
|
174
|
+
});
|
|
175
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
176
|
+
const tableRef = React.useRef(null);
|
|
177
|
+
const dragged = React.useRef(null);
|
|
98
178
|
const [paginationState, setPaginationState] = React.useState({
|
|
99
179
|
pageIndex: 0,
|
|
100
180
|
pageSize: pagination?.pageSize ?? 20
|
|
101
181
|
});
|
|
182
|
+
const withSelectColumn = enableRowSelection && selectionColumn;
|
|
183
|
+
const columnPinning = React.useMemo(
|
|
184
|
+
() => withSelectColumn && pinningIn.left?.length && !pinningIn.left.includes("__select") ? { ...pinningIn, left: ["__select", ...pinningIn.left] } : pinningIn,
|
|
185
|
+
[withSelectColumn, pinningIn]
|
|
186
|
+
);
|
|
187
|
+
const allColumns = React.useMemo(
|
|
188
|
+
() => withSelectColumn ? [
|
|
189
|
+
{
|
|
190
|
+
id: "__select",
|
|
191
|
+
enableSorting: false,
|
|
192
|
+
enableHiding: false,
|
|
193
|
+
header: ({ table: table2 }) => /* @__PURE__ */ jsx(
|
|
194
|
+
Checkbox,
|
|
195
|
+
{
|
|
196
|
+
"aria-label": messages.selectAllOnPage,
|
|
197
|
+
checked: table2.getIsAllPageRowsSelected() ? true : table2.getIsSomePageRowsSelected() ? "indeterminate" : false,
|
|
198
|
+
onCheckedChange: (v) => table2.toggleAllPageRowsSelected(v === true)
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(
|
|
202
|
+
Checkbox,
|
|
203
|
+
{
|
|
204
|
+
"aria-label": messages.selectRow,
|
|
205
|
+
checked: row.getIsSelected(),
|
|
206
|
+
disabled: !row.getCanSelect(),
|
|
207
|
+
onCheckedChange: (v) => row.toggleSelected(v === true)
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
...columns
|
|
212
|
+
] : columns,
|
|
213
|
+
[withSelectColumn, columns, messages.selectAllOnPage, messages.selectRow]
|
|
214
|
+
);
|
|
102
215
|
const tableState = React.useMemo(
|
|
103
216
|
() => ({
|
|
104
217
|
sorting: enableSorting ? sorting : void 0,
|
|
105
218
|
rowSelection: enableRowSelection ? rowSelection : void 0,
|
|
106
|
-
pagination: pagination ? paginationState : void 0
|
|
219
|
+
pagination: pagination ? paginationState : void 0,
|
|
220
|
+
columnVisibility,
|
|
221
|
+
columnSizing,
|
|
222
|
+
columnOrder,
|
|
223
|
+
columnPinning
|
|
107
224
|
}),
|
|
108
|
-
[
|
|
225
|
+
[
|
|
226
|
+
enableSorting,
|
|
227
|
+
sorting,
|
|
228
|
+
enableRowSelection,
|
|
229
|
+
rowSelection,
|
|
230
|
+
pagination,
|
|
231
|
+
paginationState,
|
|
232
|
+
columnVisibility,
|
|
233
|
+
columnSizing,
|
|
234
|
+
columnOrder,
|
|
235
|
+
columnPinning
|
|
236
|
+
]
|
|
109
237
|
);
|
|
110
238
|
const table = useReactTable({
|
|
111
239
|
data,
|
|
112
|
-
columns,
|
|
240
|
+
columns: allColumns,
|
|
113
241
|
state: tableState,
|
|
242
|
+
getRowId,
|
|
114
243
|
onSortingChange: enableSorting ? setSorting : void 0,
|
|
115
|
-
onRowSelectionChange: enableRowSelection ? setRowSelection : void 0,
|
|
244
|
+
onRowSelectionChange: enableRowSelection ? (u) => setRowSelection(apply(u, rowSelection)) : void 0,
|
|
245
|
+
onColumnVisibilityChange: (u) => setColumnVisibility(apply(u, columnVisibility)),
|
|
246
|
+
onColumnSizingChange: (u) => setColumnSizing(apply(u, columnSizing)),
|
|
247
|
+
onColumnOrderChange: (u) => setColumnOrder(apply(u, columnOrder)),
|
|
248
|
+
onColumnPinningChange: (u) => setColumnPinning(apply(u, columnPinning)),
|
|
249
|
+
enableColumnResizing,
|
|
250
|
+
columnResizeMode: "onChange",
|
|
251
|
+
columnResizeDirection: dir,
|
|
116
252
|
onPaginationChange: pagination ? setPaginationState : void 0,
|
|
117
253
|
getCoreRowModel: getCoreRowModel(),
|
|
118
254
|
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
@@ -121,58 +257,238 @@ function DataTable({
|
|
|
121
257
|
enableRowSelection,
|
|
122
258
|
manualPagination: false
|
|
123
259
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
260
|
+
const compact = density === "compact";
|
|
261
|
+
const selectedRows = enableRowSelection ? table.getSelectedRowModel().rows : [];
|
|
262
|
+
const totalRows = table.getCoreRowModel().rows.length;
|
|
263
|
+
const fmt = (n) => new Intl.NumberFormat(locale).format(n);
|
|
264
|
+
const multiSort = sorting.length > 1;
|
|
265
|
+
const offerAll = withSelectColumn && !!pagination && table.getIsAllPageRowsSelected() && totalRows > table.getRowModel().rows.length;
|
|
266
|
+
const clear = () => table.resetRowSelection(true);
|
|
267
|
+
const rows = table.getRowModel().rows;
|
|
268
|
+
const useRowWindow = rowWindow ?? useAllRows;
|
|
269
|
+
const rowWin = useRowWindow(rows.length, () => tableRef.current?.parentElement ?? null);
|
|
270
|
+
const pinned = (columnPinning.left?.length ?? 0) + (columnPinning.right?.length ?? 0) > 0;
|
|
271
|
+
useIsomorphicLayoutEffect(() => {
|
|
272
|
+
const t = tableRef.current;
|
|
273
|
+
if (!t) return;
|
|
274
|
+
t.style.width = enableColumnResizing ? px(table.getTotalSize()) : "";
|
|
275
|
+
const scroller = t.parentElement;
|
|
276
|
+
if (scroller) scroller.style.maxHeight = maxHeight == null ? "" : px(maxHeight);
|
|
277
|
+
});
|
|
278
|
+
const placeCell = (column) => (el) => {
|
|
279
|
+
if (!el) return;
|
|
280
|
+
el.style.width = enableColumnResizing ? px(column.getSize()) : "";
|
|
281
|
+
const side = column.getIsPinned();
|
|
282
|
+
el.style.insetInlineStart = side === "left" ? px(column.getStart("left")) : "";
|
|
283
|
+
el.style.insetInlineEnd = side === "right" ? px(column.getAfter("right")) : "";
|
|
284
|
+
};
|
|
285
|
+
const pinClass = (column) => {
|
|
286
|
+
const side = column.getIsPinned();
|
|
287
|
+
if (!side) return void 0;
|
|
288
|
+
return cn(
|
|
289
|
+
"sticky z-[1] bg-[var(--color-surface)]",
|
|
290
|
+
"[tr:hover>&]:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,var(--color-surface))]",
|
|
291
|
+
"[tr[data-state=selected]>&]:bg-[color-mix(in_srgb,var(--color-primary)_10%,var(--color-surface))]",
|
|
292
|
+
side === "left" && column.getIsLastColumn("left") && "border-e border-[var(--color-outline-variant)]",
|
|
293
|
+
side === "right" && column.getIsFirstColumn("right") && "border-s border-[var(--color-outline-variant)]"
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
const moveColumn = (id, to) => {
|
|
297
|
+
const ids = table.getAllLeafColumns().map((c) => c.id);
|
|
298
|
+
const order = (columnOrder.length ? columnOrder : ids).filter((c) => c !== "__select" && ids.includes(c));
|
|
299
|
+
for (const c of ids) if (c !== "__select" && !order.includes(c)) order.push(c);
|
|
300
|
+
const from = order.indexOf(id);
|
|
301
|
+
const target = typeof to === "number" ? from + to : order.indexOf(to);
|
|
302
|
+
if (from < 0 || target < 0 || target >= order.length || target === from) return;
|
|
303
|
+
order.splice(from, 1);
|
|
304
|
+
order.splice(target, 0, id);
|
|
305
|
+
setColumnOrder(withSelectColumn ? ["__select", ...order] : order);
|
|
306
|
+
if (typeof to === "number") {
|
|
307
|
+
const col = table.getColumn(id);
|
|
308
|
+
setAnnouncement(messages.columnMoved(col ? columnLabel(col) : id, target + 1, order.length));
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-density": density, ...props, children: [
|
|
312
|
+
(toolbar != null || viewOptions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
313
|
+
toolbar,
|
|
314
|
+
viewOptions && /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
315
|
+
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ms-auto", children: [
|
|
316
|
+
/* @__PURE__ */ jsx(SlidersHorizontal, { size: "sm", "aria-hidden": "true" }),
|
|
317
|
+
messages.view
|
|
318
|
+
] }) }),
|
|
319
|
+
/* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
|
|
320
|
+
/* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.density }),
|
|
321
|
+
/* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, { value: density, onValueChange: (v) => setDensity(v), children: [
|
|
322
|
+
/* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "comfortable", children: messages.comfortable }),
|
|
323
|
+
/* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "compact", children: messages.compact })
|
|
324
|
+
] }),
|
|
325
|
+
/* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
326
|
+
/* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.columns }),
|
|
327
|
+
table.getAllLeafColumns().filter((c) => c.getCanHide()).map((c) => /* @__PURE__ */ jsx(
|
|
328
|
+
DropdownMenu.CheckboxItem,
|
|
329
|
+
{
|
|
330
|
+
checked: c.getIsVisible(),
|
|
331
|
+
onCheckedChange: (v) => c.toggleVisibility(v === true),
|
|
332
|
+
onSelect: (e) => e.preventDefault(),
|
|
333
|
+
children: columnLabel(c)
|
|
334
|
+
},
|
|
335
|
+
c.id
|
|
336
|
+
))
|
|
337
|
+
] })
|
|
338
|
+
] })
|
|
339
|
+
] }),
|
|
340
|
+
offerAll && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-shape-container bg-[var(--color-surface-container-low)] px-4 py-2 text-body-sm", children: table.getIsAllRowsSelected() ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
341
|
+
/* @__PURE__ */ jsx("span", { children: messages.selectedCount(fmt(totalRows)) }),
|
|
342
|
+
/* @__PURE__ */ jsx(Button, { variant: "link", size: "sm", onClick: clear, children: messages.clearSelection })
|
|
343
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
344
|
+
/* @__PURE__ */ jsx("span", { children: messages.selectedCount(fmt(selectedRows.length)) }),
|
|
345
|
+
/* @__PURE__ */ jsx(Button, { variant: "link", size: "sm", onClick: () => table.toggleAllRowsSelected(true), children: messages.selectAllRows(fmt(totalRows)) })
|
|
346
|
+
] }) }),
|
|
347
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: announcement }),
|
|
348
|
+
/* @__PURE__ */ jsxs(
|
|
349
|
+
TableRoot,
|
|
350
|
+
{
|
|
351
|
+
ref: tableRef,
|
|
352
|
+
"aria-label": ariaLabel,
|
|
353
|
+
"aria-labelledby": ariaLabelledBy,
|
|
354
|
+
"aria-rowcount": rowWin.virtual ? rows.length + 1 : void 0,
|
|
355
|
+
className: cn(enableColumnResizing && "table-fixed"),
|
|
356
|
+
children: [
|
|
357
|
+
caption != null && /* @__PURE__ */ jsx(Table.Caption, { className: cn(captionHidden && "sr-only"), children: caption }),
|
|
358
|
+
/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: headerGroup.headers.map((header) => {
|
|
359
|
+
const align = header.column.columnDef.meta?.align ?? "start";
|
|
360
|
+
const col = header.column;
|
|
361
|
+
const reorderable = enableColumnReordering && col.id !== "__select" && !col.getIsPinned();
|
|
362
|
+
const sortable = enableSorting && col.getCanSort();
|
|
363
|
+
return /* @__PURE__ */ jsxs(
|
|
364
|
+
TableHead,
|
|
136
365
|
{
|
|
137
|
-
|
|
138
|
-
onClick: header.column.getToggleSortingHandler(),
|
|
366
|
+
ref: placeCell(col),
|
|
139
367
|
className: cn(
|
|
140
|
-
|
|
141
|
-
|
|
368
|
+
alignText[align],
|
|
369
|
+
compact && "h-8",
|
|
370
|
+
col.id === "__select" && "w-10",
|
|
371
|
+
// Anchors the resize handle; sticky (below) wins when set and anchors it too.
|
|
372
|
+
(enableColumnResizing || reorderable) && "relative",
|
|
373
|
+
maxHeight != null && "sticky top-0 z-[2] bg-[var(--color-surface)]",
|
|
374
|
+
pinClass(col),
|
|
375
|
+
maxHeight != null && col.getIsPinned() && "z-[3] [tr:hover>&]:bg-[var(--color-surface)]",
|
|
376
|
+
reorderable && "cursor-grab"
|
|
142
377
|
),
|
|
378
|
+
tabIndex: reorderable && !sortable ? 0 : void 0,
|
|
379
|
+
draggable: reorderable || void 0,
|
|
380
|
+
onDragStart: reorderable ? () => dragged.current = col.id : void 0,
|
|
381
|
+
onDragOver: reorderable ? (e) => e.preventDefault() : void 0,
|
|
382
|
+
onDrop: reorderable ? (e) => {
|
|
383
|
+
e.preventDefault();
|
|
384
|
+
if (dragged.current) moveColumn(dragged.current, col.id);
|
|
385
|
+
dragged.current = null;
|
|
386
|
+
} : void 0,
|
|
387
|
+
onKeyDown: reorderable ? (e) => {
|
|
388
|
+
if (!e.altKey || e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
const forward = e.key === "ArrowRight" === (dir !== "rtl");
|
|
391
|
+
moveColumn(col.id, forward ? 1 : -1);
|
|
392
|
+
} : void 0,
|
|
393
|
+
"aria-sort": enableSorting && header.column.getCanSort() ? header.column.getIsSorted() === "asc" ? "ascending" : header.column.getIsSorted() === "desc" ? "descending" : "none" : void 0,
|
|
143
394
|
children: [
|
|
144
|
-
|
|
145
|
-
|
|
395
|
+
header.isPlaceholder ? null : enableSorting && header.column.getCanSort() ? /* @__PURE__ */ jsxs(
|
|
396
|
+
"button",
|
|
397
|
+
{
|
|
398
|
+
type: "button",
|
|
399
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
400
|
+
className: cn(
|
|
401
|
+
"flex w-full items-center gap-1 hover:text-[var(--color-on-surface)]",
|
|
402
|
+
alignJustify[align]
|
|
403
|
+
),
|
|
404
|
+
children: [
|
|
405
|
+
flexRender(header.column.columnDef.header, header.getContext()),
|
|
406
|
+
multiSort && header.column.getIsSorted() && /* @__PURE__ */ jsxs("span", { className: "text-[10px] tabular-nums leading-none", children: [
|
|
407
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: header.column.getSortIndex() + 1 }),
|
|
408
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: messages.sortOrder(header.column.getSortIndex() + 1) })
|
|
409
|
+
] }),
|
|
410
|
+
header.column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { size: "xs", "aria-hidden": "true" }) : header.column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { size: "xs", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { size: "xs", className: "opacity-40", "aria-hidden": "true" })
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
) : flexRender(header.column.columnDef.header, header.getContext()),
|
|
414
|
+
enableColumnResizing && col.getCanResize() && /* @__PURE__ */ jsx(
|
|
415
|
+
"div",
|
|
416
|
+
{
|
|
417
|
+
role: "separator",
|
|
418
|
+
"aria-orientation": "vertical",
|
|
419
|
+
"aria-label": messages.resizeColumn(columnLabel(col)),
|
|
420
|
+
"aria-valuenow": header.getSize(),
|
|
421
|
+
"aria-valuemin": col.columnDef.minSize ?? 20,
|
|
422
|
+
"aria-valuemax": col.columnDef.maxSize ?? 1e3,
|
|
423
|
+
tabIndex: 0,
|
|
424
|
+
draggable: false,
|
|
425
|
+
onMouseDown: header.getResizeHandler(),
|
|
426
|
+
onTouchStart: header.getResizeHandler(),
|
|
427
|
+
onDoubleClick: () => col.resetSize(),
|
|
428
|
+
onKeyDown: (e) => {
|
|
429
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
e.stopPropagation();
|
|
432
|
+
const grow = e.key === "ArrowRight" === (dir !== "rtl");
|
|
433
|
+
const min = col.columnDef.minSize ?? 20;
|
|
434
|
+
const max = col.columnDef.maxSize ?? 1e3;
|
|
435
|
+
const size = Math.min(max, Math.max(min, header.getSize() + (grow ? 16 : -16)));
|
|
436
|
+
table.setColumnSizing((s) => ({ ...s, [col.id]: size }));
|
|
437
|
+
},
|
|
438
|
+
className: cn(
|
|
439
|
+
"absolute inset-y-0 end-0 z-[1] w-2 cursor-col-resize touch-none select-none outline-none",
|
|
440
|
+
"after:absolute after:inset-y-2 after:end-0 after:w-px after:bg-[var(--color-outline-variant)]",
|
|
441
|
+
"hover:after:w-0.5 hover:after:bg-[var(--color-primary)] focus-visible:after:w-0.5 focus-visible:after:bg-[var(--color-primary)]",
|
|
442
|
+
col.getIsResizing() && "after:w-0.5 after:bg-[var(--color-primary)]"
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
)
|
|
146
446
|
]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
447
|
+
},
|
|
448
|
+
header.id
|
|
449
|
+
);
|
|
450
|
+
}) }, headerGroup.id)) }),
|
|
451
|
+
/* @__PURE__ */ jsx(TableBody, { children: rows.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
452
|
+
rowWin.before > 0 && /* @__PURE__ */ jsx(Spacer, { height: rowWin.before }),
|
|
453
|
+
rowWin.indexes.map((i) => {
|
|
454
|
+
const row = rows[i];
|
|
455
|
+
return /* @__PURE__ */ jsx(
|
|
456
|
+
TableRow,
|
|
457
|
+
{
|
|
458
|
+
ref: rowWin.measure,
|
|
459
|
+
"data-index": i,
|
|
460
|
+
"aria-rowindex": rowWin.virtual ? i + 2 : void 0,
|
|
461
|
+
"data-state": enableRowSelection && row.getIsSelected() ? "selected" : void 0,
|
|
462
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(
|
|
463
|
+
TableCell,
|
|
464
|
+
{
|
|
465
|
+
ref: pinned || enableColumnResizing ? placeCell(cell.column) : void 0,
|
|
466
|
+
className: cn(
|
|
467
|
+
alignText[cell.column.columnDef.meta?.align ?? "start"],
|
|
468
|
+
compact && "py-1.5",
|
|
469
|
+
pinClass(cell.column),
|
|
470
|
+
enableColumnResizing && "truncate"
|
|
471
|
+
),
|
|
472
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
473
|
+
},
|
|
474
|
+
cell.id
|
|
475
|
+
))
|
|
476
|
+
},
|
|
477
|
+
row.id
|
|
478
|
+
);
|
|
479
|
+
}),
|
|
480
|
+
rowWin.after > 0 && /* @__PURE__ */ jsx(Spacer, { height: rowWin.after })
|
|
481
|
+
] }) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
|
|
158
482
|
TableCell,
|
|
159
483
|
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
))
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
TableCell,
|
|
169
|
-
{
|
|
170
|
-
colSpan: columns.length,
|
|
171
|
-
className: "h-24 text-center text-[var(--color-on-surface-muted)]",
|
|
172
|
-
children: messages.noResults
|
|
173
|
-
}
|
|
174
|
-
) }) })
|
|
175
|
-
] }),
|
|
484
|
+
colSpan: table.getVisibleLeafColumns().length,
|
|
485
|
+
className: "h-24 text-center text-[var(--color-on-surface-muted)]",
|
|
486
|
+
children: messages.noResults
|
|
487
|
+
}
|
|
488
|
+
) }) })
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
),
|
|
176
492
|
pagination && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 text-sm text-[var(--color-on-surface-muted)]", children: [
|
|
177
493
|
/* @__PURE__ */ jsx("span", { children: messages.pageOf(table.getState().pagination.pageIndex + 1, table.getPageCount()) }),
|
|
178
494
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -197,7 +513,27 @@ function DataTable({
|
|
|
197
513
|
}
|
|
198
514
|
)
|
|
199
515
|
] })
|
|
200
|
-
] })
|
|
516
|
+
] }),
|
|
517
|
+
bulkActions && selectedRows.length > 0 && /* @__PURE__ */ jsxs(
|
|
518
|
+
"div",
|
|
519
|
+
{
|
|
520
|
+
role: "region",
|
|
521
|
+
"aria-label": messages.selectedCount(fmt(selectedRows.length)),
|
|
522
|
+
className: cn(
|
|
523
|
+
floatingSurface,
|
|
524
|
+
"nx-motion sticky bottom-4 z-10 mx-auto flex w-fit max-w-full flex-wrap items-center gap-3 px-4 py-2"
|
|
525
|
+
),
|
|
526
|
+
"data-state": "open",
|
|
527
|
+
children: [
|
|
528
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "text-label-lg tabular-nums", children: messages.selectedCount(fmt(selectedRows.length)) }),
|
|
529
|
+
bulkActions(
|
|
530
|
+
selectedRows.map((r) => r.original),
|
|
531
|
+
clear
|
|
532
|
+
),
|
|
533
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: clear, "aria-label": messages.clearSelection, children: /* @__PURE__ */ jsx(X, { size: "sm", "aria-hidden": "true" }) })
|
|
534
|
+
]
|
|
535
|
+
}
|
|
536
|
+
)
|
|
201
537
|
] });
|
|
202
538
|
}
|
|
203
539
|
export {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { DataTable } from './DataTable.js';
|
|
2
|
-
export type { DataTableProps, DataTableColumn } from './DataTable.js';
|
|
2
|
+
export type { DataTableProps, DataTableColumn, DataTableDensity, DataTableRowWindow, DataTableRowWindowHook } from './DataTable.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type DataTableProps } from '../DataTable/DataTable.js';
|
|
3
|
+
export interface DataTableVirtualProps<TData> extends Omit<DataTableProps<TData>, 'pagination' | 'rowWindow'> {
|
|
4
|
+
/** Height of the scrolling area. Required: rows are windowed within it. */
|
|
5
|
+
maxHeight: number | string;
|
|
6
|
+
/** Row height before measuring, in px. Default 49, or 37 when compact. */
|
|
7
|
+
estimateRowHeight?: number;
|
|
8
|
+
/** Rows rendered beyond the visible ones, each way. Default 8. */
|
|
9
|
+
overscan?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* DataTable for thousands of rows: only the rows in view (plus `overscan`)
|
|
13
|
+
* are in the DOM. Rows carry aria-rowindex and the table aria-rowcount, so
|
|
14
|
+
* screen readers still report the full size. Needs `@tanstack/react-virtual`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function DataTableVirtual<TData>({ estimateRowHeight, overscan, ...props }: DataTableVirtualProps<TData>): React.JSX.Element;
|
|
17
|
+
export declare namespace DataTableVirtual {
|
|
18
|
+
var displayName: string;
|
|
19
|
+
}
|