cursedbelt 5.23.0 โ 5.26.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/dist/react/components/Badge.d.ts +6 -6
- package/dist/react/components/Badge.d.ts.map +1 -1
- package/dist/react/components/Badge.js +13 -5
- package/dist/react/components/Badge.js.map +1 -1
- package/dist/react/components/DataTable/CardView.d.ts +6 -2
- package/dist/react/components/DataTable/CardView.d.ts.map +1 -1
- package/dist/react/components/DataTable/CardView.js +6 -5
- package/dist/react/components/DataTable/CardView.js.map +1 -1
- package/dist/react/components/DataTable/DataTable.d.ts +165 -5
- package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/react/components/DataTable/DataTable.js +445 -114
- package/dist/react/components/DataTable/DataTable.js.map +1 -1
- package/dist/react/components/DataTable/RowActions.d.ts +3 -1
- package/dist/react/components/DataTable/RowActions.d.ts.map +1 -1
- package/dist/react/components/DataTable/RowActions.js +9 -2
- package/dist/react/components/DataTable/RowActions.js.map +1 -1
- package/dist/react/components/DataTable/SortHeader.d.ts +4 -1
- package/dist/react/components/DataTable/SortHeader.d.ts.map +1 -1
- package/dist/react/components/DataTable/SortHeader.js +7 -1
- package/dist/react/components/DataTable/SortHeader.js.map +1 -1
- package/dist/react/components/DataTable/cells/CellRenderer.d.ts.map +1 -1
- package/dist/react/components/DataTable/cells/CellRenderer.js +4 -1
- package/dist/react/components/DataTable/cells/CellRenderer.js.map +1 -1
- package/dist/react/components/DataTable/index.d.ts +2 -2
- package/dist/react/components/DataTable/index.d.ts.map +1 -1
- package/dist/react/components/DataTable/index.js +1 -1
- package/dist/react/components/DataTable/index.js.map +1 -1
- package/dist/react/components/DataTable/lazyPart.d.ts +27 -0
- package/dist/react/components/DataTable/lazyPart.d.ts.map +1 -0
- package/dist/react/components/DataTable/lazyPart.js +46 -0
- package/dist/react/components/DataTable/lazyPart.js.map +1 -0
- package/dist/react/components/DataTable/types.d.ts +106 -0
- package/dist/react/components/DataTable/types.d.ts.map +1 -1
- package/dist/react/components/DataTable/types.js.map +1 -1
- package/dist/react/components/auth/SignInCard.d.ts +49 -0
- package/dist/react/components/auth/SignInCard.d.ts.map +1 -0
- package/dist/react/components/auth/SignInCard.js +21 -0
- package/dist/react/components/auth/SignInCard.js.map +1 -0
- package/dist/react/components/auth/index.d.ts +1 -0
- package/dist/react/components/auth/index.d.ts.map +1 -1
- package/dist/react/components/auth/index.js +1 -0
- package/dist/react/components/auth/index.js.map +1 -1
- package/dist/react/deep-link/index.d.ts +1 -1
- package/dist/react/deep-link/index.d.ts.map +1 -1
- package/dist/react/deep-link/index.js.map +1 -1
- package/dist/react/deep-link/useDeepLink.d.ts +21 -15
- package/dist/react/deep-link/useDeepLink.d.ts.map +1 -1
- package/dist/react/deep-link/useDeepLink.js +8 -5
- package/dist/react/deep-link/useDeepLink.js.map +1 -1
- package/dist/react/file-tree/FileTree.d.ts +14 -1
- package/dist/react/file-tree/FileTree.d.ts.map +1 -1
- package/dist/react/file-tree/FileTree.js +2 -2
- package/dist/react/file-tree/FileTree.js.map +1 -1
- package/dist/react/file-tree/fileTree.css +8 -0
- package/dist/react/filter-rail/FilterRail.d.ts +31 -1
- package/dist/react/filter-rail/FilterRail.d.ts.map +1 -1
- package/dist/react/filter-rail/FilterRail.js +20 -5
- package/dist/react/filter-rail/FilterRail.js.map +1 -1
- package/dist/react/filter-rail/RailSection.d.ts +56 -0
- package/dist/react/filter-rail/RailSection.d.ts.map +1 -0
- package/dist/react/filter-rail/RailSection.js +108 -0
- package/dist/react/filter-rail/RailSection.js.map +1 -0
- package/dist/react/filter-rail/index.d.ts +2 -1
- package/dist/react/filter-rail/index.d.ts.map +1 -1
- package/dist/react/filter-rail/index.js +2 -1
- package/dist/react/filter-rail/index.js.map +1 -1
- package/dist/react/hooks/useScrollMemory.d.ts +6 -0
- package/dist/react/hooks/useScrollMemory.d.ts.map +1 -1
- package/dist/react/hooks/useScrollMemory.js +11 -0
- package/dist/react/hooks/useScrollMemory.js.map +1 -1
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +6 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/media-gallery/preferenceBus.d.ts +10 -0
- package/dist/react/media-gallery/preferenceBus.d.ts.map +1 -1
- package/dist/react/media-gallery/preferenceBus.js +24 -0
- package/dist/react/media-gallery/preferenceBus.js.map +1 -1
- package/dist/react/media-gallery/preferenceSync.d.ts.map +1 -1
- package/dist/react/media-gallery/preferenceSync.js +3 -0
- package/dist/react/media-gallery/preferenceSync.js.map +1 -1
- package/dist/react/nav/TopBar.d.ts +8 -1
- package/dist/react/nav/TopBar.d.ts.map +1 -1
- package/dist/react/nav/TopBar.js +2 -2
- package/dist/react/nav/TopBar.js.map +1 -1
- package/dist/styles-areas/analytics.css +18 -15
- package/dist/styles-areas/core.css +8 -7
- package/dist/styles-areas/data-table.css +13 -10
- package/dist/styles-areas/filter-rail.css +6 -5
- package/dist/styles-utilities.css +90 -0
- package/package.json +3 -3
- package/src/react/components/Badge.capsule.spec.ts +178 -0
- package/src/react/components/Badge.tsx +13 -5
- package/src/react/components/DataTable/CardView.tsx +8 -1
- package/src/react/components/DataTable/DataTable.spec.tsx +6 -2
- package/src/react/components/DataTable/DataTable.tsx +770 -86
- package/src/react/components/DataTable/RowActions.tsx +18 -10
- package/src/react/components/DataTable/SortHeader.tsx +19 -0
- package/src/react/components/DataTable/cells/CellRenderer.tsx +2 -0
- package/src/react/components/DataTable/index.ts +10 -1
- package/src/react/components/DataTable/lazyPart.spec.tsx +99 -0
- package/src/react/components/DataTable/lazyPart.tsx +58 -0
- package/src/react/components/DataTable/rowHooks.spec.tsx +289 -0
- package/src/react/components/DataTable/selection.spec.tsx +193 -0
- package/src/react/components/DataTable/tableSystem.spec.tsx +6 -2
- package/src/react/components/DataTable/types.ts +110 -0
- package/src/react/components/DataTable/virtualize.spec.tsx +38 -2
- package/src/react/components/auth/SignInCard.spec.tsx +48 -0
- package/src/react/components/auth/SignInCard.tsx +90 -0
- package/src/react/components/auth/index.ts +1 -0
- package/src/react/deep-link/index.ts +1 -0
- package/src/react/deep-link/useDeepLink.spec.tsx +31 -0
- package/src/react/deep-link/useDeepLink.tsx +31 -11
- package/src/react/file-tree/FileTree.tsx +15 -1
- package/src/react/file-tree/fileTree.css +8 -0
- package/src/react/filter-rail/FilterRail.tsx +93 -26
- package/src/react/filter-rail/RailSection.spec.tsx +156 -0
- package/src/react/filter-rail/RailSection.tsx +216 -0
- package/src/react/filter-rail/index.ts +2 -0
- package/src/react/hooks/useScrollMemory.ts +11 -0
- package/src/react/index.ts +10 -0
- package/src/react/media-gallery/MediaGallery.spec.tsx +9 -3
- package/src/react/media-gallery/preferenceBus.ts +28 -0
- package/src/react/media-gallery/preferenceSync.ts +3 -0
- package/src/react/nav/TopBar.spec.tsx +11 -0
- package/src/react/nav/TopBar.tsx +9 -1
- package/src/styles-areas/analytics.css +18 -15
- package/src/styles-areas/core.css +8 -7
- package/src/styles-areas/data-table.css +13 -10
- package/src/styles-areas/filter-rail.css +6 -5
- package/src/styles-utilities.css +90 -0
|
@@ -1,22 +1,52 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
|
|
3
3
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
-
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { useScrollEdgeFade } from '../../hooks/useScrollEdgeFade.js';
|
|
6
6
|
import { cn } from '../../lib/cn.js';
|
|
7
|
+
import { TAP_TARGET_HEIGHT } from '../../lib/tapTarget.js';
|
|
7
8
|
import { CardTapTarget } from '../CardTapTarget.js';
|
|
9
|
+
import { Checkbox } from '../Checkbox.js';
|
|
8
10
|
import { EmptyState } from '../EmptyState.js';
|
|
9
11
|
import { Skeleton } from '../Skeleton.js';
|
|
10
12
|
import { Surface } from '../Surface.js';
|
|
13
|
+
import { Tooltip } from '../Tooltip.js';
|
|
11
14
|
import { CardView } from './CardView.js';
|
|
12
15
|
import { CellRenderer } from './cells/CellRenderer.js';
|
|
13
16
|
import { EMPTY_FILTER_QUERY, evaluateFilterQuery, filterableText, } from './filterQuery.js';
|
|
14
|
-
import {
|
|
15
|
-
import { RowActions } from './RowActions.js';
|
|
17
|
+
import { preloadRowMenu, RowActions } from './RowActions.js';
|
|
16
18
|
import { writeRowDrag } from './rowDrag.js';
|
|
17
19
|
import { SortHeader } from './SortHeader.js';
|
|
18
|
-
import { TableToolbar } from './Toolbar.js';
|
|
19
20
|
import { resolveColumnAlign } from './types.js';
|
|
21
|
+
import { lazyPart } from './lazyPart.js';
|
|
22
|
+
// ๐ด The toolbar and the pager load only when a table DRAWS them (forge task 2309).
|
|
23
|
+
// Between them they reach Radix's Select, Menu and Popover โ ~20 kB gzip that a
|
|
24
|
+
// read-only list (`searchable={false}`, `serverWindow`, no row menu) never opens.
|
|
25
|
+
// Measured on apps/music's track list: 67 kB gzip over its entry ceiling, most of
|
|
26
|
+
// it these two parts' dependencies. A static import made every consumer pay for
|
|
27
|
+
// them. The row kebab's menu is lazy the same way, inside `RowActions`; all three
|
|
28
|
+
// are loaded ahead by `preloadDataTableParts`. `lazyPart` erases the component's
|
|
29
|
+
// type parameter, so each is given back its own signature.
|
|
30
|
+
const ToolbarPart = lazyPart(() => import('./Toolbar.js').then((m) => m.TableToolbar));
|
|
31
|
+
const PaginationPart = lazyPart(() => import('./Pagination.js').then((m) => m.TablePagination));
|
|
32
|
+
/**
|
|
33
|
+
* Load the table's lazy parts โ the toolbar, the pager, the row kebab's menu โ now,
|
|
34
|
+
* so every table mounted after this resolves draws them on its first render. For a
|
|
35
|
+
* spec setup that queries them synchronously after `render(โฆ)`, and for an app about
|
|
36
|
+
* to show a table with a toolbar. Never required: without it they arrive a tick late,
|
|
37
|
+
* into a placeholder of their own height.
|
|
38
|
+
*/
|
|
39
|
+
export async function preloadDataTableParts() {
|
|
40
|
+
await Promise.all([ToolbarPart.preload(), PaginationPart.preload(), preloadRowMenu()]);
|
|
41
|
+
}
|
|
42
|
+
/** Where the toolbar will be, the height it will be โ so its arrival shifts nothing. */
|
|
43
|
+
function ToolbarPlaceholder() {
|
|
44
|
+
return (_jsx("div", { "data-cb-table-toolbar-pending": '', "aria-hidden": true, className: 'border-b border-border px-2 py-2', children: _jsx("div", { className: cn('h-9', TAP_TARGET_HEIGHT) }) }));
|
|
45
|
+
}
|
|
46
|
+
/** Where the pager will be, the height it will be. */
|
|
47
|
+
function PaginationPlaceholder() {
|
|
48
|
+
return (_jsx("div", { "data-cb-table-pagination-pending": '', "aria-hidden": true, className: 'border-t border-border px-4 py-2.5', children: _jsx("div", { className: cn('h-8', TAP_TARGET_HEIGHT) }) }));
|
|
49
|
+
}
|
|
20
50
|
/** Build an OnChangeFn that updates local state AND notifies an optional listener. */
|
|
21
51
|
function chainUpdater(set, notify) {
|
|
22
52
|
return (updater) => set((prev) => {
|
|
@@ -45,6 +75,8 @@ function chainUpdaterResettingPage(set, setPagination, notify) {
|
|
|
45
75
|
export const AUTO_TRACK_FLOOR_PX = 120;
|
|
46
76
|
/** The trailing row-actions column is a fixed `w-20`. */
|
|
47
77
|
const ACTIONS_TRACK_PX = 80;
|
|
78
|
+
/** The leading row-selection column is a fixed `w-11` (44px โ a full tap target). */
|
|
79
|
+
const SELECT_TRACK_PX = 44;
|
|
48
80
|
/**
|
|
49
81
|
* The narrowest width at which a `fixedLayout` table still honors the tracks its
|
|
50
82
|
* columns declared: the sum of every visible column's floor (`minWidth`, else a
|
|
@@ -115,15 +147,16 @@ export function declaredTrackFloor(columns, hasActions = false) {
|
|
|
115
147
|
* been required to be stable since ES2019 and TanStack's sorted row model uses
|
|
116
148
|
* it, so rows that tie on every key in the stack keep the order they arrived in.
|
|
117
149
|
*/
|
|
118
|
-
export function nextSortStack(current, id, multi) {
|
|
150
|
+
export function nextSortStack(current, id, multi, options = {}) {
|
|
151
|
+
const first = options.descFirst ?? false;
|
|
119
152
|
const existing = current.find((sort) => sort.id === id);
|
|
120
153
|
if (!existing) {
|
|
121
|
-
const
|
|
122
|
-
return multi ? [
|
|
154
|
+
const started = { id, desc: first };
|
|
155
|
+
return multi ? [started, ...current] : [started];
|
|
123
156
|
}
|
|
124
|
-
if (
|
|
125
|
-
const flipped =
|
|
126
|
-
return multi ?
|
|
157
|
+
if (existing.desc === first || options.removable === false) {
|
|
158
|
+
const flipped = { id, desc: !existing.desc };
|
|
159
|
+
return multi ? current.map((sort) => (sort.id === id ? flipped : sort)) : [flipped];
|
|
127
160
|
}
|
|
128
161
|
return multi ? current.filter((sort) => sort.id !== id) : [];
|
|
129
162
|
}
|
|
@@ -142,6 +175,48 @@ function readPath(row, key) {
|
|
|
142
175
|
return value[part];
|
|
143
176
|
}, row);
|
|
144
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* `ColDef.hideBelow` as the container-query classes that do it. Literal strings, so the style
|
|
180
|
+
* generators (`scripts/generate*Styles.ts`) find them in the source.
|
|
181
|
+
*
|
|
182
|
+
* ๐ด The column COLLAPSES โ zero width, no padding, its content removed โ rather than
|
|
183
|
+
* `display: none`. A hidden cell is not a column, but the rows that SPAN the table (the
|
|
184
|
+
* virtualizer's spacers, a server window's placeholder rows, an expanded panel) still say
|
|
185
|
+
* `colSpan={every column}`, and a span wider than the table's real columns makes the missing
|
|
186
|
+
* ones up: they took the width the hidden columns gave back, so the table's rows stopped short
|
|
187
|
+
* of its right edge and the title โ the one width-less column โ was crushed to "Sโฆ". Measured
|
|
188
|
+
* on apps/music at 390px and 1100px, forge task 2302. Collapsed, the column count never moves,
|
|
189
|
+
* and every span stays true. (`[&>*]:hidden` takes the content out of the accessibility tree
|
|
190
|
+
* too, so a collapsed cell reads as nothing, not as a value nobody can see.)
|
|
191
|
+
*/
|
|
192
|
+
const HIDE_BELOW_CLASS = {
|
|
193
|
+
sm: '@max-2xl:w-0! @max-2xl:min-w-0! @max-2xl:p-0! @max-2xl:border-0 @max-2xl:[&>*]:hidden',
|
|
194
|
+
md: '@max-4xl:w-0! @max-4xl:min-w-0! @max-4xl:p-0! @max-4xl:border-0 @max-4xl:[&>*]:hidden',
|
|
195
|
+
lg: '@max-6xl:w-0! @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:border-0 @max-6xl:[&>*]:hidden',
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* The table's own props for a row, with the caller's `getRowProps` merged on top: classes join,
|
|
199
|
+
* styles spread, and a handler both set runs the table's first, then the caller's.
|
|
200
|
+
*/
|
|
201
|
+
export function mergeRowProps(own, extra) {
|
|
202
|
+
if (!extra)
|
|
203
|
+
return own;
|
|
204
|
+
const merged = { ...own, ...extra };
|
|
205
|
+
if (own.className || extra.className)
|
|
206
|
+
merged.className = cn(own.className, extra.className);
|
|
207
|
+
if (own.style || extra.style)
|
|
208
|
+
merged.style = { ...own.style, ...extra.style };
|
|
209
|
+
for (const [key, handler] of Object.entries(extra)) {
|
|
210
|
+
const mine = own[key];
|
|
211
|
+
if (!key.startsWith('on') || typeof handler !== 'function' || typeof mine !== 'function')
|
|
212
|
+
continue;
|
|
213
|
+
merged[key] = (event) => {
|
|
214
|
+
mine(event);
|
|
215
|
+
handler(event);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return merged;
|
|
219
|
+
}
|
|
145
220
|
/** True when a cell value carries nothing worth a column: null/undefined/''/[]. */
|
|
146
221
|
function isEmptyCellValue(v) {
|
|
147
222
|
if (v == null)
|
|
@@ -165,11 +240,15 @@ function isEmptyCellValue(v) {
|
|
|
165
240
|
* full-width accordion detail row that never breaks column alignment, and an automatic
|
|
166
241
|
* stacked-card layout under `sm` so phones are never handed a clipped grid.
|
|
167
242
|
*/
|
|
168
|
-
export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRowDelete, rowMenu, loading, emptyState, toolbar, pageSize = 50, pageSizeOptions, hidePagination, caption, className, stickyHeader, stickyFirstColumn, renderExpanded, expandedRowId, rowClassName, hideEmptyColumns, fixedLayout, minTableWidth, scrollBody, virtualize, mobileCards = true, serverSide, pageCount, rowCount, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, defaultSort, density = 'comfortable', viewMode, onViewModeChange, cardMinWidth, searchable = true, searchPlaceholder, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, }) {
|
|
243
|
+
export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRowDelete, rowMenu, loading, emptyState, toolbar, pageSize = 50, pageSizeOptions, hidePagination, caption, className, stickyHeader, stickyFirstColumn, renderExpanded, expandedRowId, rowClassName, hideEmptyColumns, fixedLayout, minTableWidth, scrollBody, virtualize, serverWindow, handleRef, scrollClassName, mobileCards = true, serverSide, pageCount, rowCount, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, defaultSort, sorting: sortingProp, enableSortingRemoval = true, sortDisabledReason, density = 'comfortable', viewMode, onViewModeChange, cardMinWidth, searchable = true, searchPlaceholder, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, selectedIds, onSelectionChange, isRowSelectable, unselectableReason, selectionLabel, selectionSummary, selectAll, getRowProps, }) {
|
|
169
244
|
// Vertical cell padding for the wide grid tier โ the one lever the density knob
|
|
170
245
|
// pulls (headers + body cells stay aligned by sharing it).
|
|
171
246
|
const cellPadY = density === 'compact' ? 'py-1.5' : 'py-2.5';
|
|
172
|
-
const [
|
|
247
|
+
const [ownSorting, setOwnSorting] = useState(defaultSort ?? []);
|
|
248
|
+
const sorting = sortingProp ?? ownSorting;
|
|
249
|
+
/** The sort as of THIS render, for a write that has to start from it (see `applySorting`). */
|
|
250
|
+
const sortingRef = useRef(sorting);
|
|
251
|
+
sortingRef.current = sorting;
|
|
173
252
|
const [columnFilters, setColumnFilters] = useState([]);
|
|
174
253
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
175
254
|
const [columnVisibility, setColumnVisibility] = useState(() => Object.fromEntries(columns.filter((c) => c.hidden).map((c) => [c.key, false])));
|
|
@@ -223,14 +302,17 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
223
302
|
* server-side table ends up sorted differently from what its chips say.
|
|
224
303
|
*/
|
|
225
304
|
const applySorting = useCallback((next) => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
305
|
+
// Computed OUTSIDE a state updater: under a controlled `sorting` there is no state to
|
|
306
|
+
// update, and a notify inside an updater fires twice in StrictMode.
|
|
307
|
+
const value = next(sortingRef.current);
|
|
308
|
+
sortingRef.current = value;
|
|
309
|
+
if (sortingProp === undefined)
|
|
310
|
+
setOwnSorting(value);
|
|
311
|
+
setPagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));
|
|
312
|
+
onSortingChange?.(value);
|
|
313
|
+
}, [onSortingChange, sortingProp]);
|
|
314
|
+
const sortDescFirstOf = useCallback((id) => columns.find((col) => col.key === id)?.sortDescFirst ?? false, [columns]);
|
|
315
|
+
const toggleSortKey = useCallback((id) => applySorting((prev) => nextSortStack(prev, id, multiSort, { descFirst: sortDescFirstOf(id), removable: enableSortingRemoval })), [applySorting, enableSortingRemoval, multiSort, sortDescFirstOf]);
|
|
234
316
|
const tsColumns = useMemo(() => liveColumns.map((col) => ({
|
|
235
317
|
id: col.key,
|
|
236
318
|
accessorKey: col.key,
|
|
@@ -241,6 +323,8 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
241
323
|
align: resolveColumnAlign(col),
|
|
242
324
|
width: col.width,
|
|
243
325
|
minWidth: col.minWidth,
|
|
326
|
+
hideBelow: col.hideBelow,
|
|
327
|
+
leading: col.leading,
|
|
244
328
|
},
|
|
245
329
|
size: typeof col.width === 'number' ? col.width : undefined,
|
|
246
330
|
enableSorting: col.sortable ?? false,
|
|
@@ -262,10 +346,10 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
262
346
|
// TanStack's column instances mid-interaction.
|
|
263
347
|
header: ({ column, table: t }) => {
|
|
264
348
|
const stack = t.getState().sorting;
|
|
265
|
-
return (_jsx(SortHeader, { column: column, label: col.label, sortable: col.sortable, sortIndex: stack.length > 1 ? stack.findIndex((s) => s.id === col.key) : -1, onSort: () => toggleSortKey(col.key) }));
|
|
349
|
+
return (_jsx(SortHeader, { column: column, label: col.label, sortable: col.sortable, sortIndex: stack.length > 1 ? stack.findIndex((s) => s.id === col.key) : -1, onSort: () => toggleSortKey(col.key), disabledReason: sortDisabledReason }));
|
|
266
350
|
},
|
|
267
351
|
cell: ({ getValue, row }) => (_jsx(CellRenderer, { col: col, value: getValue(), row: row.original, onEdit: onRowEdit ? () => onRowEdit(row.original) : undefined })),
|
|
268
|
-
})), [liveColumns, onRowEdit, toggleSortKey]);
|
|
352
|
+
})), [liveColumns, onRowEdit, sortDisabledReason, toggleSortKey]);
|
|
269
353
|
const labelOf = useCallback((id) => liveColumns.find((col) => col.key === id)?.label ?? id, [liveColumns]);
|
|
270
354
|
const controls = useMemo(() => ({
|
|
271
355
|
searchText: globalFilter,
|
|
@@ -317,16 +401,28 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
317
401
|
* failure the pager's absence hides rather than reveals, which is why it is tested by
|
|
318
402
|
* measuring `scrollHeight` in a real browser.
|
|
319
403
|
*/
|
|
320
|
-
const virtualOptions = virtualize === true ? {} : virtualize || null;
|
|
404
|
+
const virtualOptions = virtualize === true || (serverWindow && !virtualize) ? {} : virtualize || null;
|
|
321
405
|
const virtualEnabled = virtualOptions !== null;
|
|
322
406
|
const bodyScrolls = scrollBody || virtualEnabled;
|
|
323
407
|
const paginationHidden = hidePagination || virtualEnabled;
|
|
408
|
+
const selectionEnabled = selectedIds !== undefined;
|
|
409
|
+
/**
|
|
410
|
+
* Without `getRowId`, TanStack ids a row by its index in the array it was
|
|
411
|
+
* handed โ which is `filteredData`, so the builder narrowing the rows would
|
|
412
|
+
* re-number them and a selection would silently move to other rows. When
|
|
413
|
+
* selection is on, the fallback id is the row's index in `data` instead.
|
|
414
|
+
* Off, TanStack's own default stands, untouched.
|
|
415
|
+
*/
|
|
416
|
+
const dataIndex = useMemo(() => (selectionEnabled && !getRowId ? new Map(data.map((row, i) => [row, i])) : null), [data, getRowId, selectionEnabled]);
|
|
417
|
+
const resolvedGetRowId = useMemo(() => getRowId ?? (dataIndex ? (row) => String(dataIndex.get(row)) : undefined), [dataIndex, getRowId]);
|
|
324
418
|
const table = useReactTable({
|
|
325
419
|
data: filteredData,
|
|
326
420
|
columns: tsColumns,
|
|
327
|
-
getRowId,
|
|
421
|
+
getRowId: resolvedGetRowId,
|
|
328
422
|
state: { sorting, columnFilters, globalFilter: search, columnVisibility, pagination },
|
|
329
|
-
|
|
423
|
+
// Through `applySorting`, like every other write โ so a controlled `sorting` is honoured
|
|
424
|
+
// whether TanStack or a header asked for the change.
|
|
425
|
+
onSortingChange: (updater) => applySorting((prev) => (typeof updater === 'function' ? updater(prev) : updater)),
|
|
330
426
|
onColumnFiltersChange: chainUpdaterResettingPage(setColumnFilters, setPagination, onColumnFiltersChange),
|
|
331
427
|
// TanStack hands the updater whatever `state.globalFilter` is, which is now
|
|
332
428
|
// a TableSearchState; the toolbar sets TEXT, so normalise both shapes.
|
|
@@ -354,14 +450,16 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
354
450
|
return false;
|
|
355
451
|
return filterableText(row.getValue(columnId)).toLowerCase().includes(text.toLowerCase());
|
|
356
452
|
},
|
|
357
|
-
|
|
358
|
-
|
|
453
|
+
// A server window is server-ordered and server-filtered by construction: a WINDOW
|
|
454
|
+
// re-sorted locally would put row 4,000's neighbours next to row 12's.
|
|
455
|
+
manualSorting: serverSide || !!serverWindow,
|
|
456
|
+
manualFiltering: serverSide || !!serverWindow,
|
|
359
457
|
manualPagination: serverSide,
|
|
360
458
|
pageCount: serverSide ? pageCount : undefined,
|
|
361
459
|
rowCount: serverSide ? rowCount : undefined,
|
|
362
460
|
getCoreRowModel: getCoreRowModel(),
|
|
363
|
-
getSortedRowModel: serverSide ? undefined : getSortedRowModel(),
|
|
364
|
-
getFilteredRowModel: serverSide ? undefined : getFilteredRowModel(),
|
|
461
|
+
getSortedRowModel: serverSide || serverWindow ? undefined : getSortedRowModel(),
|
|
462
|
+
getFilteredRowModel: serverSide || serverWindow ? undefined : getFilteredRowModel(),
|
|
365
463
|
// ๐ด `virtualEnabled` too, not only `serverSide`. See the note where the flag is
|
|
366
464
|
// computed: a paginated virtualizer sizes its spacer from one PAGE.
|
|
367
465
|
getPaginationRowModel: serverSide || virtualEnabled ? undefined : getPaginationRowModel(),
|
|
@@ -399,17 +497,94 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
399
497
|
};
|
|
400
498
|
};
|
|
401
499
|
const rows = table.getRowModel().rows;
|
|
402
|
-
|
|
500
|
+
/**
|
|
501
|
+
* ๐ด A {@link serverWindow} row is FIXED-height, never measured. A measured list places
|
|
502
|
+
* every unloaded row at the estimate and every loaded one at its real height, so the page
|
|
503
|
+
* that arrives above the viewport re-sizes a hundred rows and moves everything under the
|
|
504
|
+
* reader โ measured in the gate: a smooth reveal of row 4,000 that paged in the rows above
|
|
505
|
+
* it on arrival came to rest thirteen rows short. apps/music's own list held every row at
|
|
506
|
+
* one height for the same reason ("Fixed row height is what makes the arithmetic jitter-free").
|
|
507
|
+
*/
|
|
508
|
+
const fixedRowHeight = serverWindow
|
|
509
|
+
? (serverWindow.rowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44))
|
|
510
|
+
: null;
|
|
511
|
+
const estimateRowHeight = fixedRowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44);
|
|
403
512
|
const scrollRef = useRef(null);
|
|
513
|
+
/** Rows before the loaded window, and the size of the whole result set โ both 0/`rows.length`
|
|
514
|
+
* without a {@link serverWindow}, which makes every index below the same number as before. */
|
|
515
|
+
const windowStart = serverWindow ? Math.max(0, serverWindow.start) : 0;
|
|
516
|
+
const windowTotal = serverWindow ? Math.max(serverWindow.total, windowStart + rows.length) : rows.length;
|
|
517
|
+
/** The caller's window callbacks, read at FIRE time: by the time a settle timer fires the
|
|
518
|
+
* window may have moved, and the fresh handler is the one that knows. */
|
|
519
|
+
const windowRef = useRef(serverWindow);
|
|
520
|
+
windowRef.current = serverWindow;
|
|
521
|
+
const loadedRef = useRef({ start: windowStart, end: windowStart + rows.length, total: windowTotal });
|
|
522
|
+
loadedRef.current = { start: windowStart, end: windowStart + rows.length, total: windowTotal };
|
|
523
|
+
const settleRef = useRef(null);
|
|
524
|
+
/** The `scrollTop` the last `onChange` saw. The virtualizer also calls `onChange` when its
|
|
525
|
+
* is-scrolling flag resets and on every re-render; only a MOVE may arm the settle timer, or
|
|
526
|
+
* the flag's own reset re-asks for the window that was just requested (measured: twice). */
|
|
527
|
+
const lastTopRef = useRef(null);
|
|
528
|
+
useEffect(() => () => {
|
|
529
|
+
if (settleRef.current)
|
|
530
|
+
clearTimeout(settleRef.current);
|
|
531
|
+
}, []);
|
|
404
532
|
const rowVirtualizer = useVirtualizer({
|
|
405
|
-
count: virtualEnabled ?
|
|
533
|
+
count: virtualEnabled ? windowTotal : 0,
|
|
406
534
|
getScrollElement: () => scrollRef.current,
|
|
407
535
|
estimateSize: () => estimateRowHeight,
|
|
408
536
|
overscan: virtualOptions?.overscan ?? 8,
|
|
537
|
+
onChange: serverWindow
|
|
538
|
+
? (instance) => {
|
|
539
|
+
const win = windowRef.current;
|
|
540
|
+
const element = instance.scrollElement;
|
|
541
|
+
if (!win || !element)
|
|
542
|
+
return;
|
|
543
|
+
const { start, end, total } = loadedRef.current;
|
|
544
|
+
// Rows over the whole result set: the first one on screen, and the one after the last.
|
|
545
|
+
const items = instance.getVirtualItems();
|
|
546
|
+
if (items.length === 0)
|
|
547
|
+
return;
|
|
548
|
+
const top = element.scrollTop;
|
|
549
|
+
const bottom = top + element.clientHeight;
|
|
550
|
+
const firstShown = items.find((item) => item.end > top)?.index ?? items[0].index;
|
|
551
|
+
const lastShown = (items.findLast((item) => item.start < bottom)?.index ?? items.at(-1).index) + 1;
|
|
552
|
+
const moved = lastTopRef.current !== top;
|
|
553
|
+
lastTopRef.current = top;
|
|
554
|
+
// Nothing on screen is loaded. Hand the destination over once the scroll RESTS,
|
|
555
|
+
// and do not also ask for a page โ it would load rows nowhere near the reader.
|
|
556
|
+
if (firstShown >= end || lastShown <= start) {
|
|
557
|
+
if (!moved)
|
|
558
|
+
return;
|
|
559
|
+
if (settleRef.current)
|
|
560
|
+
clearTimeout(settleRef.current);
|
|
561
|
+
settleRef.current = setTimeout(() => {
|
|
562
|
+
settleRef.current = null;
|
|
563
|
+
// Read at FIRE time: the window may have arrived while the scroll rested.
|
|
564
|
+
const loaded = loadedRef.current;
|
|
565
|
+
if (firstShown < loaded.end && lastShown > loaded.start)
|
|
566
|
+
return;
|
|
567
|
+
windowRef.current?.onNeedWindow?.(firstShown);
|
|
568
|
+
}, win.settleMs ?? 120);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (settleRef.current) {
|
|
572
|
+
clearTimeout(settleRef.current);
|
|
573
|
+
settleRef.current = null;
|
|
574
|
+
}
|
|
575
|
+
// Against the end of the LOADED window, never the spacer's โ see the prop doc.
|
|
576
|
+
if (win.onReachEnd && end < total && end - lastShown < (win.reachRows ?? 20))
|
|
577
|
+
win.onReachEnd();
|
|
578
|
+
// Tighter, and only off row 0: a reveal lands a few rows into a fresh window, and a
|
|
579
|
+
// generous trigger here would fire on arrival and cascade back to the top.
|
|
580
|
+
if (win.onReachStart && start > 0 && firstShown - start < (win.reachRowsUp ?? 8))
|
|
581
|
+
win.onReachStart();
|
|
582
|
+
}
|
|
583
|
+
: undefined,
|
|
409
584
|
// ๐ด MEASURED, not assumed. A wrapping cell, an expanded detail panel and the
|
|
410
585
|
// `compact` density all change a row's height, and a caller cannot be asked to
|
|
411
586
|
// compute any of them. `estimateSize` only seeds the spacer before first paint.
|
|
412
|
-
measureElement: (el) => el.getBoundingClientRect().height,
|
|
587
|
+
measureElement: (el) => fixedRowHeight ?? el.getBoundingClientRect().height,
|
|
413
588
|
});
|
|
414
589
|
/**
|
|
415
590
|
* ๐ด Invalidate the measurement cache when the row height changes.
|
|
@@ -433,13 +608,118 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
433
608
|
}, [rowVirtualizer, estimateRowHeight]);
|
|
434
609
|
const virtualRows = virtualEnabled ? rowVirtualizer.getVirtualItems() : [];
|
|
435
610
|
/** The rows to render, and the two spacers that keep the scrollbar honest about the
|
|
436
|
-
* ones that are not. Off, this is every row and no spacers โ byte-identical output.
|
|
437
|
-
|
|
611
|
+
* ones that are not. Off, this is every row and no spacers โ byte-identical output.
|
|
612
|
+
* `null` is a row a {@link serverWindow} has not loaded: a placeholder of the estimated
|
|
613
|
+
* height, so the window can land anywhere in the spacer before its rows arrive. */
|
|
614
|
+
const bodyRows = virtualEnabled
|
|
615
|
+
? virtualRows.map((v) => rows[v.index - windowStart] ?? null)
|
|
616
|
+
: rows;
|
|
617
|
+
useImperativeHandle(handleRef, () => ({
|
|
618
|
+
get scrollElement() {
|
|
619
|
+
return scrollRef.current;
|
|
620
|
+
},
|
|
621
|
+
scrollToIndex(index, options = {}) {
|
|
622
|
+
const element = scrollRef.current;
|
|
623
|
+
if (!element || !virtualEnabled)
|
|
624
|
+
return null;
|
|
625
|
+
const clamped = Math.max(0, Math.min(index, loadedRef.current.total - 1));
|
|
626
|
+
const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped * estimateRowHeight];
|
|
627
|
+
const top = Math.max(0, Math.round(offset));
|
|
628
|
+
element.scrollTo({ top, behavior: options.behavior ?? 'auto' });
|
|
629
|
+
return top;
|
|
630
|
+
},
|
|
631
|
+
}), [estimateRowHeight, rowVirtualizer, virtualEnabled]);
|
|
438
632
|
const padTop = virtualEnabled ? (virtualRows[0]?.start ?? 0) : 0;
|
|
439
633
|
const padBottom = virtualEnabled
|
|
440
634
|
? rowVirtualizer.getTotalSize() - (virtualRows.at(-1)?.end ?? 0)
|
|
441
635
|
: 0;
|
|
442
636
|
const hasActions = !!(onRowEdit || onRowDelete || rowMenu);
|
|
637
|
+
// โโ Row selection (see the `selectedIds` prop doc) โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
638
|
+
const canSelect = (row) => isRowSelectable?.(row) ?? true;
|
|
639
|
+
// Select-all's universe: the FILTERED rows across every page, minus the ones
|
|
640
|
+
// that cannot be selected. `getFilteredRowModel` is pre-pagination by design.
|
|
641
|
+
const selectableFilteredIds = selectionEnabled
|
|
642
|
+
? table
|
|
643
|
+
.getFilteredRowModel()
|
|
644
|
+
.rows.filter((row) => canSelect(row.original))
|
|
645
|
+
.map((row) => row.id)
|
|
646
|
+
: [];
|
|
647
|
+
const selectedFilteredCount = selectionEnabled
|
|
648
|
+
? selectableFilteredIds.filter((id) => selectedIds.has(id)).length
|
|
649
|
+
: 0;
|
|
650
|
+
const headerChecked = selectedFilteredCount === 0
|
|
651
|
+
? false
|
|
652
|
+
: selectedFilteredCount === selectableFilteredIds.length
|
|
653
|
+
? true
|
|
654
|
+
: 'indeterminate';
|
|
655
|
+
const toggleRowSelected = (id) => {
|
|
656
|
+
if (!selectedIds)
|
|
657
|
+
return;
|
|
658
|
+
const next = new Set(selectedIds);
|
|
659
|
+
if (next.has(id))
|
|
660
|
+
next.delete(id);
|
|
661
|
+
else
|
|
662
|
+
next.add(id);
|
|
663
|
+
onSelectionChange?.(next);
|
|
664
|
+
};
|
|
665
|
+
const toggleAllSelected = () => {
|
|
666
|
+
if (!selectedIds)
|
|
667
|
+
return;
|
|
668
|
+
const next = new Set(selectedIds);
|
|
669
|
+
if (headerChecked === true)
|
|
670
|
+
for (const id of selectableFilteredIds)
|
|
671
|
+
next.delete(id);
|
|
672
|
+
else
|
|
673
|
+
for (const id of selectableFilteredIds)
|
|
674
|
+
next.add(id);
|
|
675
|
+
onSelectionChange?.(next);
|
|
676
|
+
};
|
|
677
|
+
const labelForSelection = (row) => {
|
|
678
|
+
if (selectionLabel)
|
|
679
|
+
return selectionLabel(row);
|
|
680
|
+
const first = liveColumns[0];
|
|
681
|
+
const value = first ? readPath(row, first.key) : undefined;
|
|
682
|
+
const text = first?.displayValue ? first.displayValue(value, row) : filterableText(value);
|
|
683
|
+
return `Select ${text || 'row'}`;
|
|
684
|
+
};
|
|
685
|
+
const selectAllLabel = `Select all ${selectableFilteredIds.length} rows`;
|
|
686
|
+
/** One row's checkbox โ the same control in every tier. */
|
|
687
|
+
const rowCheckbox = (row) => {
|
|
688
|
+
if (!selectedIds)
|
|
689
|
+
return null;
|
|
690
|
+
const selectable = canSelect(row.original);
|
|
691
|
+
const box = (_jsx(Checkbox, { checked: selectedIds.has(row.id), disabled: !selectable, onCheckedChange: () => toggleRowSelected(row.id), "aria-label": labelForSelection(row.original) }));
|
|
692
|
+
if (selectable)
|
|
693
|
+
return box;
|
|
694
|
+
// A disabled button fires no pointer events, so the reason hangs off a
|
|
695
|
+
// wrapper โ visible and explained, never hidden (the owner UI rule).
|
|
696
|
+
return (_jsx(Tooltip, { content: unselectableReason?.(row.original) ?? "This row can't be selected", mobile: 'tap', children: _jsx("span", { "data-cb-unselectable": '', className: 'inline-flex', children: box }) }));
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* The whole cell toggles, not just the 16px box โ generous hit target. A click
|
|
700
|
+
* that landed on the checkbox's own `<label>` has already toggled it, so only
|
|
701
|
+
* the padding around it is handled here. Propagation stops either way so a
|
|
702
|
+
* selection click is never also the row click.
|
|
703
|
+
*/
|
|
704
|
+
const selectCellProps = (row) => ({
|
|
705
|
+
'data-cb-select-cell': '',
|
|
706
|
+
onClick: (event) => {
|
|
707
|
+
event.stopPropagation();
|
|
708
|
+
if (event.target.closest('label'))
|
|
709
|
+
return;
|
|
710
|
+
if (canSelect(row.original))
|
|
711
|
+
toggleRowSelected(row.id);
|
|
712
|
+
},
|
|
713
|
+
// Space/Enter on the focused checkbox must not bubble into the row's
|
|
714
|
+
// Enter-to-open handler.
|
|
715
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
716
|
+
});
|
|
717
|
+
const headerCheckbox = (label) => selectAll ? (_jsx(Tooltip, { content: selectAll.label, children: _jsx("span", { className: 'inline-flex', children: _jsx(Checkbox, { checked: selectAll.checked, disabled: selectAll.disabled, onCheckedChange: (checked) => selectAll.onChange(checked === true), "aria-label": selectAll.label, label: label, labelClassName: label == null ? undefined : 'text-xs text-muted-foreground' }) }) })) : (_jsx(Checkbox, { checked: headerChecked, disabled: selectableFilteredIds.length === 0, onCheckedChange: toggleAllSelected, "aria-label": selectAllLabel, label: label, labelClassName: label == null ? undefined : 'text-xs text-muted-foreground' }));
|
|
718
|
+
const summaryNode = selectedIds && selectionSummary
|
|
719
|
+
? selectionSummary(resolvedGetRowId
|
|
720
|
+
? data.filter((row) => selectedIds.has(resolvedGetRowId(row)))
|
|
721
|
+
: [])
|
|
722
|
+
: null;
|
|
443
723
|
const toolbarNode = typeof toolbar === 'function'
|
|
444
724
|
? toolbar(table)
|
|
445
725
|
: (toolbar ??
|
|
@@ -450,8 +730,8 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
450
730
|
// passes its own gets exactly that, and `TableToolbar` reads the same
|
|
451
731
|
// controls off `table.options.meta`, so composing a toolbar is never
|
|
452
732
|
// the thing that costs you the search box.
|
|
453
|
-
(searchable ? _jsx(
|
|
454
|
-
const visibleColumnCount = table.getVisibleLeafColumns().length + (hasActions ? 1 : 0);
|
|
733
|
+
(searchable ? (_jsx(ToolbarPart, { table: table, searchPlaceholder: searchPlaceholder, fallback: _jsx(ToolbarPlaceholder, {}) })) : null));
|
|
734
|
+
const visibleColumnCount = table.getVisibleLeafColumns().length + (hasActions ? 1 : 0) + (selectionEnabled ? 1 : 0);
|
|
455
735
|
// Cards track the SAME live visibility state as the grid (ViewOptionsMenu toggles
|
|
456
736
|
// apply to both) โ not just the ColDefs' initial `hidden` flags.
|
|
457
737
|
const cardColumns = liveColumns.filter((c) => table.getColumn(c.key)?.getIsVisible() ?? true);
|
|
@@ -464,9 +744,16 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
464
744
|
// columns missing rather than one that scrolls (`useScrollEdgeFade`).
|
|
465
745
|
const gridFade = useScrollEdgeFade();
|
|
466
746
|
const tableMinWidth = fixedLayout
|
|
467
|
-
? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns, hasActions))
|
|
747
|
+
? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns, hasActions) + (selectionEnabled ? SELECT_TRACK_PX : 0))
|
|
468
748
|
: minTableWidth;
|
|
469
|
-
return (_jsxs("div", { className: cn('@container flex min-h-0 flex-col', className), children: [toolbarNode, loading ? (_jsx("div", { className: 'flex flex-col gap-2 p-4', children: Array.from({ length: 5 }).map((_, i) => (_jsx(Skeleton, { className: 'h-10 w-full' }, `dt-skeleton-${i}`))) })) : rows.length === 0 ? (_jsx("div", { className: 'p-2', children: emptyState ?? _jsx(EmptyState, { title: 'No results' }) })) : (_jsxs("div", { ref: scrollRef,
|
|
749
|
+
return (_jsxs("div", { className: cn('@container flex min-h-0 flex-col', className), children: [toolbarNode, summaryNode != null ? (_jsx("div", { "data-cb-table-selection-summary": '', "aria-live": 'polite', className: 'flex min-h-11 flex-wrap items-center gap-3 border-b border-border px-4 py-2 text-sm', children: summaryNode })) : null, loading ? (_jsx("div", { className: 'flex flex-col gap-2 p-4', children: Array.from({ length: 5 }).map((_, i) => (_jsx(Skeleton, { className: 'h-10 w-full' }, `dt-skeleton-${i}`))) })) : rows.length === 0 && windowTotal === 0 ? (_jsx("div", { className: 'p-2', children: emptyState ?? _jsx(EmptyState, { title: 'No results' }) })) : (_jsxs("div", { ref: scrollRef, "data-cb-table-scroll": bodyScrolls ? '' : undefined,
|
|
750
|
+
// guardrails-ignore no-raw-overflow: dual-axis table scroll container โ sticky header pins to it; ScrollArea cannot own horizontal table scroll.
|
|
751
|
+
className: cn(bodyScrolls && 'min-h-0 flex-1 overflow-auto', scrollClassName), children: [selectionEnabled && (viewMode === 'card' || mobileCards) ? (_jsx("div", { "data-cb-table-select-all": '', className: cn('flex items-center px-2 py-1', viewMode !== 'card' && '@xl:hidden'), children: headerCheckbox('Select all') })) : null, viewMode === 'card' ? (_jsx(CardView, { columns: cardColumns, data: rows.map((row) => row.original), getRowId: getRowId, onRowClick: onRowClick, dragPropsFor: getRowDrag ? dragPropsFor : undefined, selectFor: selectionEnabled
|
|
752
|
+
? (_row, index) => {
|
|
753
|
+
const tsRow = rows[index];
|
|
754
|
+
return tsRow ? rowCheckbox(tsRow) : null;
|
|
755
|
+
}
|
|
756
|
+
: undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", { "data-cb-table-cards": '', className: 'flex flex-col gap-3 @xl:hidden', children: rows.map((row) => (_jsx(MobileRowCard, { row: row, columns: cardColumns, className: rowClassName?.(row.original), onRowClick: onRowClick, dragProps: mergeRowProps(dragPropsFor(row.original), getRowProps?.(row.original, windowStart + row.index)), select: selectionEnabled ? rowCheckbox(row) : undefined, actions: hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) })) : null, expanded: renderExpanded && expandedRowId != null && row.id === expandedRowId
|
|
470
757
|
? renderExpanded(row.original)
|
|
471
758
|
: null }, row.id))) })), mobileCards && (_jsx("div", { "data-cb-table-list": '', className: 'hidden @xl:block @3xl:hidden', children: (() => {
|
|
472
759
|
const [title, ...rest] = cardColumns;
|
|
@@ -488,49 +775,67 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
488
775
|
// absent (the header) and every preceding lane drifts ~32px off
|
|
489
776
|
// its label โ the v4 judge's tablet misalignment finding.
|
|
490
777
|
const template = [
|
|
778
|
+
...(selectionEnabled ? ['44px'] : []),
|
|
491
779
|
'minmax(0,1fr)',
|
|
492
780
|
...rest.map(trackOf),
|
|
493
781
|
...(hasActions ? ['32px'] : []),
|
|
494
782
|
].join(' ');
|
|
495
|
-
const header = (_jsxs("div", { "data-cb-table-list-header": '', className: 'grid items-center gap-3 border-b border-border px-4 py-2 text-caption font-bold uppercase tracking-wider text-muted-foreground', style: { gridTemplateColumns: template }, children: [_jsx("span", { children: title?.label ?? '' }), rest.map((col) => (_jsx("span", { className: cn(resolveColumnAlign(col) === 'right' && 'text-right'), children: col.label }, col.key))), hasActions ? _jsx("span", { "aria-hidden": true }) : null] }));
|
|
783
|
+
const header = (_jsxs("div", { "data-cb-table-list-header": '', className: 'grid items-center gap-3 border-b border-border px-4 py-2 text-caption font-bold uppercase tracking-wider text-muted-foreground', style: { gridTemplateColumns: template }, children: [selectionEnabled ? _jsx("span", { className: 'flex justify-center', children: headerCheckbox() }) : null, _jsx("span", { children: title?.label ?? '' }), rest.map((col) => (_jsx("span", { className: cn(resolveColumnAlign(col) === 'right' && 'text-right'), children: col.label }, col.key))), hasActions ? _jsx("span", { "aria-hidden": true }) : null] }));
|
|
496
784
|
const body = rows.map((row) => {
|
|
497
785
|
const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
|
|
498
|
-
return (_jsxs(Fragment, { children: [_jsxs("div", { ...
|
|
786
|
+
return (_jsxs(Fragment, { children: [_jsxs("div", { ...mergeRowProps({
|
|
787
|
+
...dragPropsFor(row.original),
|
|
788
|
+
className: cn('grid items-center gap-3 px-4 py-3 transition-colors hover:bg-muted/40', getRowDrag && 'cursor-grab active:cursor-grabbing', rowClassName?.(row.original)),
|
|
789
|
+
style: { gridTemplateColumns: template },
|
|
790
|
+
}, getRowProps?.(row.original, windowStart + row.index)), children: [selectionEnabled ? (_jsx("div", { ...selectCellProps(row), className: '-my-3 flex self-stretch items-center justify-center', children: rowCheckbox(row) })) : null, _jsx("div", { className: 'min-w-0', children: title ? (_jsx(CellRenderer, { col: title, value: row.original[title.key], row: row.original })) : null }), rest.map((col) => (_jsx("div", { className: cn(resolveColumnAlign(col) === 'right' && 'justify-self-end text-right'), children: _jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }) }, col.key))), hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) })) : null] }), isExpanded ? (_jsx("div", { className: 'bg-muted/20 px-4 pt-1 pb-3', children: _jsx("div", { className: 'rounded-lg border border-border bg-card p-3 shadow-raised', children: renderExpanded(row.original) }) })) : null] }, row.id));
|
|
499
791
|
});
|
|
500
792
|
return (_jsxs(_Fragment, { children: [header, _jsx("div", { className: 'divide-y divide-border', children: body })] }));
|
|
501
793
|
})() })), _jsx("div", { ref: bodyScrolls ? undefined : gridFade.ref, onScroll: bodyScrolls ? undefined : gridFade.onScroll, style: bodyScrolls ? undefined : gridFade.maskStyle,
|
|
502
794
|
// guardrails-ignore no-raw-overflow: content-sized horizontal table scroll, not a page-level vertical scroll region โ see reasoning above. Under scrollBody the OUTER wrapper owns both axes (a nested overflow ancestor would un-stick the sticky header).
|
|
503
|
-
className: cn(!bodyScrolls && 'overflow-x-auto', mobileCards && 'hidden @3xl:block'), children: _jsxs("table", { className: cn('w-full border-collapse text-sm', fixedLayout && 'table-fixed'), style: tableMinWidth != null ? { minWidth: tableMinWidth } : undefined, children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { children: table.getHeaderGroups().map((hg) => (_jsxs("tr", { children: [
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
? '
|
|
513
|
-
:
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
795
|
+
className: cn(!bodyScrolls && 'overflow-x-auto', mobileCards && 'hidden @3xl:block'), children: _jsxs("table", { className: cn('w-full border-collapse text-sm', fixedLayout && 'table-fixed'), style: tableMinWidth != null ? { minWidth: tableMinWidth } : undefined, children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { children: table.getHeaderGroups().map((hg) => (_jsxs("tr", { children: [(() => {
|
|
796
|
+
// `ColDef.leading` columns sit BEFORE the checkbox column; see its doc.
|
|
797
|
+
const renderHeader = (h, headerIndex) => {
|
|
798
|
+
const sorted = h.column.getIsSorted();
|
|
799
|
+
const meta = h.column.columnDef.meta;
|
|
800
|
+
const width = meta?.width ?? h.column.columnDef.size;
|
|
801
|
+
const align = meta?.align;
|
|
802
|
+
const frozen = stickyFirstColumn === true && headerIndex === 0;
|
|
803
|
+
return (_jsx("th", { scope: 'col', "aria-sort": sorted === 'asc'
|
|
804
|
+
? 'ascending'
|
|
805
|
+
: sorted === 'desc'
|
|
806
|
+
? 'descending'
|
|
807
|
+
: undefined, style: width != null || meta?.minWidth != null
|
|
808
|
+
? { width, minWidth: meta?.minWidth }
|
|
809
|
+
: undefined, className: cn(
|
|
810
|
+
// Metadata treatment: headers are chrome, not data โ small,
|
|
811
|
+
// bold, uppercase, tracked, muted (STANDARDS ยง5c) โ via the
|
|
812
|
+
// explicit secondary token, never an opacity fade (the
|
|
813
|
+
// design manual bans opacity-blended text). The border
|
|
814
|
+
// rides the <th> (not the <tr>) so it pins with stickyHeader.
|
|
815
|
+
'border-b border-border px-4 text-caption font-bold uppercase tracking-wider text-muted-foreground', cellPadY, align === 'right' ? 'text-right' : 'text-left',
|
|
816
|
+
// Sticky pins per-<th> (a <tr> can't stick); needs its own
|
|
817
|
+
// near-opaque bg so rows scrolling beneath never bleed through.
|
|
818
|
+
stickyHeader &&
|
|
819
|
+
'sticky top-0 z-10 bg-card/95 backdrop-blur-sm',
|
|
820
|
+
// The corner cell must outrank BOTH pinned axes, or the
|
|
821
|
+
// next header slides over the frozen one. It is opaque
|
|
822
|
+
// for the same reason the body cells are.
|
|
823
|
+
frozen &&
|
|
824
|
+
(selectionEnabled
|
|
825
|
+
? 'sticky left-11 bg-card shadow-[1px_0_0_0_var(--color-border)]'
|
|
826
|
+
: 'sticky left-0 bg-card shadow-[1px_0_0_0_var(--color-border)]'), frozen && (stickyHeader ? 'z-30' : 'z-20'), meta?.hideBelow && HIDE_BELOW_CLASS[meta.hideBelow]), children: h.isPlaceholder
|
|
827
|
+
? null
|
|
828
|
+
: flexRender(h.column.columnDef.header, h.getContext()) }, h.id));
|
|
829
|
+
};
|
|
830
|
+
const isLeading = (h) => h.column.columnDef.meta?.leading === true;
|
|
831
|
+
return (_jsxs(_Fragment, { children: [hg.headers.filter(isLeading).map((h) => renderHeader(h, -1)), selectionEnabled ? (_jsx("th", { scope: 'col', className: cn('w-11 border-b border-border px-0 text-center', stickyHeader && 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm', stickyFirstColumn && 'sticky left-0 bg-card', stickyFirstColumn && (stickyHeader ? 'z-30' : 'z-20')), children: headerCheckbox() })) : null, hg.headers.filter((h) => !isLeading(h)).map(renderHeader)] }));
|
|
832
|
+
})(), hasActions ? (_jsx("th", { className: cn('w-20 border-b border-border px-2', stickyHeader && 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm'), "aria-label": 'Row actions' })) : null] }, hg.id))) }), _jsxs("tbody", { children: [padTop > 0 ? (_jsx("tr", { "aria-hidden": true, style: { height: padTop }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null, bodyRows.map((row, sliceIndex) => {
|
|
833
|
+
if (row === null) {
|
|
834
|
+
// A row the server window has not loaded yet: the estimated height, so
|
|
835
|
+
// the spacer arithmetic holds, and a skeleton line rather than a gap.
|
|
836
|
+
const index = virtualRows[sliceIndex].index;
|
|
837
|
+
return (_jsx("tr", { "data-index": index, "data-cb-placeholder": '', "aria-hidden": true, className: cn('border-b border-border', index % 2 === 1 && 'bg-muted/50'), style: { height: estimateRowHeight }, children: _jsx("td", { colSpan: visibleColumnCount, className: 'px-3', children: _jsx(Skeleton, { className: 'h-3', style: { width: '40%' } }) }) }, `cb-placeholder-${index}`));
|
|
838
|
+
}
|
|
534
839
|
/* ๐ด The ZEBRA index is the row's ABSOLUTE position, not its
|
|
535
840
|
position in the rendered slice. Off by that, the stripes
|
|
536
841
|
would repaint themselves every time the window moved โ the
|
|
@@ -538,53 +843,78 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
538
843
|
fault rather than as a guide. */
|
|
539
844
|
const rowIndex = virtualEnabled ? virtualRows[sliceIndex].index : sliceIndex;
|
|
540
845
|
const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
|
|
541
|
-
return (_jsxs(Fragment, { children: [_jsxs("tr", {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
'cursor-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
846
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", {
|
|
847
|
+
// ๐ด The virtualizer reads its real height back off this node, and
|
|
848
|
+
// `data-index` is how it knows WHICH row it measured โ without it
|
|
849
|
+
// every measurement lands on index 0 and the spacer never grows.
|
|
850
|
+
// (The ref stays OUT of the merge: `getRowProps` cannot take it.)
|
|
851
|
+
ref: virtualEnabled && fixedRowHeight === null ? rowVirtualizer.measureElement : undefined, ...mergeRowProps({
|
|
852
|
+
...(virtualEnabled ? { 'data-index': rowIndex } : {}),
|
|
853
|
+
style: fixedRowHeight === null ? undefined : { height: fixedRowHeight },
|
|
854
|
+
'data-expanded': isExpanded || undefined,
|
|
855
|
+
...dragPropsFor(row.original),
|
|
856
|
+
className: cn(getRowDrag && 'cursor-grab active:cursor-grabbing',
|
|
857
|
+
// Always-on hover wash: wide rows need a trace line for the
|
|
858
|
+
// eye even when the row itself isn't clickable. Zebra fill on
|
|
859
|
+
// every second row gives the eye a guide path across wide
|
|
860
|
+
// columns (design manual ยง4 zebra-vs-outline rule) โ indexed
|
|
861
|
+
// off the DATA row, not DOM position, so an expansion row
|
|
862
|
+
// never flips the rhythm.
|
|
863
|
+
'border-b border-border transition-colors last:border-0 hover:bg-muted', rowIndex % 2 === 1 && 'bg-muted/50', rowClassName?.(row.original), isExpanded && 'border-0 bg-muted/30', onRowClick &&
|
|
864
|
+
'cursor-pointer hover:bg-muted focus-visible:bg-muted focus-visible:outline-none'),
|
|
865
|
+
tabIndex: onRowClick ? 0 : undefined,
|
|
866
|
+
onClick: onRowClick ? (event) => onRowClick(row.original, event) : undefined,
|
|
867
|
+
onKeyDown: onRowClick
|
|
868
|
+
? (e) => {
|
|
869
|
+
if (e.key === 'Enter')
|
|
870
|
+
onRowClick(row.original);
|
|
871
|
+
}
|
|
872
|
+
: undefined,
|
|
873
|
+
}, getRowProps?.(row.original, rowIndex)), children: [(() => {
|
|
874
|
+
const cells = row.getVisibleCells();
|
|
875
|
+
const isLeading = (cell) => cell.column.columnDef.meta?.leading === true;
|
|
876
|
+
const renderCell = (cell, cellIndex) => {
|
|
877
|
+
const frozen = stickyFirstColumn === true && cellIndex === 0;
|
|
878
|
+
return (_jsx("td", { style: frozen
|
|
879
|
+
? {
|
|
880
|
+
// The zebra step, made OPAQUE โ see the prop doc.
|
|
881
|
+
// A custom property rather than `background:`
|
|
882
|
+
// directly, so the value survives any CSS parser
|
|
883
|
+
// that has not learned `color-mix` (happy-dom) and
|
|
884
|
+
// the gate can read it back.
|
|
885
|
+
'--cb-frozen-bg': rowIndex % 2 === 1
|
|
886
|
+
? 'color-mix(in srgb, var(--color-muted) 50%, var(--color-card))'
|
|
887
|
+
: 'var(--color-card)',
|
|
888
|
+
}
|
|
889
|
+
: undefined, className: cn('px-4 align-middle', cellPadY, frozen &&
|
|
890
|
+
(selectionEnabled
|
|
891
|
+
? 'sticky left-11 z-10 bg-[var(--cb-frozen-bg)] shadow-[1px_0_0_0_var(--color-border)]'
|
|
892
|
+
: 'sticky left-0 z-10 bg-[var(--cb-frozen-bg)] shadow-[1px_0_0_0_var(--color-border)]'),
|
|
893
|
+
// A rigid track cannot widen for its content, so an
|
|
894
|
+
// unbreakable token (a task id, a path, a hash) would
|
|
895
|
+
// otherwise run straight across the next column's value.
|
|
896
|
+
// Break inside the word instead โ the row grows taller,
|
|
897
|
+
// which is recoverable; overlapping text is not. Not
|
|
898
|
+
// `overflow: hidden`: a cell may host a menu/tooltip that
|
|
899
|
+
// has to escape its bounds.
|
|
900
|
+
fixedLayout && '[overflow-wrap:anywhere]', cell.column.columnDef.meta?.align === 'right' && 'text-right', cell.column.columnDef.meta?.hideBelow &&
|
|
901
|
+
HIDE_BELOW_CLASS[cell.column.columnDef.meta.hideBelow]), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
902
|
+
};
|
|
903
|
+
return (_jsxs(_Fragment, { children: [cells.filter(isLeading).map((cell) => renderCell(cell, -1)), selectionEnabled ? (_jsx("td", { ...selectCellProps(row), style: stickyFirstColumn
|
|
904
|
+
? {
|
|
905
|
+
'--cb-frozen-bg': rowIndex % 2 === 1
|
|
906
|
+
? 'color-mix(in srgb, var(--color-muted) 50%, var(--color-card))'
|
|
907
|
+
: 'var(--color-card)',
|
|
908
|
+
}
|
|
909
|
+
: undefined, className: cn('w-11 cursor-pointer px-0 text-center align-middle', stickyFirstColumn && 'sticky left-0 z-10 bg-[var(--cb-frozen-bg)]'), children: rowCheckbox(row) })) : null, cells.filter((cell) => !isLeading(cell)).map(renderCell)] }));
|
|
910
|
+
})(), hasActions ? (_jsx("td", { className: 'px-2 py-2', children: _jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) }) })) : null] }), isExpanded ? (_jsx("tr", { className: 'border-b border-border bg-muted/20', ref: (el) => {
|
|
581
911
|
if (el && scrolledExpansionRef.current !== row.id) {
|
|
582
912
|
scrolledExpansionRef.current = row.id;
|
|
583
913
|
// A row expanded near the fold must reveal its panel.
|
|
584
914
|
el.scrollIntoView({ block: 'nearest' });
|
|
585
915
|
}
|
|
586
916
|
}, children: _jsx("td", { colSpan: visibleColumnCount, className: 'p-0', children: _jsx("div", { className: 'animate-in fade-in slide-in-from-top-1 px-4 pt-1 pb-3 duration-200', children: _jsx("div", { className: 'rounded-lg border border-border bg-card p-3 shadow-raised', children: renderExpanded(row.original) }) }) }) })) : null] }, row.id));
|
|
587
|
-
}), padBottom > 0 ? (_jsx("tr", { "aria-hidden": true, style: { height: padBottom }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null] })] }) })] })), bodyScrolls && (_jsx("div", { "aria-hidden": true, className: 'pointer-events-none sticky bottom-0 -mt-6 h-6 w-full bg-gradient-to-t from-card to-transparent' }))] })), !loading && rows.length > 0 && !paginationHidden ? (_jsx(
|
|
917
|
+
}), padBottom > 0 ? (_jsx("tr", { "aria-hidden": true, style: { height: padBottom }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null] })] }) })] })), bodyScrolls && (_jsx("div", { "aria-hidden": true, className: 'pointer-events-none sticky bottom-0 -mt-6 h-6 w-full bg-gradient-to-t from-card to-transparent' }))] })), !loading && rows.length > 0 && !paginationHidden ? (_jsx(PaginationPart, { table: table, pageSizeOptions: pageSizeOptions, fallback: _jsx(PaginationPlaceholder, {}) })) : null] }));
|
|
588
918
|
}
|
|
589
919
|
/**
|
|
590
920
|
* One row rendered as a phone card: first visible column = the title zone (unlabeled,
|
|
@@ -593,7 +923,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
593
923
|
* anchored bottom-right inside the card bounds. Values render through the SAME
|
|
594
924
|
* {@link CellRenderer} registry as the grid, so a custom cell shows identically here.
|
|
595
925
|
*/
|
|
596
|
-
function MobileRowCard({ row, columns, className, onRowClick, dragProps, actions, expanded, }) {
|
|
926
|
+
function MobileRowCard({ row, columns, className, onRowClick, dragProps, select, actions, expanded, }) {
|
|
597
927
|
const visible = columns.filter((c) => !c.hidden);
|
|
598
928
|
const [title, ...rest] = visible;
|
|
599
929
|
const footer = rest.filter((c) => c.cardFooter || c.label === '');
|
|
@@ -606,6 +936,7 @@ function MobileRowCard({ row, columns, className, onRowClick, dragProps, actions
|
|
|
606
936
|
// glance pairs identity with state; the rest of the inline strip flows below.
|
|
607
937
|
const [titleSideCol, ...restInline] = inline;
|
|
608
938
|
const body = (_jsxs("div", { className: cn('flex flex-col gap-2', actions && 'pr-10'), children: [title ? (_jsxs("div", { className: 'flex items-start justify-between gap-2 text-sm', children: [_jsx("div", { className: 'min-w-0 flex-1', children: _jsx(CellRenderer, { col: title, value: row.original[title.key], row: row.original }) }), titleSideCol ? (_jsx("span", { className: 'shrink-0', children: _jsx(CellRenderer, { col: titleSideCol, value: row.original[titleSideCol.key], row: row.original }) })) : null] })) : null, restInline.length > 0 && (_jsx("div", { className: 'flex flex-wrap items-center gap-2', children: restInline.map((col) => (_jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }, col.key))) })), detail.length > 0 && (_jsx("dl", { className: 'flex flex-col gap-1.5', children: detail.map((col) => (_jsxs("div", { className: 'flex items-baseline justify-between gap-3', children: [_jsx("dt", { className: 'cb-meta-label shrink-0', children: col.label }), _jsx("dd", { className: 'min-w-0 text-right text-sm', children: _jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }) })] }, col.key))) }))] }));
|
|
609
|
-
|
|
939
|
+
const main = onRowClick ? (_jsx(CardTapTarget, { onClick: () => onRowClick(row.original), className: '-m-1 rounded-md p-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', children: body })) : (body);
|
|
940
|
+
return (_jsxs(Surface, { level: 'raised', pad: 'sm', radius: 'lg', ...dragProps, className: cn('relative isolate', dragProps?.draggable && 'cursor-grab', className, dragProps?.className), children: [actions ? _jsx("div", { className: 'absolute top-3 right-3 z-10', children: actions }) : null, select == null ? main : (_jsxs("div", { className: 'flex items-start gap-1', children: [_jsx("div", { className: '-mt-2 -ml-2 shrink-0', children: select }), _jsx("div", { className: 'min-w-0 flex-1', children: main })] })), footer.length > 0 && (_jsx("div", { className: 'mt-3 flex items-center justify-end gap-2 border-t border-border/60 pt-3', children: footer.map((col) => (_jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }, col.key))) })), expanded ? _jsx("div", { className: 'mt-4', children: expanded }) : null] }));
|
|
610
941
|
}
|
|
611
942
|
//# sourceMappingURL=DataTable.js.map
|