@jielga/tmdatagrid 0.1.0 → 0.2.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/README.md +35 -2
- package/dist/index.d.ts +442 -337
- package/dist/index.js +811 -694
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +15 -7
- package/skills/columns/SKILL.md +8 -7
- package/skills/features/SKILL.md +104 -16
- package/skills/getting-started/SKILL.md +39 -16
- package/skills/options/SKILL.md +12 -7
- package/skills/server-side/SKILL.md +5 -2
- package/src/tmdatagrid/TMDataGridContext.ts +1 -1
- package/src/tmdatagrid/components/TMDataGrid.module.css +63 -0
- package/src/tmdatagrid/{TMDataGrid.tsx → components/TMDataGrid.tsx} +9 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.module.css +25 -0
- package/src/tmdatagrid/{TMDataGridColumnsPanel.tsx → components/TMDataGridColumnsPanel.tsx} +3 -3
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +20 -0
- package/src/tmdatagrid/{TMDataGridFilterPanel.tsx → components/TMDataGridFilterPanel.tsx} +4 -4
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +10 -0
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +143 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +153 -0
- package/src/tmdatagrid/{TMDataGridHeaderCell.tsx → components/TMDataGridHeaderCell.tsx} +22 -13
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +100 -0
- package/src/tmdatagrid/components/TMDataGridTable.module.css +103 -0
- package/src/tmdatagrid/{TMDataGridTable.tsx → components/TMDataGridTable.tsx} +79 -20
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +12 -0
- package/src/tmdatagrid/{TMDataGridToolbar.tsx → components/TMDataGridToolbar.tsx} +5 -5
- package/src/tmdatagrid/components/sticky.module.css +45 -0
- package/src/tmdatagrid/{capabilities.ts → core/capabilities.ts} +30 -2
- package/src/tmdatagrid/{columnOrdering.ts → core/columnOrdering.ts} +2 -2
- package/src/tmdatagrid/{columnUtils.ts → core/columnUtils.ts} +1 -1
- package/src/tmdatagrid/{persistence.ts → core/persistence.ts} +80 -2
- package/src/tmdatagrid/index.ts +15 -9
- package/src/tmdatagrid/useTMDataGrid.tsx +102 -55
- package/src/tmdatagrid/TMDataGrid.module.css +0 -410
- package/src/tmdatagrid/TMDataGridFooter.tsx +0 -74
- /package/src/tmdatagrid/{icons.ts → components/icons.ts} +0 -0
- /package/src/tmdatagrid/{filterOperators.ts → core/filterOperators.ts} +0 -0
- /package/src/tmdatagrid/{sizes.ts → core/sizes.ts} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Checkbox } from "@mantine/core";
|
|
2
1
|
import { useCreateStore } from "@tanstack/react-store";
|
|
3
2
|
import {
|
|
4
3
|
type ColumnDef,
|
|
@@ -27,6 +26,7 @@ import {
|
|
|
27
26
|
type Table,
|
|
28
27
|
tableFeatures,
|
|
29
28
|
type TableOptions,
|
|
29
|
+
type TableState,
|
|
30
30
|
useTable,
|
|
31
31
|
} from "@tanstack/react-table";
|
|
32
32
|
import type { Store } from "@tanstack/store";
|
|
@@ -35,16 +35,29 @@ import {
|
|
|
35
35
|
type TMDataGridColumnType,
|
|
36
36
|
tmDataGridFilterFn,
|
|
37
37
|
getDefaultOperator,
|
|
38
|
-
} from "./filterOperators";
|
|
38
|
+
} from "./core/filterOperators";
|
|
39
39
|
import {
|
|
40
40
|
hasPersistenceKeys,
|
|
41
41
|
readPersistedState,
|
|
42
42
|
type TMDataGridPersistence,
|
|
43
43
|
writePersistedState,
|
|
44
|
-
} from "./persistence";
|
|
45
|
-
import {
|
|
44
|
+
} from "./core/persistence";
|
|
45
|
+
import {
|
|
46
|
+
readFeatureFlags,
|
|
47
|
+
type TMDataGridFeatureFlags,
|
|
48
|
+
type TMDataGridRowSelectionMode,
|
|
49
|
+
} from "./core/capabilities";
|
|
50
|
+
import {
|
|
51
|
+
createSelectColumn,
|
|
52
|
+
SELECT_COLUMN_ID,
|
|
53
|
+
} from "./components/TMDataGridSelectColumn";
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
/**
|
|
56
|
+
* How long the grid waits after the last state change before writing to
|
|
57
|
+
* storage. Long enough to collapse a resize drag into one write, short enough
|
|
58
|
+
* that a reload right after a change still sees it.
|
|
59
|
+
*/
|
|
60
|
+
const PERSIST_DEBOUNCE_MS = 200;
|
|
48
61
|
|
|
49
62
|
/** Per-column configuration the TMDataGrid chrome reads. */
|
|
50
63
|
export type TMDataGridColumnMeta = {
|
|
@@ -175,6 +188,44 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
|
|
|
175
188
|
* reordering is entirely a matter of interface.
|
|
176
189
|
*/
|
|
177
190
|
enableColumnOrdering?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Client-side pagination and the built-in `TMDataGrid.Footer` pager.
|
|
193
|
+
* Defaults to `false`: the grid renders every filtered row and relies on
|
|
194
|
+
* virtualization.
|
|
195
|
+
*
|
|
196
|
+
* The second grid-defined switch (TanStack defines no `enablePagination`
|
|
197
|
+
* option). `manualPagination: true` implies it — a server-paged grid needs
|
|
198
|
+
* no extra flag.
|
|
199
|
+
*/
|
|
200
|
+
enablePagination?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* How rows are selected. Defaults to `"checkbox"`.
|
|
203
|
+
*
|
|
204
|
+
* - `"checkbox"` — the generated checkbox column selects; clicking a row
|
|
205
|
+
* elsewhere does not.
|
|
206
|
+
* - `"row"` — no checkbox column; clicking a row toggles it. Other rows keep
|
|
207
|
+
* their state, so a click never clears the rest of the selection.
|
|
208
|
+
*
|
|
209
|
+
* Ignored when `enableRowSelection` is `false`. Both modes write to the same
|
|
210
|
+
* `rowSelection` state.
|
|
211
|
+
*/
|
|
212
|
+
rowSelectionMode?: TMDataGridRowSelectionMode;
|
|
213
|
+
/**
|
|
214
|
+
* Give selected rows the highlight background. Defaults to `true` under
|
|
215
|
+
* `rowSelectionMode: "row"`, where the highlight is the only feedback a click
|
|
216
|
+
* gives, and `false` under `"checkbox"`, where the box already shows it.
|
|
217
|
+
*
|
|
218
|
+
* The colour is the `--dg-row-selected-bg` CSS variable, so it can be changed
|
|
219
|
+
* without turning the flag on or off:
|
|
220
|
+
*
|
|
221
|
+
* ```tsx
|
|
222
|
+
* <TMDataGrid
|
|
223
|
+
* {...grid}
|
|
224
|
+
* style={{ "--dg-row-selected-bg": "var(--mantine-color-blue-0)" }}
|
|
225
|
+
* />
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
highlightSelectedRows?: boolean;
|
|
178
229
|
};
|
|
179
230
|
|
|
180
231
|
type TMDataGridColumnDef<TData extends RowData> = ColumnDef<
|
|
@@ -201,48 +252,6 @@ function withTMDataGridDefaults<TData extends RowData>(
|
|
|
201
252
|
});
|
|
202
253
|
}
|
|
203
254
|
|
|
204
|
-
function createSelectColumn<TData extends RowData>(): TMDataGridColumnDef<TData> {
|
|
205
|
-
return {
|
|
206
|
-
id: SELECT_COLUMN_ID,
|
|
207
|
-
meta: {
|
|
208
|
-
label: "Checkbox selection",
|
|
209
|
-
align: "center",
|
|
210
|
-
// Structurally the first column; it also anchors the left pinned lane, so
|
|
211
|
-
// no other column can be moved in front of it.
|
|
212
|
-
enableOrdering: false,
|
|
213
|
-
},
|
|
214
|
-
size: 48,
|
|
215
|
-
minSize: 48,
|
|
216
|
-
maxSize: 48,
|
|
217
|
-
enableResizing: false,
|
|
218
|
-
enableSorting: false,
|
|
219
|
-
enableColumnFilter: false,
|
|
220
|
-
enableGlobalFilter: false,
|
|
221
|
-
// Structurally pinned to the left; users shouldn't be able to move it.
|
|
222
|
-
enablePinning: false,
|
|
223
|
-
header: ({ table }) => (
|
|
224
|
-
<Checkbox
|
|
225
|
-
size="xs"
|
|
226
|
-
aria-label="Select all rows"
|
|
227
|
-
checked={table.getIsAllRowsSelected()}
|
|
228
|
-
indeterminate={table.getIsSomeRowsSelected()}
|
|
229
|
-
onChange={table.getToggleAllRowsSelectedHandler()}
|
|
230
|
-
/>
|
|
231
|
-
),
|
|
232
|
-
cell: ({ row }) => (
|
|
233
|
-
<Checkbox
|
|
234
|
-
size="xs"
|
|
235
|
-
aria-label="Select row"
|
|
236
|
-
checked={row.getIsSelected()}
|
|
237
|
-
disabled={!row.getCanSelect()}
|
|
238
|
-
indeterminate={row.getIsSomeSelected()}
|
|
239
|
-
onChange={row.getToggleSelectedHandler()}
|
|
240
|
-
onClick={(event) => event.stopPropagation()}
|
|
241
|
-
/>
|
|
242
|
-
),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
255
|
/**
|
|
247
256
|
* Builds a TMDataGrid table plus its chrome store.
|
|
248
257
|
*
|
|
@@ -250,23 +259,31 @@ function createSelectColumn<TData extends RowData>(): TMDataGridColumnDef<TData>
|
|
|
250
259
|
* only needs `manualPagination` / `manualFiltering` / `manualSorting`,
|
|
251
260
|
* `rowCount` and the matching `onXChange` callbacks — the chrome reads
|
|
252
261
|
* `getRowCount()` / `getPageCount()` / `getPaginatedRowModel()`, all of which
|
|
253
|
-
* already respect manual mode.
|
|
262
|
+
* already respect manual mode. `manualPagination` also switches the pagination
|
|
263
|
+
* flag on, so `<TMDataGrid.Footer />` renders its pager without further
|
|
264
|
+
* options.
|
|
254
265
|
*/
|
|
255
266
|
export function useTMDataGrid<TData extends RowData>({
|
|
256
267
|
persist,
|
|
257
|
-
// Not
|
|
268
|
+
// Not TanStack options, so they are kept out of what `useTable` receives.
|
|
258
269
|
enableColumnOrdering,
|
|
270
|
+
enablePagination,
|
|
271
|
+
rowSelectionMode,
|
|
272
|
+
highlightSelectedRows,
|
|
259
273
|
...options
|
|
260
274
|
}: UseTMDataGridOptions<TData>): TMDataGridApi<TData> {
|
|
261
275
|
const selectionEnabled = options.enableRowSelection !== false;
|
|
262
276
|
const pinningEnabled = options.enableColumnPinning !== false;
|
|
277
|
+
// Only "checkbox" mode owns a column; "row" mode selects from the row itself.
|
|
278
|
+
const selectColumnEnabled =
|
|
279
|
+
selectionEnabled && rowSelectionMode !== "row";
|
|
263
280
|
|
|
264
281
|
const columns = useMemo(() => {
|
|
265
282
|
const base = withTMDataGridDefaults<TData>(
|
|
266
283
|
options.columns as ReadonlyArray<TMDataGridColumnDef<TData>>,
|
|
267
284
|
);
|
|
268
|
-
return
|
|
269
|
-
}, [options.columns,
|
|
285
|
+
return selectColumnEnabled ? [createSelectColumn<TData>(), ...base] : base;
|
|
286
|
+
}, [options.columns, selectColumnEnabled]);
|
|
270
287
|
|
|
271
288
|
// Read once on mount: `initialState` is only consumed on the first render,
|
|
272
289
|
// and re-reading later would fight the user's live edits.
|
|
@@ -287,7 +304,7 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
287
304
|
// The checkbox column is structurally pinned, so it is re-applied on
|
|
288
305
|
// top of anything restored from storage.
|
|
289
306
|
left: [
|
|
290
|
-
...(
|
|
307
|
+
...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
|
|
291
308
|
...(
|
|
292
309
|
persistedState.columnPinning?.left ??
|
|
293
310
|
options.initialState?.columnPinning?.left ??
|
|
@@ -311,13 +328,37 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
311
328
|
// Mirror every state change back to storage. Subscribing (rather than writing
|
|
312
329
|
// from an effect on a state snapshot) means nothing is missed, including
|
|
313
330
|
// changes made straight through the table API by the consumer.
|
|
331
|
+
//
|
|
332
|
+
// Writes are debounced because `columnResizeMode: "onChange"` publishes a new
|
|
333
|
+
// state on every pointer move of a resize drag, and `setItem` serialises
|
|
334
|
+
// synchronously on the main thread. The trailing edge is enough: storage only
|
|
335
|
+
// has to agree with the table once the user stops.
|
|
314
336
|
useEffect(() => {
|
|
315
337
|
if (!hasPersistenceKeys(persist)) return;
|
|
316
338
|
writePersistedState(table.store.state, persist);
|
|
339
|
+
|
|
340
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
341
|
+
let pending: TableState<TMDataGridFeatures> | null = null;
|
|
342
|
+
|
|
343
|
+
const flush = () => {
|
|
344
|
+
timeout = undefined;
|
|
345
|
+
if (pending === null) return;
|
|
346
|
+
writePersistedState(pending, persist);
|
|
347
|
+
pending = null;
|
|
348
|
+
};
|
|
349
|
+
|
|
317
350
|
const subscription = table.store.subscribe((state) => {
|
|
318
|
-
|
|
351
|
+
pending = state;
|
|
352
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
353
|
+
timeout = setTimeout(flush, PERSIST_DEBOUNCE_MS);
|
|
319
354
|
});
|
|
320
|
-
|
|
355
|
+
|
|
356
|
+
return () => {
|
|
357
|
+
subscription.unsubscribe();
|
|
358
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
359
|
+
// An unmount mid-debounce would otherwise drop the last change.
|
|
360
|
+
flush();
|
|
361
|
+
};
|
|
321
362
|
}, [persist, table]);
|
|
322
363
|
|
|
323
364
|
const ui = useCreateStore<TMDataGridUiState, TMDataGridUiActions>(
|
|
@@ -354,7 +395,13 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
354
395
|
// Deliberately not memoized on `table`: the flags must re-derive whenever the
|
|
355
396
|
// caller passes different options, and `table` keeps the same identity when
|
|
356
397
|
// they do. See readFeatureFlags.
|
|
357
|
-
const features = readFeatureFlags({
|
|
398
|
+
const features = readFeatureFlags({
|
|
399
|
+
...options,
|
|
400
|
+
enableColumnOrdering,
|
|
401
|
+
enablePagination,
|
|
402
|
+
rowSelectionMode,
|
|
403
|
+
highlightSelectedRows,
|
|
404
|
+
});
|
|
358
405
|
|
|
359
406
|
return { table, ui, features };
|
|
360
407
|
}
|
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
/* Size scale. Row heights mirror SIZE_ROW_HEIGHT in sizes.ts — the virtualizer
|
|
2
|
-
needs the number in JS, so the two have to agree. */
|
|
3
|
-
.root[data-size="xs"] {
|
|
4
|
-
--dg-row-height: 34px;
|
|
5
|
-
--dg-header-height: 32px;
|
|
6
|
-
--dg-font-size: var(--mantine-font-size-xs);
|
|
7
|
-
--dg-padding: 6px;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.root[data-size="sm"] {
|
|
11
|
-
--dg-row-height: 42px;
|
|
12
|
-
--dg-header-height: 38px;
|
|
13
|
-
--dg-font-size: var(--mantine-font-size-sm);
|
|
14
|
-
--dg-padding: 8px;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.root[data-size="md"] {
|
|
18
|
-
--dg-row-height: 52px;
|
|
19
|
-
--dg-header-height: 44px;
|
|
20
|
-
--dg-font-size: var(--mantine-font-size-sm);
|
|
21
|
-
--dg-padding: 10px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.root[data-size="lg"] {
|
|
25
|
-
--dg-row-height: 62px;
|
|
26
|
-
--dg-header-height: 52px;
|
|
27
|
-
--dg-font-size: var(--mantine-font-size-md);
|
|
28
|
-
--dg-padding: 14px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.root[data-size="xl"] {
|
|
32
|
-
--dg-row-height: 72px;
|
|
33
|
-
--dg-header-height: 60px;
|
|
34
|
-
--dg-font-size: var(--mantine-font-size-lg);
|
|
35
|
-
--dg-padding: 18px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.root {
|
|
39
|
-
/* Fallbacks, so the parts still work if rendered outside a sized root. */
|
|
40
|
-
--dg-row-height: 52px;
|
|
41
|
-
--dg-header-height: 44px;
|
|
42
|
-
--dg-font-size: var(--mantine-font-size-sm);
|
|
43
|
-
--dg-padding: 10px;
|
|
44
|
-
|
|
45
|
-
display: flex;
|
|
46
|
-
flex-direction: column;
|
|
47
|
-
min-height: 0;
|
|
48
|
-
overflow: hidden;
|
|
49
|
-
/* Header text is a stretched flex item, so an inherited `text-align: center`
|
|
50
|
-
from the host page would centre headers away from their cell content. */
|
|
51
|
-
text-align: left;
|
|
52
|
-
border: 1px solid var(--mantine-color-default-border);
|
|
53
|
-
border-radius: var(--mantine-radius-md);
|
|
54
|
-
background-color: var(--mantine-color-body);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.toolbar {
|
|
58
|
-
display: flex;
|
|
59
|
-
align-items: center;
|
|
60
|
-
gap: var(--mantine-spacing-xs);
|
|
61
|
-
padding: 6px var(--mantine-spacing-xs);
|
|
62
|
-
min-height: 42px;
|
|
63
|
-
flex-shrink: 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.toolbarSpacer {
|
|
67
|
-
flex: 1;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* Anchors the filter panel, which floats over the first body rows. */
|
|
71
|
-
.tableWrapper {
|
|
72
|
-
position: relative;
|
|
73
|
-
flex: 1;
|
|
74
|
-
min-height: 0;
|
|
75
|
-
display: flex;
|
|
76
|
-
flex-direction: column;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.scrollContainer {
|
|
80
|
-
position: relative;
|
|
81
|
-
flex: 1;
|
|
82
|
-
min-height: 0;
|
|
83
|
-
overflow: auto;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Column tracks are set inline via gridTemplateColumns. */
|
|
87
|
-
.grid {
|
|
88
|
-
display: grid;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/* Rows are real layout boxes (not display: contents) so position: sticky works,
|
|
92
|
-
and subgrid keeps their cells on the parent's column tracks. */
|
|
93
|
-
.headerRow {
|
|
94
|
-
display: grid;
|
|
95
|
-
grid-column: 1 / -1;
|
|
96
|
-
grid-template-columns: subgrid;
|
|
97
|
-
position: sticky;
|
|
98
|
-
top: 0;
|
|
99
|
-
z-index: 3;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.headerCell {
|
|
103
|
-
position: relative;
|
|
104
|
-
display: flex;
|
|
105
|
-
align-items: center;
|
|
106
|
-
gap: 2px;
|
|
107
|
-
padding: 0 var(--dg-padding);
|
|
108
|
-
min-width: 0;
|
|
109
|
-
min-height: var(--dg-header-height);
|
|
110
|
-
font-weight: 600;
|
|
111
|
-
font-size: var(--dg-font-size);
|
|
112
|
-
background-color: var(--mantine-color-body);
|
|
113
|
-
border-bottom: 1px solid var(--mantine-color-default-border);
|
|
114
|
-
user-select: none;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.headerCell[data-align="right"] {
|
|
118
|
-
justify-content: flex-end;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.headerCell[data-align="center"] {
|
|
122
|
-
justify-content: center;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* Sorted or filtered columns read as active, like the MUI data grid. */
|
|
126
|
-
.headerCell[data-active="true"] .headerTitle {
|
|
127
|
-
color: var(--mantine-primary-color-filled);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.headerTitle {
|
|
131
|
-
flex: 1 1 auto;
|
|
132
|
-
min-width: 0;
|
|
133
|
-
overflow: hidden;
|
|
134
|
-
text-overflow: ellipsis;
|
|
135
|
-
white-space: nowrap;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/* Right-aligned headers stay text so the ellipsis still works; centered ones
|
|
139
|
-
hold a control (the select-all checkbox), so they become a flex box. */
|
|
140
|
-
.headerCell[data-align="right"] .headerTitle {
|
|
141
|
-
text-align: right;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.headerCell[data-align="center"] .headerTitle {
|
|
145
|
-
display: flex;
|
|
146
|
-
justify-content: center;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.headerActions {
|
|
150
|
-
display: flex;
|
|
151
|
-
align-items: center;
|
|
152
|
-
gap: 0;
|
|
153
|
-
flex-shrink: 0;
|
|
154
|
-
margin-left: auto;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/* Actions always reserve their width. On right-aligned columns they move to the
|
|
158
|
-
leading edge so the header text stays flush with the cell values below it. */
|
|
159
|
-
.headerCell[data-align="right"] .headerActions {
|
|
160
|
-
order: -1;
|
|
161
|
-
margin-left: 0;
|
|
162
|
-
margin-right: auto;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Idle actions are removed from layout, not just faded — reserving their width
|
|
166
|
-
would truncate header text that otherwise fits. Indicators that must stay
|
|
167
|
-
visible (active sort, active filter, open menu) set data-pinned-visible. */
|
|
168
|
-
.headerAction {
|
|
169
|
-
display: none;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.headerCell:hover .headerAction,
|
|
173
|
-
.headerCell:focus-within .headerAction,
|
|
174
|
-
.headerAction[data-pinned-visible="true"] {
|
|
175
|
-
display: flex;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.headerCellSortable {
|
|
179
|
-
cursor: pointer;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/* Column reordering. The dragged header stays in place and dims — the browser
|
|
183
|
-
already carries a snapshot of it under the pointer — while the header being
|
|
184
|
-
dragged over draws a bar on the edge the column will land against. */
|
|
185
|
-
.headerCellDragging {
|
|
186
|
-
opacity: 0.4;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.headerCellDropBefore::before,
|
|
190
|
-
.headerCellDropAfter::before {
|
|
191
|
-
content: "";
|
|
192
|
-
position: absolute;
|
|
193
|
-
top: 0;
|
|
194
|
-
bottom: 0;
|
|
195
|
-
width: 2px;
|
|
196
|
-
background-color: var(--mantine-primary-color-filled);
|
|
197
|
-
z-index: 5;
|
|
198
|
-
pointer-events: none;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.headerCellDropBefore::before {
|
|
202
|
-
left: 0;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.headerCellDropAfter::before {
|
|
206
|
-
right: 0;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/* Doubles as the column divider seen between every header cell. */
|
|
210
|
-
.columnSeparator {
|
|
211
|
-
position: absolute;
|
|
212
|
-
top: 25%;
|
|
213
|
-
right: 0;
|
|
214
|
-
height: 50%;
|
|
215
|
-
width: 10px;
|
|
216
|
-
transform: translateX(50%);
|
|
217
|
-
display: flex;
|
|
218
|
-
justify-content: center;
|
|
219
|
-
z-index: 1;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.columnSeparatorResizable {
|
|
223
|
-
cursor: col-resize;
|
|
224
|
-
touch-action: none;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* The divider is a hover affordance, not permanent chrome — it only appears on
|
|
228
|
-
the header cell you are pointing at, or on the column being resized. */
|
|
229
|
-
.columnSeparator::after {
|
|
230
|
-
content: "";
|
|
231
|
-
width: 1px;
|
|
232
|
-
height: 100%;
|
|
233
|
-
background-color: transparent;
|
|
234
|
-
transition:
|
|
235
|
-
background-color 120ms ease,
|
|
236
|
-
width 120ms ease;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.headerCell:hover .columnSeparator::after,
|
|
240
|
-
.headerCell:focus-within .columnSeparator::after {
|
|
241
|
-
background-color: var(--mantine-color-default-border);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.columnSeparatorResizable:hover::after,
|
|
245
|
-
.columnSeparatorActive::after {
|
|
246
|
-
width: 2px;
|
|
247
|
-
background-color: var(--mantine-primary-color-filled);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.bodyRow {
|
|
251
|
-
--row-bg: var(--mantine-color-body);
|
|
252
|
-
display: grid;
|
|
253
|
-
grid-column: 1 / -1;
|
|
254
|
-
grid-template-columns: subgrid;
|
|
255
|
-
align-items: stretch;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.bodyRow:hover {
|
|
259
|
-
--row-bg: var(--mantine-color-default-hover);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.bodyRow[data-selected="true"] {
|
|
263
|
-
--row-bg: var(--mantine-primary-color-light);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/* Clipping lives on .cellContent, not here — a pinned cell has to let its
|
|
267
|
-
boundary gradient escape past its own right/left edge. */
|
|
268
|
-
.bodyCell {
|
|
269
|
-
display: flex;
|
|
270
|
-
align-items: center;
|
|
271
|
-
padding: 0 var(--dg-padding);
|
|
272
|
-
border-bottom: 1px solid var(--mantine-color-default-border);
|
|
273
|
-
/* Inherited from .bodyRow — sticky cells need an opaque background. */
|
|
274
|
-
background-color: var(--row-bg);
|
|
275
|
-
font-size: var(--dg-font-size);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.cellContent {
|
|
279
|
-
min-width: 0;
|
|
280
|
-
overflow: hidden;
|
|
281
|
-
white-space: nowrap;
|
|
282
|
-
text-overflow: ellipsis;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.bodyCell[data-align="right"] {
|
|
286
|
-
justify-content: flex-end;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.bodyCell[data-align="center"] {
|
|
290
|
-
justify-content: center;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/* Only stretch to fill when left-aligned, so right/centre stay put. */
|
|
294
|
-
.bodyCell[data-align="left"] .cellContent {
|
|
295
|
-
flex: 1 1 auto;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/* Boundary of a pinned column group.
|
|
299
|
-
A box-shadow is drawn per cell, and its blur fades in at each cell's own top
|
|
300
|
-
and bottom edge — stacked down a column that reads as banding at every row
|
|
301
|
-
seam. Instead: a crisp 1px border for the hard edge, plus a gradient pseudo
|
|
302
|
-
element pinned to `top: 0; bottom: 0` so consecutive cells butt together into
|
|
303
|
-
one unbroken band. Mantine sets `color-scheme` on the root, so light-dark()
|
|
304
|
-
resolves without a second selector. */
|
|
305
|
-
.stickyLeft {
|
|
306
|
-
border-right: 1px solid var(--mantine-color-default-border);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.stickyRight {
|
|
310
|
-
border-left: 1px solid var(--mantine-color-default-border);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
.stickyLeft::after,
|
|
314
|
-
.stickyRight::after {
|
|
315
|
-
content: "";
|
|
316
|
-
position: absolute;
|
|
317
|
-
top: 0;
|
|
318
|
-
bottom: 0;
|
|
319
|
-
width: 4px;
|
|
320
|
-
pointer-events: none;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
.stickyLeft::after {
|
|
324
|
-
left: 100%;
|
|
325
|
-
background: linear-gradient(
|
|
326
|
-
to right,
|
|
327
|
-
light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.28)),
|
|
328
|
-
transparent
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.stickyRight::after {
|
|
333
|
-
right: 100%;
|
|
334
|
-
background: linear-gradient(
|
|
335
|
-
to left,
|
|
336
|
-
light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.28)),
|
|
337
|
-
transparent
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.messageRow {
|
|
342
|
-
grid-column: 1 / -1;
|
|
343
|
-
display: flex;
|
|
344
|
-
flex-direction: column;
|
|
345
|
-
align-items: center;
|
|
346
|
-
justify-content: center;
|
|
347
|
-
gap: var(--mantine-spacing-xs);
|
|
348
|
-
padding: var(--mantine-spacing-xl);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
.footer {
|
|
352
|
-
display: flex;
|
|
353
|
-
align-items: center;
|
|
354
|
-
justify-content: flex-end;
|
|
355
|
-
gap: var(--mantine-spacing-lg);
|
|
356
|
-
padding: 4px var(--mantine-spacing-sm);
|
|
357
|
-
min-height: 48px;
|
|
358
|
-
flex-shrink: 0;
|
|
359
|
-
border-top: 1px solid var(--mantine-color-default-border);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
.filterPanel {
|
|
363
|
-
position: absolute;
|
|
364
|
-
top: 46px;
|
|
365
|
-
left: var(--mantine-spacing-md);
|
|
366
|
-
z-index: 5;
|
|
367
|
-
padding: var(--mantine-spacing-sm);
|
|
368
|
-
border-radius: var(--mantine-radius-md);
|
|
369
|
-
border: 1px solid var(--mantine-color-default-border);
|
|
370
|
-
background-color: var(--mantine-color-body);
|
|
371
|
-
box-shadow: var(--mantine-shadow-md);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
.filterRow {
|
|
375
|
-
display: flex;
|
|
376
|
-
align-items: flex-end;
|
|
377
|
-
gap: var(--mantine-spacing-xs);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/* Marks the pin position a column is currently in. */
|
|
381
|
-
.menuItemActive {
|
|
382
|
-
background-color: var(--mantine-color-default-hover);
|
|
383
|
-
font-weight: 600;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.columnsPanel {
|
|
387
|
-
width: 280px;
|
|
388
|
-
display: flex;
|
|
389
|
-
flex-direction: column;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.columnsPanelSearch {
|
|
393
|
-
padding: var(--mantine-spacing-sm);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
.columnsPanelList {
|
|
397
|
-
display: flex;
|
|
398
|
-
flex-direction: column;
|
|
399
|
-
gap: var(--mantine-spacing-sm);
|
|
400
|
-
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
.columnsPanelFooter {
|
|
404
|
-
display: flex;
|
|
405
|
-
align-items: center;
|
|
406
|
-
justify-content: space-between;
|
|
407
|
-
gap: var(--mantine-spacing-sm);
|
|
408
|
-
padding: var(--mantine-spacing-sm);
|
|
409
|
-
border-top: 1px solid var(--mantine-color-default-border);
|
|
410
|
-
}
|