@jielga/tmdatagrid 0.1.0 → 0.3.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 (47) hide show
  1. package/README.md +35 -2
  2. package/dist/index.d.ts +792 -327
  3. package/dist/index.js +1581 -892
  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 +78 -20
  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 +73 -0
  14. package/src/tmdatagrid/{TMDataGrid.tsx → components/TMDataGrid.tsx} +15 -4
  15. package/src/tmdatagrid/components/TMDataGridColumnsPanel.module.css +25 -0
  16. package/src/tmdatagrid/{TMDataGridColumnsPanel.tsx → components/TMDataGridColumnsPanel.tsx} +10 -4
  17. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +35 -0
  18. package/src/tmdatagrid/{TMDataGridFilterPanel.tsx → components/TMDataGridFilterPanel.tsx} +91 -14
  19. package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +35 -0
  20. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +135 -0
  21. package/src/tmdatagrid/components/TMDataGridFooter.module.css +18 -0
  22. package/src/tmdatagrid/components/TMDataGridFooter.tsx +171 -0
  23. package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +48 -0
  24. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +147 -0
  25. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +159 -0
  26. package/src/tmdatagrid/{TMDataGridHeaderCell.tsx → components/TMDataGridHeaderCell.tsx} +99 -14
  27. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +177 -0
  28. package/src/tmdatagrid/components/TMDataGridTable.module.css +137 -0
  29. package/src/tmdatagrid/components/TMDataGridTable.tsx +720 -0
  30. package/src/tmdatagrid/components/TMDataGridToolbar.module.css +12 -0
  31. package/src/tmdatagrid/{TMDataGridToolbar.tsx → components/TMDataGridToolbar.tsx} +8 -5
  32. package/src/tmdatagrid/{icons.ts → components/icons.ts} +5 -0
  33. package/src/tmdatagrid/components/sticky.module.css +45 -0
  34. package/src/tmdatagrid/core/capabilities.ts +215 -0
  35. package/src/tmdatagrid/{columnOrdering.ts → core/columnOrdering.ts} +2 -2
  36. package/src/tmdatagrid/{columnUtils.ts → core/columnUtils.ts} +1 -1
  37. package/src/tmdatagrid/{filterOperators.ts → core/filterOperators.ts} +25 -0
  38. package/src/tmdatagrid/core/grouping.ts +21 -0
  39. package/src/tmdatagrid/{persistence.ts → core/persistence.ts} +92 -2
  40. package/src/tmdatagrid/core/rowSelection.ts +202 -0
  41. package/src/tmdatagrid/index.ts +39 -9
  42. package/src/tmdatagrid/useTMDataGrid.tsx +318 -66
  43. package/src/tmdatagrid/TMDataGrid.module.css +0 -410
  44. package/src/tmdatagrid/TMDataGridFooter.tsx +0 -74
  45. package/src/tmdatagrid/TMDataGridTable.tsx +0 -297
  46. package/src/tmdatagrid/capabilities.ts +0 -117
  47. /package/src/tmdatagrid/{sizes.ts → core/sizes.ts} +0 -0
@@ -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
- }
@@ -1,74 +0,0 @@
1
- import { ActionIcon, Group, Select, Text } from "@mantine/core";
2
- import { useSelector } from "@tanstack/react-store";
3
- import classes from "./TMDataGrid.module.css";
4
- import { useTMDataGridContext } from "./TMDataGridContext";
5
- import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
6
-
7
- export type TMDataGridFooterProps = {
8
- pageSizeOptions?: ReadonlyArray<number>;
9
- };
10
-
11
- /**
12
- * MUI-style pager: "Rows per page · from–to of total · ‹ ›".
13
- *
14
- * Row totals come from `table.getRowCount()`, which prefers `options.rowCount`
15
- * — so a server-paged grid shows the server's total without changes here.
16
- */
17
- export function TMDataGridFooter({
18
- pageSizeOptions = [10, 25, 50, 100],
19
- }: TMDataGridFooterProps) {
20
- const { table, controlSize } = useTMDataGridContext();
21
- useSelector(table.store);
22
-
23
- const { pageIndex, pageSize } = table.store.state.pagination;
24
- const total = table.getRowCount();
25
- const from = total === 0 ? 0 : pageIndex * pageSize + 1;
26
- const to = Math.min(total, (pageIndex + 1) * pageSize);
27
-
28
- return (
29
- <div className={classes.footer}>
30
- <Group gap="xs" wrap="nowrap">
31
- <Text size={controlSize} c="dimmed">
32
- Rows per page:
33
- </Text>
34
- <Select
35
- size={controlSize}
36
- w={78}
37
- allowDeselect={false}
38
- variant="unstyled"
39
- data={pageSizeOptions.map(String)}
40
- value={String(pageSize)}
41
- onChange={(value) => {
42
- table.setPageSize(Number(value) || pageSizeOptions[0]);
43
- table.setPageIndex(0);
44
- }}
45
- />
46
- </Group>
47
-
48
- <Text size={controlSize} c="dimmed">
49
- {from}–{to} of {total}
50
- </Text>
51
-
52
- <Group gap={4} wrap="nowrap">
53
- <ActionIcon
54
- variant="subtle"
55
- color="gray"
56
- aria-label="Previous page"
57
- disabled={!table.getCanPreviousPage()}
58
- onClick={() => table.previousPage()}
59
- >
60
- <ChevronLeftIcon size={18} stroke={1.6} />
61
- </ActionIcon>
62
- <ActionIcon
63
- variant="subtle"
64
- color="gray"
65
- aria-label="Next page"
66
- disabled={!table.getCanNextPage()}
67
- onClick={() => table.nextPage()}
68
- >
69
- <ChevronRightIcon size={18} stroke={1.6} />
70
- </ActionIcon>
71
- </Group>
72
- </div>
73
- );
74
- }