@nexub/react 7.6.0 → 7.7.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 +16 -2
- package/dist/components/DataTable/DataTable.js +131 -76
- package/dist/components/DataTable/ViewOptions.d.ts +15 -0
- package/dist/components/DataTable/ViewOptions.js +33 -0
- package/dist/components/DataTableVirtual/DataTableVirtual.d.ts +1 -1
- package/dist/components/FilterBuilder/FilterBuilder.js +33 -15
- package/dist/components/Kanban/Kanban.d.ts +1 -1
- package/dist/components/Sortable/Sortable.d.ts +2 -2
- package/dist/components/Sortable/reorder.d.ts +10 -11
- package/dist/components/Sortable/reorder.js +185 -27
- package/dist/styles.css +1 -1
- package/package.json +8 -7
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.
|
|
2
|
+
"version": "7.7.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, and on open popups (menus, listboxes, dialogs), 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 RowSelectionState, type RowData, type VisibilityState, type ColumnSizingState, type ColumnOrderState, type ColumnPinningState, type ExpandedState } from '@tanstack/react-table';
|
|
2
|
+
import { type ColumnDef, type SortingState, type RowSelectionState, type RowData, type VisibilityState, type ColumnSizingState, type ColumnOrderState, type ColumnPinningState, type ExpandedState } 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' */
|
|
@@ -75,6 +75,20 @@ export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivEleme
|
|
|
75
75
|
pagination?: {
|
|
76
76
|
pageSize?: number;
|
|
77
77
|
};
|
|
78
|
+
/** Controlled sort (with `enableSorting`): `[{ id: 'name', desc: false }]`. */
|
|
79
|
+
sort?: SortingState;
|
|
80
|
+
onSortChange?: (sort: SortingState) => void;
|
|
81
|
+
/** Rows arrive sorted (server-side): headers still toggle `sort`, but rows aren't reordered. */
|
|
82
|
+
manualSort?: boolean;
|
|
83
|
+
/** Controlled page, zero-based. Turns pagination on. */
|
|
84
|
+
page?: number;
|
|
85
|
+
/** Rows per page; overrides `pagination.pageSize`. Turns pagination on. @default 20 */
|
|
86
|
+
pageSize?: number;
|
|
87
|
+
onPageChange?: (page: number) => void;
|
|
88
|
+
/** `data` is the current page only (server-side); set `rowCount` for the page count. Turns pagination on. */
|
|
89
|
+
manualPagination?: boolean;
|
|
90
|
+
/** Total rows across all pages, with `manualPagination`. */
|
|
91
|
+
rowCount?: number;
|
|
78
92
|
/**
|
|
79
93
|
* Nested rows (a tree table): return a row's children. The first column gets
|
|
80
94
|
* an expand toggle and indents each level. Sorting sorts within each level.
|
|
@@ -96,4 +110,4 @@ export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivEleme
|
|
|
96
110
|
/** Shown in a full-width row when there are no rows. @default messages.noResults */
|
|
97
111
|
emptyState?: React.ReactNode;
|
|
98
112
|
}
|
|
99
|
-
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, grid, rowWindow, pagination, getSubRows, expanded: expandedProp, defaultExpanded, onExpandedChange, onRowOrderChange, getRowLabel, loading, emptyState, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
113
|
+
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, grid, rowWindow, pagination: paginationProp, sort, onSortChange, manualSort, page, pageSize: pageSizeProp, onPageChange, manualPagination, rowCount, getSubRows, expanded: expandedProp, defaultExpanded, onExpandedChange, onRowOrderChange, getRowLabel, loading, emptyState, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
@@ -20,7 +20,6 @@ import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
|
20
20
|
import { Table } from "../Table/Table.js";
|
|
21
21
|
import { Checkbox } from "../Checkbox/Checkbox.js";
|
|
22
22
|
import { Button } from "../Button/Button.js";
|
|
23
|
-
import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
24
23
|
import { createGridNavigation } from "./gridNavigation.js";
|
|
25
24
|
import { useReorder } from "../Sortable/reorder.js";
|
|
26
25
|
import { Skeleton } from "../Skeleton/Skeleton.js";
|
|
@@ -134,6 +133,67 @@ function TreeCell({
|
|
|
134
133
|
] });
|
|
135
134
|
}
|
|
136
135
|
const px = (n) => typeof n === "number" ? `${n}px` : n;
|
|
136
|
+
const ViewOptions = React.lazy(() => import("./ViewOptions.js").then((m) => ({ default: m.ViewOptions })));
|
|
137
|
+
const placeCell = (column, resizable) => (el) => {
|
|
138
|
+
if (!el) return;
|
|
139
|
+
el.style.width = resizable ? px(column.getSize()) : "";
|
|
140
|
+
const side = column.getIsPinned();
|
|
141
|
+
el.style.insetInlineStart = side === "left" ? px(column.getStart("left")) : "";
|
|
142
|
+
el.style.insetInlineEnd = side === "right" ? px(column.getAfter("right")) : "";
|
|
143
|
+
};
|
|
144
|
+
const pinClass = (column) => {
|
|
145
|
+
const side = column.getIsPinned();
|
|
146
|
+
if (!side) return void 0;
|
|
147
|
+
return cn(
|
|
148
|
+
"sticky z-[1] bg-[var(--color-surface)]",
|
|
149
|
+
"[tr:hover>&]:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,var(--color-surface))]",
|
|
150
|
+
"[tr[data-state=selected]>&]:bg-[color-mix(in_srgb,var(--color-primary)_10%,var(--color-surface))]",
|
|
151
|
+
side === "left" && column.getIsLastColumn("left") && "border-e border-[var(--color-outline-variant)]",
|
|
152
|
+
side === "right" && column.getIsFirstColumn("right") && "border-s border-[var(--color-outline-variant)]"
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
function RowViewImpl({
|
|
156
|
+
row,
|
|
157
|
+
index,
|
|
158
|
+
selected,
|
|
159
|
+
compact,
|
|
160
|
+
pinned,
|
|
161
|
+
resizable,
|
|
162
|
+
selectable,
|
|
163
|
+
tree,
|
|
164
|
+
virtual,
|
|
165
|
+
measure,
|
|
166
|
+
expandLabel,
|
|
167
|
+
collapseLabel,
|
|
168
|
+
dragProps
|
|
169
|
+
}) {
|
|
170
|
+
return /* @__PURE__ */ jsx(
|
|
171
|
+
TableRow,
|
|
172
|
+
{
|
|
173
|
+
ref: measure,
|
|
174
|
+
"data-index": index,
|
|
175
|
+
"aria-rowindex": virtual ? index + 2 : void 0,
|
|
176
|
+
"data-state": selectable && selected ? "selected" : void 0,
|
|
177
|
+
...dragProps,
|
|
178
|
+
children: row.getVisibleCells().map((cell, _i, cells) => /* @__PURE__ */ jsx(
|
|
179
|
+
TableCell,
|
|
180
|
+
{
|
|
181
|
+
ref: pinned || resizable ? placeCell(cell.column, resizable) : void 0,
|
|
182
|
+
className: cn(
|
|
183
|
+
alignText[cell.column.columnDef.meta?.align ?? "start"],
|
|
184
|
+
compact && "py-1.5",
|
|
185
|
+
pinClass(cell.column),
|
|
186
|
+
resizable && "truncate"
|
|
187
|
+
),
|
|
188
|
+
children: tree && cell === cells.find((c) => c.column.id !== "__select") ? /* @__PURE__ */ jsx(TreeCell, { row, expandLabel, collapseLabel, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }) : flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
189
|
+
},
|
|
190
|
+
cell.id
|
|
191
|
+
))
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const sameRow = (a, b) => Object.keys(a).every((k) => k === "row" || a[k] === b[k]) && a.row.id === b.row.id && a.row.original === b.row.original && a.row.index === b.row.index && a.row.depth === b.row.depth;
|
|
196
|
+
const RowView = React.memo(RowViewImpl, sameRow);
|
|
137
197
|
const apply = (u, prev) => typeof u === "function" ? u(prev) : u;
|
|
138
198
|
const columnLabel = (c) => typeof c.columnDef.header === "string" ? c.columnDef.header : c.id;
|
|
139
199
|
function DataTable({
|
|
@@ -164,7 +224,15 @@ function DataTable({
|
|
|
164
224
|
maxHeight,
|
|
165
225
|
grid = false,
|
|
166
226
|
rowWindow,
|
|
167
|
-
pagination,
|
|
227
|
+
pagination: paginationProp,
|
|
228
|
+
sort,
|
|
229
|
+
onSortChange,
|
|
230
|
+
manualSort = false,
|
|
231
|
+
page,
|
|
232
|
+
pageSize: pageSizeProp,
|
|
233
|
+
onPageChange,
|
|
234
|
+
manualPagination = false,
|
|
235
|
+
rowCount,
|
|
168
236
|
getSubRows,
|
|
169
237
|
expanded: expandedProp,
|
|
170
238
|
defaultExpanded,
|
|
@@ -182,7 +250,11 @@ function DataTable({
|
|
|
182
250
|
}) {
|
|
183
251
|
const messages = useNexubMessages();
|
|
184
252
|
const { locale, dir } = useLocale();
|
|
185
|
-
const [sorting, setSorting] =
|
|
253
|
+
const [sorting = [], setSorting] = useControllableState({
|
|
254
|
+
value: sort,
|
|
255
|
+
defaultValue: [],
|
|
256
|
+
onChange: onSortChange
|
|
257
|
+
});
|
|
186
258
|
const [rowSelection = {}, setRowSelection] = useControllableState({
|
|
187
259
|
value: rowSelectionProp,
|
|
188
260
|
defaultValue: {},
|
|
@@ -222,10 +294,14 @@ function DataTable({
|
|
|
222
294
|
const tableRef = React.useRef(null);
|
|
223
295
|
const gridNav = React.useMemo(() => createGridNavigation(dir), [dir]);
|
|
224
296
|
const dragged = React.useRef(null);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
297
|
+
const pagination = !!paginationProp || page !== void 0 || pageSizeProp !== void 0 || manualPagination;
|
|
298
|
+
const [pageIndex = 0, setPageIndex] = useControllableState({
|
|
299
|
+
value: page,
|
|
300
|
+
defaultValue: 0,
|
|
301
|
+
onChange: onPageChange
|
|
228
302
|
});
|
|
303
|
+
const pageSize = pageSizeProp ?? paginationProp?.pageSize ?? 20;
|
|
304
|
+
const paginationState = React.useMemo(() => ({ pageIndex, pageSize }), [pageIndex, pageSize]);
|
|
229
305
|
const withSelectColumn = enableRowSelection && selectionColumn;
|
|
230
306
|
const reorderRows = Boolean(onRowOrderChange) && sorting.length === 0;
|
|
231
307
|
const rowReorder = useReorder({
|
|
@@ -312,7 +388,8 @@ function DataTable({
|
|
|
312
388
|
columns: allColumns,
|
|
313
389
|
state: tableState,
|
|
314
390
|
getRowId,
|
|
315
|
-
onSortingChange: enableSorting ? setSorting : void 0,
|
|
391
|
+
onSortingChange: enableSorting ? (u) => setSorting(apply(u, sorting)) : void 0,
|
|
392
|
+
manualSorting: manualSort,
|
|
316
393
|
onRowSelectionChange: enableRowSelection ? (u) => setRowSelection(apply(u, rowSelection)) : void 0,
|
|
317
394
|
onColumnVisibilityChange: (u) => setColumnVisibility(apply(u, columnVisibility)),
|
|
318
395
|
onColumnSizingChange: (u) => setColumnSizing(apply(u, columnSizing)),
|
|
@@ -321,11 +398,15 @@ function DataTable({
|
|
|
321
398
|
enableColumnResizing,
|
|
322
399
|
columnResizeMode: "onChange",
|
|
323
400
|
columnResizeDirection: dir,
|
|
324
|
-
onPaginationChange: pagination ?
|
|
401
|
+
onPaginationChange: pagination ? (u) => {
|
|
402
|
+
const next = apply(u, paginationState).pageIndex;
|
|
403
|
+
if (next !== pageIndex) setPageIndex(next);
|
|
404
|
+
} : void 0,
|
|
325
405
|
getCoreRowModel: getCoreRowModel(),
|
|
326
|
-
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
406
|
+
getSortedRowModel: enableSorting && !manualSort ? getSortedRowModel() : void 0,
|
|
327
407
|
getFilteredRowModel: getFilteredRowModel(),
|
|
328
|
-
getPaginationRowModel: pagination ? getPaginationRowModel() : void 0,
|
|
408
|
+
getPaginationRowModel: pagination && !manualPagination ? getPaginationRowModel() : void 0,
|
|
409
|
+
rowCount,
|
|
329
410
|
getSubRows,
|
|
330
411
|
getExpandedRowModel: getSubRows ? getExpandedRowModel() : void 0,
|
|
331
412
|
onExpandedChange: getSubRows ? (u) => setExpanded(apply(u, expanded)) : void 0,
|
|
@@ -333,7 +414,7 @@ function DataTable({
|
|
|
333
414
|
// (TanStack then expands inside the page model; without pages it must not).
|
|
334
415
|
paginateExpandedRows: !pagination,
|
|
335
416
|
enableRowSelection,
|
|
336
|
-
manualPagination
|
|
417
|
+
manualPagination
|
|
337
418
|
});
|
|
338
419
|
const compact = density === "compact";
|
|
339
420
|
const selectedRows = enableRowSelection ? table.getSelectedRowModel().rows : [];
|
|
@@ -354,24 +435,11 @@ function DataTable({
|
|
|
354
435
|
if (scroller) scroller.style.maxHeight = maxHeight == null ? "" : px(maxHeight);
|
|
355
436
|
if (grid) gridNav.sync(t);
|
|
356
437
|
});
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
el.style.insetInlineEnd = side === "right" ? px(column.getAfter("right")) : "";
|
|
363
|
-
};
|
|
364
|
-
const pinClass = (column) => {
|
|
365
|
-
const side = column.getIsPinned();
|
|
366
|
-
if (!side) return void 0;
|
|
367
|
-
return cn(
|
|
368
|
-
"sticky z-[1] bg-[var(--color-surface)]",
|
|
369
|
-
"[tr:hover>&]:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,var(--color-surface))]",
|
|
370
|
-
"[tr[data-state=selected]>&]:bg-[color-mix(in_srgb,var(--color-primary)_10%,var(--color-surface))]",
|
|
371
|
-
side === "left" && column.getIsLastColumn("left") && "border-e border-[var(--color-outline-variant)]",
|
|
372
|
-
side === "right" && column.getIsFirstColumn("right") && "border-s border-[var(--color-outline-variant)]"
|
|
373
|
-
);
|
|
374
|
-
};
|
|
438
|
+
const visibleColumns = table.getVisibleLeafColumns();
|
|
439
|
+
const viewButton = /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ms-auto", children: [
|
|
440
|
+
/* @__PURE__ */ jsx(SlidersHorizontal, { size: "sm", "aria-hidden": "true" }),
|
|
441
|
+
messages.view
|
|
442
|
+
] });
|
|
375
443
|
const moveColumn = (id, to) => {
|
|
376
444
|
const ids = table.getAllLeafColumns().map((c) => c.id);
|
|
377
445
|
const order = (columnOrder.length ? columnOrder : ids).filter((c) => c !== "__select" && ids.includes(c));
|
|
@@ -390,31 +458,21 @@ function DataTable({
|
|
|
390
458
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-density": density, ...props, children: [
|
|
391
459
|
(toolbar != null || viewOptions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
392
460
|
toolbar,
|
|
393
|
-
viewOptions &&
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
{
|
|
409
|
-
checked: c.getIsVisible(),
|
|
410
|
-
onCheckedChange: (v) => c.toggleVisibility(v === true),
|
|
411
|
-
onSelect: (e) => e.preventDefault(),
|
|
412
|
-
children: columnLabel(c)
|
|
413
|
-
},
|
|
414
|
-
c.id
|
|
415
|
-
))
|
|
416
|
-
] })
|
|
417
|
-
] })
|
|
461
|
+
viewOptions && // The plain button stands in (same size, no layout shift) until the menu loads.
|
|
462
|
+
/* @__PURE__ */ jsx(React.Suspense, { fallback: viewButton, children: /* @__PURE__ */ jsx(
|
|
463
|
+
ViewOptions,
|
|
464
|
+
{
|
|
465
|
+
trigger: viewButton,
|
|
466
|
+
density,
|
|
467
|
+
onDensityChange: setDensity,
|
|
468
|
+
columns: table.getAllLeafColumns().filter((c) => c.getCanHide()).map((c) => ({
|
|
469
|
+
id: c.id,
|
|
470
|
+
label: columnLabel(c),
|
|
471
|
+
visible: c.getIsVisible(),
|
|
472
|
+
toggle: (v) => c.toggleVisibility(v)
|
|
473
|
+
}))
|
|
474
|
+
}
|
|
475
|
+
) })
|
|
418
476
|
] }),
|
|
419
477
|
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: [
|
|
420
478
|
/* @__PURE__ */ jsx("span", { children: messages.selectedCount(fmt(totalRows)) }),
|
|
@@ -447,7 +505,7 @@ function DataTable({
|
|
|
447
505
|
return /* @__PURE__ */ jsxs(
|
|
448
506
|
TableHead,
|
|
449
507
|
{
|
|
450
|
-
ref: placeCell(col),
|
|
508
|
+
ref: placeCell(col, enableColumnResizing),
|
|
451
509
|
className: cn(
|
|
452
510
|
alignText[align],
|
|
453
511
|
compact && "h-8",
|
|
@@ -532,32 +590,29 @@ function DataTable({
|
|
|
532
590
|
header.id
|
|
533
591
|
);
|
|
534
592
|
}) }, headerGroup.id)) }),
|
|
535
|
-
/* @__PURE__ */ jsx(TableBody, { children: loading ? Array.from({ length: pagination
|
|
593
|
+
/* @__PURE__ */ jsx(TableBody, { children: loading ? Array.from({ length: pagination ? pageSize : 5 }, (_, i) => /* @__PURE__ */ jsx(TableRow, { "data-loading": "", className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((c) => /* @__PURE__ */ jsx(TableCell, { className: cn(compact && "py-1.5"), children: /* @__PURE__ */ jsx(Skeleton, {}) }, c.id)) }, i)) : rows.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
536
594
|
rowWin.before > 0 && /* @__PURE__ */ jsx(Spacer, { height: rowWin.before }),
|
|
537
595
|
rowWin.indexes.map((i) => {
|
|
538
596
|
const row = rows[i];
|
|
539
597
|
return /* @__PURE__ */ jsx(
|
|
540
|
-
|
|
598
|
+
RowView,
|
|
541
599
|
{
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
},
|
|
559
|
-
cell.id
|
|
560
|
-
))
|
|
600
|
+
row,
|
|
601
|
+
index: i,
|
|
602
|
+
selected: enableRowSelection && row.getIsSelected(),
|
|
603
|
+
expanded: !!getSubRows && row.getIsExpanded(),
|
|
604
|
+
columns: visibleColumns,
|
|
605
|
+
columnSizing,
|
|
606
|
+
compact,
|
|
607
|
+
pinned,
|
|
608
|
+
resizable: enableColumnResizing,
|
|
609
|
+
selectable: enableRowSelection,
|
|
610
|
+
tree: !!getSubRows,
|
|
611
|
+
virtual: rowWin.virtual,
|
|
612
|
+
measure: rowWin.measure,
|
|
613
|
+
expandLabel: messages.expandRow,
|
|
614
|
+
collapseLabel: messages.collapseRow,
|
|
615
|
+
dragProps: reorderRows && row.depth === 0 ? rowReorder.itemProps([0, row.index]) : void 0
|
|
561
616
|
},
|
|
562
617
|
row.id
|
|
563
618
|
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { DataTableDensity } from './DataTable.js';
|
|
3
|
+
export interface ViewOptionsProps {
|
|
4
|
+
trigger: React.ReactElement;
|
|
5
|
+
density: DataTableDensity;
|
|
6
|
+
onDensityChange: (density: DataTableDensity) => void;
|
|
7
|
+
/** Hideable columns. */
|
|
8
|
+
columns: {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
visible: boolean;
|
|
12
|
+
toggle: (visible: boolean) => void;
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
export declare function ViewOptions({ trigger, density, onDensityChange, columns }: ViewOptionsProps): React.JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
5
|
+
import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
6
|
+
function ViewOptions({ trigger, density, onDensityChange, columns }) {
|
|
7
|
+
const messages = useNexubMessages();
|
|
8
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
9
|
+
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: trigger }),
|
|
10
|
+
/* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
|
|
11
|
+
/* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.density }),
|
|
12
|
+
/* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, { value: density, onValueChange: (v) => onDensityChange(v), children: [
|
|
13
|
+
/* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "comfortable", children: messages.comfortable }),
|
|
14
|
+
/* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "compact", children: messages.compact })
|
|
15
|
+
] }),
|
|
16
|
+
/* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
17
|
+
/* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.columns }),
|
|
18
|
+
columns.map((c) => /* @__PURE__ */ jsx(
|
|
19
|
+
DropdownMenu.CheckboxItem,
|
|
20
|
+
{
|
|
21
|
+
checked: c.visible,
|
|
22
|
+
onCheckedChange: (v) => c.toggle(v === true),
|
|
23
|
+
onSelect: (e) => e.preventDefault(),
|
|
24
|
+
children: c.label
|
|
25
|
+
},
|
|
26
|
+
c.id
|
|
27
|
+
))
|
|
28
|
+
] })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
ViewOptions
|
|
33
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { type DataTableProps } from '../DataTable/DataTable.js';
|
|
3
|
-
export interface DataTableVirtualProps<TData> extends Omit<DataTableProps<TData>, 'pagination' | 'rowWindow'> {
|
|
3
|
+
export interface DataTableVirtualProps<TData> extends Omit<DataTableProps<TData>, 'pagination' | 'rowWindow' | 'page' | 'pageSize' | 'onPageChange' | 'manualPagination' | 'rowCount'> {
|
|
4
4
|
/** Height of the scrolling area. Required: rows are windowed within it. */
|
|
5
5
|
maxHeight: number | string;
|
|
6
6
|
/** Row height before measuring, in px. Default 49, or 37 when compact. */
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@nexub/core";
|
|
13
13
|
import { cn } from "../../utils/cn.js";
|
|
14
14
|
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
15
|
+
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
15
16
|
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
16
17
|
import { Button } from "../Button/Button.js";
|
|
17
18
|
import { IconButton } from "../IconButton/IconButton.js";
|
|
@@ -24,14 +25,20 @@ function FilterBuilder({ fields, value: valueProp, defaultValue, onValueChange,
|
|
|
24
25
|
defaultValue: defaultValue ?? emptyFilter(),
|
|
25
26
|
onChange: onValueChange
|
|
26
27
|
});
|
|
28
|
+
const latest = React.useRef(value);
|
|
29
|
+
useIsomorphicLayoutEffect(() => {
|
|
30
|
+
latest.current = value;
|
|
31
|
+
});
|
|
32
|
+
const update = React.useCallback((fn) => setValue(fn(latest.current)), [setValue]);
|
|
33
|
+
const deferred = React.useDeferredValue(value);
|
|
27
34
|
return /* @__PURE__ */ jsx(
|
|
28
|
-
|
|
35
|
+
MemoGroupView,
|
|
29
36
|
{
|
|
30
|
-
group:
|
|
37
|
+
group: deferred,
|
|
31
38
|
fields,
|
|
32
39
|
depth: 0,
|
|
33
40
|
maxDepth,
|
|
34
|
-
onChange:
|
|
41
|
+
onChange: update,
|
|
35
42
|
className
|
|
36
43
|
}
|
|
37
44
|
);
|
|
@@ -40,7 +47,7 @@ FilterBuilder.displayName = "FilterBuilder";
|
|
|
40
47
|
function GroupView({ group, fields, depth, maxDepth, onChange, onRemove, className }) {
|
|
41
48
|
const t = useNexubMessages();
|
|
42
49
|
const first = fields[0];
|
|
43
|
-
const setRules = (
|
|
50
|
+
const setRules = (fn) => onChange((g) => ({ ...g, rules: fn(g.rules) }));
|
|
44
51
|
const newRule = () => newFilterRule(first);
|
|
45
52
|
return /* @__PURE__ */ jsxs(
|
|
46
53
|
"div",
|
|
@@ -60,7 +67,7 @@ function GroupView({ group, fields, depth, maxDepth, onChange, onRemove, classNa
|
|
|
60
67
|
size: "sm",
|
|
61
68
|
"aria-label": t.filterGroup(group.combinator),
|
|
62
69
|
value: group.combinator,
|
|
63
|
-
onValueChange: (v) => v && onChange({ ...
|
|
70
|
+
onValueChange: (v) => v && onChange((g) => ({ ...g, combinator: v })),
|
|
64
71
|
children: [
|
|
65
72
|
/* @__PURE__ */ jsx(SegmentedControl.Item, { value: "and", children: t.matchAll }),
|
|
66
73
|
/* @__PURE__ */ jsx(SegmentedControl.Item, { value: "or", children: t.matchAny })
|
|
@@ -69,13 +76,13 @@ function GroupView({ group, fields, depth, maxDepth, onChange, onRemove, classNa
|
|
|
69
76
|
),
|
|
70
77
|
onRemove && /* @__PURE__ */ jsx(IconButton, { label: t.removeGroup, variant: "ghost", size: "sm", className: "ms-auto", onClick: onRemove, children: /* @__PURE__ */ jsx(X, { size: "sm", "aria-hidden": "true" }) })
|
|
71
78
|
] }),
|
|
72
|
-
group.rules.map((r
|
|
73
|
-
const replace = (
|
|
74
|
-
const remove = () => setRules(
|
|
79
|
+
group.rules.map((r) => {
|
|
80
|
+
const replace = (fn) => setRules((rules) => rules.map((x) => x.id === r.id ? fn(x) : x));
|
|
81
|
+
const remove = () => setRules((rules) => rules.filter((x) => x.id !== r.id));
|
|
75
82
|
return isFilterGroup(r) ? /* @__PURE__ */ jsx(GroupView, { group: r, fields, depth: depth + 1, maxDepth, onChange: replace, onRemove: remove }, r.id) : /* @__PURE__ */ jsx(RuleRow, { rule: r, fields, onChange: replace, onRemove: remove }, r.id);
|
|
76
83
|
}),
|
|
77
84
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
78
|
-
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setRules([...
|
|
85
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setRules((rules) => [...rules, newRule()]), disabled: !first, children: [
|
|
79
86
|
/* @__PURE__ */ jsx(Plus, { size: "sm", "aria-hidden": "true" }),
|
|
80
87
|
t.addFilter
|
|
81
88
|
] }),
|
|
@@ -84,7 +91,7 @@ function GroupView({ group, fields, depth, maxDepth, onChange, onRemove, classNa
|
|
|
84
91
|
{
|
|
85
92
|
variant: "ghost",
|
|
86
93
|
size: "sm",
|
|
87
|
-
onClick: () => setRules([...
|
|
94
|
+
onClick: () => setRules((rules) => [...rules, { id: uid(), combinator: "and", rules: [newRule()] }]),
|
|
88
95
|
disabled: !first,
|
|
89
96
|
children: [
|
|
90
97
|
/* @__PURE__ */ jsx(Plus, { size: "sm", "aria-hidden": "true" }),
|
|
@@ -97,6 +104,7 @@ function GroupView({ group, fields, depth, maxDepth, onChange, onRemove, classNa
|
|
|
97
104
|
}
|
|
98
105
|
);
|
|
99
106
|
}
|
|
107
|
+
const MemoGroupView = React.memo(GroupView);
|
|
100
108
|
function RuleRow({
|
|
101
109
|
rule,
|
|
102
110
|
fields,
|
|
@@ -104,6 +112,12 @@ function RuleRow({
|
|
|
104
112
|
onRemove
|
|
105
113
|
}) {
|
|
106
114
|
const t = useNexubMessages();
|
|
115
|
+
const [draft, setDraft] = React.useState(rule.value);
|
|
116
|
+
const [seen, setSeen] = React.useState(rule.value);
|
|
117
|
+
if (rule.value !== seen) {
|
|
118
|
+
setSeen(rule.value);
|
|
119
|
+
setDraft(rule.value);
|
|
120
|
+
}
|
|
107
121
|
const field = fields.find((f) => f.id === rule.field) ?? fields[0];
|
|
108
122
|
const ops = filterOperators[field?.type ?? "text"];
|
|
109
123
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
@@ -114,7 +128,7 @@ function RuleRow({
|
|
|
114
128
|
onValueChange: (id) => {
|
|
115
129
|
const f = fields.find((x) => x.id === id);
|
|
116
130
|
const nextOps = filterOperators[f?.type ?? "text"];
|
|
117
|
-
onChange({ ...
|
|
131
|
+
onChange((r) => ({ ...r, field: id, operator: nextOps.includes(r.operator) ? r.operator : nextOps[0], value: "" }));
|
|
118
132
|
},
|
|
119
133
|
children: [
|
|
120
134
|
/* @__PURE__ */ jsx(Select.Trigger, { "aria-label": t.filterField, className: "w-auto min-w-36", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
|
|
@@ -122,11 +136,11 @@ function RuleRow({
|
|
|
122
136
|
]
|
|
123
137
|
}
|
|
124
138
|
),
|
|
125
|
-
/* @__PURE__ */ jsxs(Select.Root, { value: rule.operator, onValueChange: (op) => onChange({ ...
|
|
139
|
+
/* @__PURE__ */ jsxs(Select.Root, { value: rule.operator, onValueChange: (op) => onChange((r) => ({ ...r, operator: op })), children: [
|
|
126
140
|
/* @__PURE__ */ jsx(Select.Trigger, { "aria-label": t.filterOperator, className: "w-auto min-w-32", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
|
|
127
141
|
/* @__PURE__ */ jsx(Select.Content, { children: ops.map((op) => /* @__PURE__ */ jsx(Select.Item, { value: op, children: t.filterOperators[op] }, op)) })
|
|
128
142
|
] }),
|
|
129
|
-
rule.operator !== "isEmpty" && (field?.type === "select" ? /* @__PURE__ */ jsxs(Select.Root, { value: rule.value || void 0, onValueChange: (v) => onChange({ ...
|
|
143
|
+
rule.operator !== "isEmpty" && (field?.type === "select" ? /* @__PURE__ */ jsxs(Select.Root, { value: rule.value || void 0, onValueChange: (v) => onChange((r) => ({ ...r, value: v })), children: [
|
|
130
144
|
/* @__PURE__ */ jsx(Select.Trigger, { "aria-label": t.filterValue, className: "w-auto min-w-36", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
|
|
131
145
|
/* @__PURE__ */ jsx(Select.Content, { children: field.options?.map((o) => /* @__PURE__ */ jsx(Select.Item, { value: o.value, children: o.label }, o.value)) })
|
|
132
146
|
] }) : /* @__PURE__ */ jsx(
|
|
@@ -134,8 +148,12 @@ function RuleRow({
|
|
|
134
148
|
{
|
|
135
149
|
"aria-label": t.filterValue,
|
|
136
150
|
type: field?.type === "number" ? "number" : field?.type === "date" ? "date" : "text",
|
|
137
|
-
value:
|
|
138
|
-
onChange: (e) =>
|
|
151
|
+
value: draft,
|
|
152
|
+
onChange: (e) => {
|
|
153
|
+
const v = e.target.value;
|
|
154
|
+
setDraft(v);
|
|
155
|
+
onChange((r) => ({ ...r, value: v }));
|
|
156
|
+
},
|
|
139
157
|
wrapperClassName: "w-auto min-w-36 flex-1"
|
|
140
158
|
}
|
|
141
159
|
)),
|
|
@@ -14,7 +14,7 @@ export interface KanbanProps<T> {
|
|
|
14
14
|
className?: string;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Columns of cards people can move: drag a card, or focus
|
|
17
|
+
* Columns of cards people can move: drag a card's handle (Escape cancels), or focus it and press
|
|
18
18
|
* Alt+Arrow keys (up/down within a column, left/right across columns).
|
|
19
19
|
*/
|
|
20
20
|
export declare function Kanban<T>({ columns, getId, getLabel, onChange, renderCard, className }: KanbanProps<T>): React.JSX.Element;
|
|
@@ -12,7 +12,7 @@ export interface SortableProps<T> {
|
|
|
12
12
|
className?: string;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* A list people can reorder: drag an item
|
|
16
|
-
* Alt+ArrowUp / Alt+ArrowDown. Each move is announced.
|
|
15
|
+
* A list people can reorder: drag an item's handle (mouse, pen or touch; Escape
|
|
16
|
+
* cancels), or focus the handle and press Alt+ArrowUp / Alt+ArrowDown. Each move is announced.
|
|
17
17
|
*/
|
|
18
18
|
export declare function Sortable<T>({ items, getId, getLabel, onReorder, children, className, ...aria }: SortableProps<T>): React.JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
/** Internal: lists of items, moved by
|
|
2
|
+
/** Internal: lists of items, moved by dragging a handle (Pointer Events) or Alt+Arrow on it. */
|
|
3
3
|
export type Pos = [list: number, index: number];
|
|
4
4
|
export declare function moveItem<T>(lists: T[][], [fc, fi]: Pos, [tc, ti]: Pos): T[][];
|
|
5
5
|
interface ReorderOptions<T> {
|
|
@@ -10,18 +10,17 @@ interface ReorderOptions<T> {
|
|
|
10
10
|
listLabels?: string[];
|
|
11
11
|
onMove: (next: T[][]) => void;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
type ItemProps = {
|
|
14
|
+
'data-nx-reorder-item': '';
|
|
15
|
+
'data-nx-dragging'?: '';
|
|
16
|
+
'data-nx-drop'?: 'before' | 'after';
|
|
17
|
+
};
|
|
18
|
+
/** Shared engine: pointer drag, keyboard moves, focus after a move, FLIP settle, announcements. */
|
|
14
19
|
export declare function useReorder<T>({ lists, getId, getLabel, listLabels, onMove }: ReorderOptions<T>): {
|
|
15
|
-
itemProps: (pos: Pos) =>
|
|
16
|
-
draggable: boolean;
|
|
17
|
-
onDragStart: (e: React.DragEvent) => void;
|
|
18
|
-
onDragOver: (e: React.DragEvent) => void;
|
|
19
|
-
onDrop: (e: React.DragEvent) => void;
|
|
20
|
-
onDragEnd: () => void;
|
|
21
|
-
};
|
|
20
|
+
itemProps: (pos: Pos) => ItemProps;
|
|
22
21
|
listProps: (list: number) => {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
ref: (el: HTMLElement | null) => void;
|
|
23
|
+
'data-nx-drop': string | undefined;
|
|
25
24
|
};
|
|
26
25
|
handle: (pos: Pos, className?: string) => React.JSX.Element;
|
|
27
26
|
live: React.JSX.Element;
|