@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.
Files changed (39) hide show
  1. package/README.md +35 -2
  2. package/dist/index.d.ts +442 -337
  3. package/dist/index.js +811 -694
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/package.json +15 -7
  7. package/skills/columns/SKILL.md +8 -7
  8. package/skills/features/SKILL.md +104 -16
  9. package/skills/getting-started/SKILL.md +39 -16
  10. package/skills/options/SKILL.md +12 -7
  11. package/skills/server-side/SKILL.md +5 -2
  12. package/src/tmdatagrid/TMDataGridContext.ts +1 -1
  13. package/src/tmdatagrid/components/TMDataGrid.module.css +63 -0
  14. package/src/tmdatagrid/{TMDataGrid.tsx → components/TMDataGrid.tsx} +9 -4
  15. package/src/tmdatagrid/components/TMDataGridColumnsPanel.module.css +25 -0
  16. package/src/tmdatagrid/{TMDataGridColumnsPanel.tsx → components/TMDataGridColumnsPanel.tsx} +3 -3
  17. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +20 -0
  18. package/src/tmdatagrid/{TMDataGridFilterPanel.tsx → components/TMDataGridFilterPanel.tsx} +4 -4
  19. package/src/tmdatagrid/components/TMDataGridFooter.module.css +10 -0
  20. package/src/tmdatagrid/components/TMDataGridFooter.tsx +143 -0
  21. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +153 -0
  22. package/src/tmdatagrid/{TMDataGridHeaderCell.tsx → components/TMDataGridHeaderCell.tsx} +22 -13
  23. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +100 -0
  24. package/src/tmdatagrid/components/TMDataGridTable.module.css +103 -0
  25. package/src/tmdatagrid/{TMDataGridTable.tsx → components/TMDataGridTable.tsx} +79 -20
  26. package/src/tmdatagrid/components/TMDataGridToolbar.module.css +12 -0
  27. package/src/tmdatagrid/{TMDataGridToolbar.tsx → components/TMDataGridToolbar.tsx} +5 -5
  28. package/src/tmdatagrid/components/sticky.module.css +45 -0
  29. package/src/tmdatagrid/{capabilities.ts → core/capabilities.ts} +30 -2
  30. package/src/tmdatagrid/{columnOrdering.ts → core/columnOrdering.ts} +2 -2
  31. package/src/tmdatagrid/{columnUtils.ts → core/columnUtils.ts} +1 -1
  32. package/src/tmdatagrid/{persistence.ts → core/persistence.ts} +80 -2
  33. package/src/tmdatagrid/index.ts +15 -9
  34. package/src/tmdatagrid/useTMDataGrid.tsx +102 -55
  35. package/src/tmdatagrid/TMDataGrid.module.css +0 -410
  36. package/src/tmdatagrid/TMDataGridFooter.tsx +0 -74
  37. /package/src/tmdatagrid/{icons.ts → components/icons.ts} +0 -0
  38. /package/src/tmdatagrid/{filterOperators.ts → core/filterOperators.ts} +0 -0
  39. /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 { readFeatureFlags, type TMDataGridFeatureFlags } from "./capabilities";
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
- export const SELECT_COLUMN_ID = "__select__";
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 a TanStack option, so it is kept out of what `useTable` receives.
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 selectionEnabled ? [createSelectColumn<TData>(), ...base] : base;
269
- }, [options.columns, selectionEnabled]);
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
- ...(selectionEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
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
- writePersistedState(state, persist);
351
+ pending = state;
352
+ if (timeout !== undefined) clearTimeout(timeout);
353
+ timeout = setTimeout(flush, PERSIST_DEBOUNCE_MS);
319
354
  });
320
- return () => subscription.unsubscribe();
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({ ...options, enableColumnOrdering });
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
- }