@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
package/dist/styles.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.
|
|
1
|
+
._root_1jc4n_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1jc4n_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1jc4n_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1jc4n_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1jc4n_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1jc4n_8{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);text-align:left;border:1px solid var(--mantine-color-default-border);border-radius:var(--mantine-radius-md);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_1492o_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_1492o_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_1492o_11{gap:var(--mantine-spacing-sm);padding:0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelFooter_1492o_18{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._filterPanel_1jw69_4{top:calc(var(--dg-header-height) + 2px);left:var(--mantine-spacing-md);z-index:5;padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterRow_1jw69_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._footer_6r35a_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._tableWrapper_utldp_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_utldp_10{flex:1;min-height:0;position:relative;overflow:auto}._grid_utldp_18{display:grid}._headerRow_utldp_24{grid-column:1/-1;grid-template-columns:subgrid;z-index:3;display:grid;position:sticky;top:0}._bodyRow_utldp_33{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_utldp_33:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_utldp_33[data-highlighted=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_utldp_33[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_utldp_33:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_utldp_65{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--row-bg);font-size:var(--dg-font-size);align-items:center;display:flex}._cellContent_utldp_63{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_utldp_65[data-align=right]{justify-content:flex-end}._bodyCell_utldp_65[data-align=center]{justify-content:center}._bodyCell_utldp_65[data-align=left] ._cellContent_utldp_63{flex:auto}._messageRow_utldp_95{justify-content:center;align-items:center;gap:var(--mantine-spacing-xs);padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex}._stickyLeft_1iqrf_11{border-right:1px solid var(--mantine-color-default-border)}._stickyRight_1iqrf_15{border-left:1px solid var(--mantine-color-default-border)}._stickyLeft_1iqrf_11:after,._stickyRight_1iqrf_15:after{content:"";pointer-events:none;width:4px;position:absolute;top:0;bottom:0}._stickyLeft_1iqrf_11:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);left:100%}._stickyRight_1iqrf_15:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);right:100%}._headerCell_1oqkm_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_1oqkm_1[data-align=right]{justify-content:flex-end}._headerCell_1oqkm_1[data-align=center]{justify-content:center}._headerCell_1oqkm_1[data-active=true] ._headerTitle_1oqkm_25{color:var(--mantine-primary-color-filled)}._headerTitle_1oqkm_25{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_1oqkm_1[data-align=right] ._headerTitle_1oqkm_25{text-align:right}._headerCell_1oqkm_1[data-align=center] ._headerTitle_1oqkm_25{justify-content:center;display:flex}._headerActions_1oqkm_48{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_1oqkm_1[data-align=right] ._headerActions_1oqkm_48{order:-1;margin-left:0;margin-right:auto}._headerAction_1oqkm_48{display:none}._headerCell_1oqkm_1:hover ._headerAction_1oqkm_48,._headerCell_1oqkm_1:focus-within ._headerAction_1oqkm_48,._headerAction_1oqkm_48[data-pinned-visible=true]{display:flex}._headerCellSortable_1oqkm_77{cursor:pointer}._headerCellDragging_1oqkm_84{opacity:.4}._headerCellDropBefore_1oqkm_88:before,._headerCellDropAfter_1oqkm_89:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_1oqkm_88:before{left:0}._headerCellDropAfter_1oqkm_89:before{right:0}._columnSeparator_1oqkm_109{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_1oqkm_121{cursor:col-resize;touch-action:none}._columnSeparator_1oqkm_109:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_1oqkm_1:hover ._columnSeparator_1oqkm_109:after,._headerCell_1oqkm_1:focus-within ._columnSeparator_1oqkm_109:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_1oqkm_121:hover:after,._columnSeparatorActive_1oqkm_144:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_1oqkm_150{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_4my8t_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_4my8t_10{flex:1}
|
|
2
2
|
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jielga/tmdatagrid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A React data grid built on TanStack Table v9 and Mantine — always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
41
|
"src/tmdatagrid",
|
|
42
|
+
"!src/tmdatagrid/**/*.test.*",
|
|
42
43
|
"skills",
|
|
43
44
|
"!skills/_artifacts"
|
|
44
45
|
],
|
|
@@ -56,8 +57,10 @@
|
|
|
56
57
|
"scripts": {
|
|
57
58
|
"dev": "vite",
|
|
58
59
|
"build": "tsc -b && vite build",
|
|
59
|
-
"build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json &&
|
|
60
|
+
"build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json && rolldown -c rolldown.dts.config.mjs",
|
|
60
61
|
"lint": "oxlint",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest",
|
|
61
64
|
"preview": "vite preview",
|
|
62
65
|
"changeset": "changeset",
|
|
63
66
|
"version-packages": "changeset version && node scripts/sync-skill-version.mjs",
|
|
@@ -91,21 +94,26 @@
|
|
|
91
94
|
"@tanstack/react-virtual": "^3.14.4",
|
|
92
95
|
"@tanstack/store": "^0.11.0",
|
|
93
96
|
"@tanstack/table-core": "^9.0.0-beta.21",
|
|
97
|
+
"@testing-library/jest-dom": "^7.0.0",
|
|
98
|
+
"@testing-library/react": "^16.3.2",
|
|
99
|
+
"@testing-library/user-event": "^14.6.1",
|
|
94
100
|
"@types/babel__core": "^7.20.5",
|
|
95
101
|
"@types/node": "^24.13.2",
|
|
96
102
|
"@types/react": "^19.2.17",
|
|
97
103
|
"@types/react-dom": "^19.2.3",
|
|
98
|
-
"@vitejs/plugin-react": "^6.0.
|
|
104
|
+
"@vitejs/plugin-react": "^6.0.4",
|
|
99
105
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
100
106
|
"husky": "^9.1.7",
|
|
107
|
+
"jsdom": "^29.1.1",
|
|
101
108
|
"oxlint": "^1.69.0",
|
|
102
109
|
"react": "^19.2.7",
|
|
103
110
|
"react-dom": "^19.2.7",
|
|
104
111
|
"react-markdown": "^10.1.0",
|
|
105
112
|
"remark-gfm": "^4.0.1",
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"typescript": "~
|
|
109
|
-
"vite": "^8.1.
|
|
113
|
+
"rolldown": "~1.1.5",
|
|
114
|
+
"rolldown-plugin-dts": "^0.27.14",
|
|
115
|
+
"typescript": "~7.0.2",
|
|
116
|
+
"vite": "^8.1.5",
|
|
117
|
+
"vitest": "^4.1.10"
|
|
110
118
|
}
|
|
111
119
|
}
|
package/skills/columns/SKILL.md
CHANGED
|
@@ -11,11 +11,11 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '0.
|
|
14
|
+
library_version: '0.2.0'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/columns.md'
|
|
17
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/filterOperators.ts'
|
|
18
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/columnUtils.ts'
|
|
17
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/filterOperators.ts'
|
|
18
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/columnUtils.ts'
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# TMDataGrid — Columns
|
|
@@ -105,10 +105,11 @@ A column whose menu has no remaining items renders no menu button.
|
|
|
105
105
|
|
|
106
106
|
## Checkbox column
|
|
107
107
|
|
|
108
|
-
Unless `enableRowSelection` is `false
|
|
109
|
-
is added as the first column and pinned to the
|
|
110
|
-
selection" in the column manager and can be
|
|
111
|
-
no column menu and cannot be sorted,
|
|
108
|
+
Unless `enableRowSelection` is `false` or `rowSelectionMode` is `"row"`, a column
|
|
109
|
+
with the id `SELECT_COLUMN_ID` is added as the first column and pinned to the
|
|
110
|
+
left. It is listed as "Checkbox selection" in the column manager and can be
|
|
111
|
+
hidden like any other column. It has no column menu and cannot be sorted,
|
|
112
|
+
filtered, resized, re-pinned or moved.
|
|
112
113
|
|
|
113
114
|
Because it cannot be moved, it also anchors the left pinned region: no column
|
|
114
115
|
can be placed in front of it.
|
package/skills/features/SKILL.md
CHANGED
|
@@ -4,20 +4,24 @@ description: >
|
|
|
4
4
|
Enable or disable TMDataGrid interface elements through standard TanStack
|
|
5
5
|
table and column options, and reuse the same checks in your own components.
|
|
6
6
|
Covers the enableSorting/enableColumnFilters/enableHiding/enableColumnPinning/
|
|
7
|
-
enableColumnResizing/enableColumnOrdering/enableRowSelection matrix,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
enableColumnResizing/enableColumnOrdering/enableRowSelection matrix, the
|
|
8
|
+
checkbox and row selection modes behind rowSelectionMode, the
|
|
9
|
+
highlightSelectedRows flag and the --dg-row-selected-bg colour, the
|
|
10
|
+
default-off enablePagination switch and its three modes (none, client,
|
|
11
|
+
manual), getGridCapabilities and getColumnCapabilities, why the features
|
|
12
|
+
argument is required for reactivity under the React Compiler, column pinning,
|
|
13
|
+
column ordering with moveColumn and moveColumnByStep, and always-on
|
|
14
|
+
virtualization. Load when building a read-only grid, hiding grid chrome,
|
|
15
|
+
enabling pagination, reordering columns from code, or writing a custom toolbar
|
|
16
|
+
button.
|
|
13
17
|
metadata:
|
|
14
18
|
type: core
|
|
15
19
|
library: '@jielga/tmdatagrid'
|
|
16
|
-
library_version: '0.
|
|
20
|
+
library_version: '0.2.0'
|
|
17
21
|
sources:
|
|
18
22
|
- 'Jielga/TMDataGrid:src/docs/features.md'
|
|
19
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/capabilities.ts'
|
|
20
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/columnOrdering.ts'
|
|
23
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/capabilities.ts'
|
|
24
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/columnOrdering.ts'
|
|
21
25
|
---
|
|
22
26
|
|
|
23
27
|
# TMDataGrid — Features and capabilities
|
|
@@ -26,10 +30,10 @@ Almost every control is bound to the TanStack capability check for that feature,
|
|
|
26
30
|
so setting the standard table or column option removes the corresponding
|
|
27
31
|
interface. Empty menus and inactive buttons are never rendered.
|
|
28
32
|
|
|
29
|
-
Column ordering
|
|
30
|
-
|
|
31
|
-
`
|
|
32
|
-
options around
|
|
33
|
+
Column ordering and pagination are the two exceptions. TanStack ships state and
|
|
34
|
+
APIs for both but no `enable` option, so the grid defines `enableColumnOrdering`
|
|
35
|
+
(with `meta.enableOrdering`) and `enablePagination` itself. Ordering behaves
|
|
36
|
+
like the options around it; pagination is the one switch that defaults to off.
|
|
33
37
|
|
|
34
38
|
## Option reference
|
|
35
39
|
|
|
@@ -46,6 +50,9 @@ options around them.
|
|
|
46
50
|
| `enableColumnOrdering: false` | Table | Header dragging and the move menu items |
|
|
47
51
|
| `meta.enableOrdering: false` | Column | That column's header dragging and move menu items |
|
|
48
52
|
| `enableRowSelection: false` | Table | The checkbox column |
|
|
53
|
+
| `rowSelectionMode: "row"` | Table | The checkbox column — the row itself selects instead. See Row selection |
|
|
54
|
+
| `highlightSelectedRows: false` | Table | The highlight background on selected rows. Follows the selection mode by default |
|
|
55
|
+
| `enablePagination: true` | Table | Opt-in: adds paging and the `Footer` pager. Off by default |
|
|
49
56
|
|
|
50
57
|
A column whose menu has no remaining items renders no menu button. Dividers are
|
|
51
58
|
never left at the end of a menu.
|
|
@@ -62,8 +69,76 @@ const grid = useTMDataGrid({
|
|
|
62
69
|
});
|
|
63
70
|
```
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
## Row selection
|
|
73
|
+
|
|
74
|
+
On by default, in two modes. Both write to the same `rowSelection` state, so
|
|
75
|
+
everything downstream — the toolbar count, `getSelectedRowModel()`, persistence
|
|
76
|
+
— is unaffected by the choice.
|
|
77
|
+
|
|
78
|
+
**Checkbox** — the default. A checkbox column is added as the first column, with
|
|
79
|
+
a select-all box in its header. Clicking a row elsewhere does not select it, and
|
|
80
|
+
a selected row is not highlighted: the checkbox already says so.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const grid = useTMDataGrid({ data, columns });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Row** — no checkbox column. Clicking a row toggles it, and the row takes the
|
|
87
|
+
highlight background — the only feedback a click gives. Other rows keep their
|
|
88
|
+
state, so a click never clears the rest of the selection. Rows are focusable in
|
|
89
|
+
this mode and Space or Enter toggles the focused row.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
const grid = useTMDataGrid({ data, columns, rowSelectionMode: "row" });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`enableRowSelection: false` removes both, and `rowSelectionMode` is then
|
|
96
|
+
ignored. `TMDataGrid.Table`'s `onRowClick` still fires in either mode — under
|
|
97
|
+
`"row"` it runs in addition to the selection, not instead of it.
|
|
98
|
+
|
|
99
|
+
`highlightSelectedRows` follows the mode — on for `"row"`, off for
|
|
100
|
+
`"checkbox"` — and can be set to override either way. The colour is the
|
|
101
|
+
`--dg-row-selected-bg` CSS variable (default `--mantine-primary-color-light`);
|
|
102
|
+
change it on the grid element without touching the flag:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Checkbox column, and selected rows are highlighted too.
|
|
106
|
+
const grid = useTMDataGrid({ data, columns, highlightSelectedRows: true });
|
|
107
|
+
|
|
108
|
+
<TMDataGrid
|
|
109
|
+
{...grid}
|
|
110
|
+
style={{ "--dg-row-selected-bg": "var(--mantine-color-blue-0)" }}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Rows carry `data-selected` whenever selected and `data-highlighted` only when
|
|
115
|
+
also highlighted, so custom styling can key off either.
|
|
116
|
+
|
|
117
|
+
## Pagination
|
|
118
|
+
|
|
119
|
+
Off by default: the grid renders every filtered and sorted row and relies on
|
|
120
|
+
virtualization. Three modes:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// No pagination (default) — TMDataGrid.Footer renders nothing.
|
|
124
|
+
const grid = useTMDataGrid({ data, columns });
|
|
125
|
+
|
|
126
|
+
// Client pagination — pages locally, Footer renders its pager.
|
|
127
|
+
const grid = useTMDataGrid({ data, columns, enablePagination: true });
|
|
128
|
+
|
|
129
|
+
// Manual pagination — manualPagination implies enablePagination.
|
|
130
|
+
const grid = useTMDataGrid({
|
|
131
|
+
data: page.rows,
|
|
132
|
+
columns,
|
|
133
|
+
manualPagination: true,
|
|
134
|
+
rowCount: page.total,
|
|
135
|
+
state: { pagination },
|
|
136
|
+
onPaginationChange: setPagination,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The built-in pager can be replaced with the Footer's `pagination` render prop
|
|
141
|
+
(see the `getting-started` skill) or built from scratch on the table API.
|
|
67
142
|
|
|
68
143
|
## Capability helpers
|
|
69
144
|
|
|
@@ -90,7 +165,8 @@ function ExportButton() {
|
|
|
90
165
|
| `canHideAny` | At least one leaf column can be hidden. |
|
|
91
166
|
| `canPinAny` | At least one leaf column can be pinned. |
|
|
92
167
|
| `canReorderAny` | At least one leaf column can be moved. |
|
|
93
|
-
| `canSelectRows` | `enableRowSelection` is not `false`. |
|
|
168
|
+
| `canSelectRows` | `enableRowSelection` is not `false`. The mode is in `features.rowSelectionMode`. |
|
|
169
|
+
| `canPaginate` | `enablePagination` or `manualPagination` is `true`. |
|
|
94
170
|
|
|
95
171
|
`getColumnCapabilities(column, features)` returns the same information for a
|
|
96
172
|
single column as `canSort`, `canFilter`, `canHide`, `canPin`, `canResize` and
|
|
@@ -207,6 +283,18 @@ table.setColumnOrder(["__select__", "department", "id", …]);
|
|
|
207
283
|
moveColumn({ table, columnId: "department", targetId: "id", side: "before" });
|
|
208
284
|
```
|
|
209
285
|
|
|
286
|
+
### Rendering the Footer without enabling pagination
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
// Wrong — pagination defaults to off, so the Footer renders nothing.
|
|
290
|
+
const grid = useTMDataGrid({ data, columns });
|
|
291
|
+
<TMDataGrid.Footer />
|
|
292
|
+
|
|
293
|
+
// Right — opt in (manualPagination: true also counts).
|
|
294
|
+
const grid = useTMDataGrid({ data, columns, enablePagination: true });
|
|
295
|
+
<TMDataGrid.Footer />
|
|
296
|
+
```
|
|
297
|
+
|
|
210
298
|
### Expecting a table-level flag to override a column
|
|
211
299
|
|
|
212
300
|
Table and column options compose — the table flag gates the feature, and the
|
|
@@ -10,19 +10,20 @@ description: >
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: core
|
|
12
12
|
library: '@jielga/tmdatagrid'
|
|
13
|
-
library_version: '0.
|
|
13
|
+
library_version: '0.2.0'
|
|
14
14
|
sources:
|
|
15
15
|
- 'Jielga/TMDataGrid:src/docs/getting-started.md'
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/components.md'
|
|
17
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/TMDataGrid.tsx'
|
|
18
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/sizes.ts'
|
|
17
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
|
|
18
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/sizes.ts'
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# TMDataGrid — Getting started
|
|
22
22
|
|
|
23
23
|
`useTMDataGrid` creates the table, `TMDataGrid` provides it through context, and
|
|
24
24
|
the parts rendered inside read what they need from that context. Only the parts
|
|
25
|
-
you render exist
|
|
25
|
+
you render exist, and only the features you enable have state: pagination is
|
|
26
|
+
opt-in via `enablePagination` — by default every row renders, virtualized.
|
|
26
27
|
|
|
27
28
|
## Install
|
|
28
29
|
|
|
@@ -69,6 +70,7 @@ export function Employees({ data }: { data: Employee[] }) {
|
|
|
69
70
|
data,
|
|
70
71
|
columns,
|
|
71
72
|
getRowId: (row) => String(row.id),
|
|
73
|
+
enablePagination: true,
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
return (
|
|
@@ -101,13 +103,14 @@ Spread the hook result (`{...grid}`) rather than assigning `table`, `ui` and
|
|
|
101
103
|
| Column menu | On hover: sort, filter, pin, move, hide, manage columns. |
|
|
102
104
|
| Filter panel | Column, operator and value rows. |
|
|
103
105
|
| Column manager | Search, toggle, show/hide all, reset. |
|
|
104
|
-
| Row selection | Checkbox column pinned to the left
|
|
105
|
-
| Pagination |
|
|
106
|
+
| Row selection | Checkbox column pinned to the left, or click-to-select rows with `rowSelectionMode: "row"`. |
|
|
107
|
+
| Pagination | Off by default: all rows render, virtualized. Opt in with `enablePagination: true`. |
|
|
106
108
|
| Sizing | `size="xs"` to `size="xl"` scales rows, type and controls. |
|
|
107
109
|
|
|
108
110
|
Each is bound to a capability check — disabling the standard table or column
|
|
109
|
-
option also removes its interface. Column ordering
|
|
110
|
-
|
|
111
|
+
option also removes its interface. Column ordering (`enableColumnOrdering`) and
|
|
112
|
+
pagination (`enablePagination`) are the two switches the grid defines itself.
|
|
113
|
+
See the `features` skill.
|
|
111
114
|
|
|
112
115
|
## Layout
|
|
113
116
|
|
|
@@ -125,9 +128,9 @@ and presence are up to you — the root is a plain flex column.
|
|
|
125
128
|
|
|
126
129
|
| Component | Props | Notes |
|
|
127
130
|
| --- | --- | --- |
|
|
128
|
-
| `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. |
|
|
129
|
-
| `TMDataGrid.Table` | `onRowClick(row)` | Header, virtualized body, filter panel. |
|
|
130
|
-
| `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`) | Pagination controls. |
|
|
131
|
+
| `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. `style` also takes CSS variables: `--dg-row-selected-bg`, `--dg-row-height`, `--dg-header-height`, `--dg-font-size`, `--dg-padding`. |
|
|
132
|
+
| `TMDataGrid.Table` | `onRowClick(row)` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `rowSelectionMode: "row"`. |
|
|
133
|
+
| `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`), `pagination` render prop | Pagination controls. Renders nothing unless pagination is enabled. |
|
|
131
134
|
| `TMDataGrid.Toolbar` | `children` | Flex row above the grid. |
|
|
132
135
|
| `TMDataGrid.Spacer` | — | Pushes later toolbar items right. |
|
|
133
136
|
| `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
|
|
@@ -146,6 +149,26 @@ Pass the row type so `onRowClick` stays typed:
|
|
|
146
149
|
`options.rowCount`. `TMDataGrid.SummaryCount` uses `meta.totalRowCount` when
|
|
147
150
|
provided, otherwise the pre-filtered row count.
|
|
148
151
|
|
|
152
|
+
The Footer's `pagination` render prop replaces the built-in pager with your own
|
|
153
|
+
UI, fed by the distilled pagination API:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<TMDataGrid.Footer
|
|
157
|
+
pagination={(api) => (
|
|
158
|
+
<Pagination
|
|
159
|
+
total={api.pageCount}
|
|
160
|
+
value={api.pageIndex + 1}
|
|
161
|
+
onChange={(page) => api.setPageIndex(page - 1)}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`TMDataGridPaginationApi` carries `pageIndex`, `pageSize`, `pageCount`,
|
|
168
|
+
`rowCount`, `canPreviousPage`, `canNextPage` and the actions `setPageIndex`,
|
|
169
|
+
`setPageSize`, `previousPage`, `nextPage`, `firstPage`, `lastPage`. Outside the
|
|
170
|
+
Footer, `getTMDataGridPaginationApi(grid.table)` returns the same object.
|
|
171
|
+
|
|
149
172
|
## size
|
|
150
173
|
|
|
151
174
|
Standard Mantine scale. Controls row height, header height, font size and cell
|
|
@@ -201,9 +224,9 @@ is no error.
|
|
|
201
224
|
`<TMDataGrid table={grid.table}>` omits `ui` and `features`. Panels stop opening
|
|
202
225
|
and feature toggles stop being reactive. Spread instead: `<TMDataGrid {...grid}>`.
|
|
203
226
|
|
|
204
|
-
### Expecting Footer to
|
|
227
|
+
### Expecting Footer to enable pagination
|
|
205
228
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
229
|
+
Rendering `TMDataGrid.Footer` does not switch pagination on — the option does.
|
|
230
|
+
With pagination off (the default) the Footer renders nothing and every row is
|
|
231
|
+
rendered, virtualized. Set `enablePagination: true` (or `manualPagination:
|
|
232
|
+
true` for server paging) to page the data and show the pager.
|
package/skills/options/SKILL.md
CHANGED
|
@@ -4,7 +4,8 @@ description: >
|
|
|
4
4
|
useTMDataGrid options, return value and state persistence. Covers passthrough
|
|
5
5
|
of TanStack TableOptions, the default initialState (pagination, columnPinning,
|
|
6
6
|
globalFilterFn), the meta object (loading, noResultsLabel, rowHeight,
|
|
7
|
-
totalRowCount), the grid's own persist
|
|
7
|
+
totalRowCount), the grid's own persist, enableColumnOrdering,
|
|
8
|
+
enablePagination, rowSelectionMode and highlightSelectedRows options, the
|
|
8
9
|
persist option with dataKey/settingsKey slice selection and storageMode, the
|
|
9
10
|
returned table/ui/features triple, ui panel and column drag actions, and
|
|
10
11
|
reading grid state with useSelector. Load when configuring the hook,
|
|
@@ -12,11 +13,11 @@ description: >
|
|
|
12
13
|
metadata:
|
|
13
14
|
type: core
|
|
14
15
|
library: '@jielga/tmdatagrid'
|
|
15
|
-
library_version: '0.
|
|
16
|
+
library_version: '0.2.0'
|
|
16
17
|
sources:
|
|
17
18
|
- 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
|
|
18
19
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
19
|
-
- 'Jielga/TMDataGrid:src/tmdatagrid/persistence.ts'
|
|
20
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/persistence.ts'
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
# TMDataGrid — useTMDataGrid
|
|
@@ -37,21 +38,25 @@ All TanStack `TableOptions` are supported and passed through unchanged, includin
|
|
|
37
38
|
flags and `rowCount`. The `features` option is supplied internally and cannot be
|
|
38
39
|
overridden.
|
|
39
40
|
|
|
40
|
-
`persist`
|
|
41
|
-
|
|
41
|
+
`persist`, `enableColumnOrdering`, `enablePagination`, `rowSelectionMode` and
|
|
42
|
+
`highlightSelectedRows` are the grid's own options and are consumed by the hook
|
|
43
|
+
rather than forwarded to TanStack.
|
|
42
44
|
|
|
43
45
|
| Option | Type | Default | Description |
|
|
44
46
|
| --- | --- | --- | --- |
|
|
45
47
|
| `data` | `TData[]` | – | Row data. Keep the reference stable with `useMemo`. |
|
|
46
48
|
| `columns` | `ColumnDef[]` | – | Created with `createTMDataGridColumnHelper`. |
|
|
47
49
|
| `getRowId` | `(row, index) => string` | Row index | Used by row selection and virtualization. |
|
|
48
|
-
| `enableRowSelection` | `boolean \| (row) => boolean` | `true` | `false` removes
|
|
50
|
+
| `enableRowSelection` | `boolean \| (row) => boolean` | `true` | `false` removes row selection and its checkbox column. |
|
|
51
|
+
| `rowSelectionMode` | `"checkbox" \| "row"` | `"checkbox"` | `"row"` drops the checkbox column and selects on row click. Defined by the grid. |
|
|
52
|
+
| `highlightSelectedRows` | `boolean` | Follows the mode | Highlight background on selected rows: off for `"checkbox"`, on for `"row"`. Colour is the `--dg-row-selected-bg` CSS variable. Defined by the grid. |
|
|
49
53
|
| `enableSorting` | `boolean` | `true` | Enables sorting for the table. |
|
|
50
54
|
| `enableColumnFilters` | `boolean` | `true` | Enables filtering for the table. |
|
|
51
55
|
| `enableHiding` | `boolean` | `true` | Enables column visibility for the table. |
|
|
52
56
|
| `enableColumnPinning` | `boolean` | `true` | Enables pinning for the table. |
|
|
53
57
|
| `enableColumnResizing` | `boolean` | `true` | Enables resizing for the table. |
|
|
54
58
|
| `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid. |
|
|
59
|
+
| `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid. |
|
|
55
60
|
| `columnResizeMode` | `"onChange" \| "onEnd"` | `"onChange"` | Resize update strategy. |
|
|
56
61
|
| `initialState` | `Partial<TableState>` | See below | Merged over the grid defaults. |
|
|
57
62
|
| `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration, see below. |
|
|
@@ -61,7 +66,7 @@ by the hook rather than forwarded to TanStack.
|
|
|
61
66
|
|
|
62
67
|
| Slice | Default |
|
|
63
68
|
| --- | --- |
|
|
64
|
-
| `pagination` | `{ pageIndex: 0, pageSize: 25 }` |
|
|
69
|
+
| `pagination` | `{ pageIndex: 0, pageSize: 25 }` — inert until pagination is enabled |
|
|
65
70
|
| `columnPinning.left` | The checkbox column, followed by any columns you provide |
|
|
66
71
|
| `globalFilterFn` | `"includesString"` |
|
|
67
72
|
|
|
@@ -10,7 +10,7 @@ description: >
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: core
|
|
12
12
|
library: '@jielga/tmdatagrid'
|
|
13
|
-
library_version: '0.
|
|
13
|
+
library_version: '0.2.0'
|
|
14
14
|
sources:
|
|
15
15
|
- 'Jielga/TMDataGrid:src/docs/server-side.md'
|
|
16
16
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
@@ -21,7 +21,10 @@ sources:
|
|
|
21
21
|
The grid reads rows through `getPaginatedRowModel()` and totals through
|
|
22
22
|
`getRowCount()` and `getPageCount()`, all of which respect TanStack's manual
|
|
23
23
|
modes. A server-driven grid therefore needs only the standard `manual*`
|
|
24
|
-
configuration — no grid-specific options.
|
|
24
|
+
configuration — no grid-specific options. `manualPagination: true` also
|
|
25
|
+
switches the grid's pagination flag on, so `TMDataGrid.Footer` renders its
|
|
26
|
+
pager without `enablePagination`. Declare `pageCount: -1` when the total is
|
|
27
|
+
unknown; the next button then stays enabled.
|
|
25
28
|
|
|
26
29
|
## Setup
|
|
27
30
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* The grid root: the size scale every other module reads, and the frame.
|
|
2
|
+
Each component owns its own module; these variables cascade into all of
|
|
3
|
+
them, and the pinned-column boundary shared by header and body cells lives
|
|
4
|
+
in sticky.module.css. */
|
|
5
|
+
|
|
6
|
+
/* Size scale. Row heights mirror SIZE_ROW_HEIGHT in core/sizes.ts — the
|
|
7
|
+
virtualizer needs the number in JS, so the two have to agree. */
|
|
8
|
+
.root[data-size="xs"] {
|
|
9
|
+
--dg-row-height: 34px;
|
|
10
|
+
--dg-header-height: 32px;
|
|
11
|
+
--dg-font-size: var(--mantine-font-size-xs);
|
|
12
|
+
--dg-padding: 6px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.root[data-size="sm"] {
|
|
16
|
+
--dg-row-height: 42px;
|
|
17
|
+
--dg-header-height: 38px;
|
|
18
|
+
--dg-font-size: var(--mantine-font-size-sm);
|
|
19
|
+
--dg-padding: 8px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.root[data-size="md"] {
|
|
23
|
+
--dg-row-height: 52px;
|
|
24
|
+
--dg-header-height: 44px;
|
|
25
|
+
--dg-font-size: var(--mantine-font-size-sm);
|
|
26
|
+
--dg-padding: 10px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.root[data-size="lg"] {
|
|
30
|
+
--dg-row-height: 62px;
|
|
31
|
+
--dg-header-height: 52px;
|
|
32
|
+
--dg-font-size: var(--mantine-font-size-md);
|
|
33
|
+
--dg-padding: 14px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.root[data-size="xl"] {
|
|
37
|
+
--dg-row-height: 72px;
|
|
38
|
+
--dg-header-height: 60px;
|
|
39
|
+
--dg-font-size: var(--mantine-font-size-lg);
|
|
40
|
+
--dg-padding: 18px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.root {
|
|
44
|
+
/* Fallbacks, so the parts still work if rendered outside a sized root. */
|
|
45
|
+
--dg-row-height: 52px;
|
|
46
|
+
--dg-header-height: 44px;
|
|
47
|
+
--dg-font-size: var(--mantine-font-size-sm);
|
|
48
|
+
--dg-padding: 10px;
|
|
49
|
+
/* Background of a highlighted (selected) row. Override it on the grid:
|
|
50
|
+
<TMDataGrid style={{ "--dg-row-selected-bg": "var(--mantine-color-blue-0)" }} /> */
|
|
51
|
+
--dg-row-selected-bg: var(--mantine-primary-color-light);
|
|
52
|
+
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
min-height: 0;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
/* Header text is a stretched flex item, so an inherited `text-align: center`
|
|
58
|
+
from the host page would centre headers away from their cell content. */
|
|
59
|
+
text-align: left;
|
|
60
|
+
border: 1px solid var(--mantine-color-default-border);
|
|
61
|
+
border-radius: var(--mantine-radius-md);
|
|
62
|
+
background-color: var(--mantine-color-body);
|
|
63
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { RowData } from "@tanstack/react-table";
|
|
|
2
2
|
import { type CSSProperties, type ReactNode, useMemo } from "react";
|
|
3
3
|
import classes from "./TMDataGrid.module.css";
|
|
4
4
|
import { TMDataGridColumnsPanel } from "./TMDataGridColumnsPanel";
|
|
5
|
-
import { TMDataGridContextProvider } from "
|
|
5
|
+
import { TMDataGridContextProvider } from "../TMDataGridContext";
|
|
6
6
|
import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
|
|
7
7
|
import { TMDataGridFooter } from "./TMDataGridFooter";
|
|
8
8
|
import { TMDataGridTable } from "./TMDataGridTable";
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
SIZE_CONTROL_SIZE,
|
|
19
19
|
SIZE_ROW_HEIGHT,
|
|
20
20
|
type TMDataGridSize,
|
|
21
|
-
} from "
|
|
22
|
-
import type { TMDataGridApi } from "
|
|
21
|
+
} from "../core/sizes";
|
|
22
|
+
import type { TMDataGridApi } from "../useTMDataGrid";
|
|
23
23
|
|
|
24
24
|
export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
|
|
25
25
|
children: ReactNode;
|
|
@@ -29,7 +29,12 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
|
|
|
29
29
|
*/
|
|
30
30
|
size?: TMDataGridSize;
|
|
31
31
|
className?: string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Inline styles. Widened to accept CSS variables, which is how the grid's
|
|
34
|
+
* colours are themed — `--dg-row-selected-bg` for the selected row
|
|
35
|
+
* background. Plain `CSSProperties` rejects `--*` keys.
|
|
36
|
+
*/
|
|
37
|
+
style?: CSSProperties & Record<`--${string}`, string | number>;
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.columnsPanel {
|
|
2
|
+
width: 280px;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.columnsPanelSearch {
|
|
8
|
+
padding: var(--mantine-spacing-sm);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.columnsPanelList {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: var(--mantine-spacing-sm);
|
|
15
|
+
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.columnsPanelFooter {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: space-between;
|
|
22
|
+
gap: var(--mantine-spacing-sm);
|
|
23
|
+
padding: var(--mantine-spacing-sm);
|
|
24
|
+
border-top: 1px solid var(--mantine-color-default-border);
|
|
25
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Button, Checkbox, ScrollArea, Text, TextInput } from "@mantine/core";
|
|
2
2
|
import { useSelector } from "@tanstack/react-store";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import classes from "./
|
|
5
|
-
import { useTMDataGridContext } from "
|
|
6
|
-
import { getColumnLabel } from "
|
|
4
|
+
import classes from "./TMDataGridColumnsPanel.module.css";
|
|
5
|
+
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
6
|
+
import { getColumnLabel } from "../core/columnUtils";
|
|
7
7
|
import { SearchIcon } from "./icons";
|
|
8
8
|
|
|
9
9
|
/**
|