@jielga/tmdatagrid 2.0.0-beta.8 → 2.0.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 +5 -212
- package/dist/index.d.ts +1323 -796
- package/dist/index.js +4719 -3193
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/adding-rows.md +132 -0
- package/docs/anatomy.md +119 -0
- package/docs/card-view.md +108 -0
- package/docs/cell-selection.md +194 -0
- package/docs/column-layout.md +182 -0
- package/docs/column-menu.md +66 -0
- package/docs/columns.md +268 -0
- package/docs/components.md +311 -0
- package/docs/draft-store.md +242 -0
- package/docs/editing.md +303 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +319 -0
- package/docs/filtering.md +362 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/migrating-to-2.md +163 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +114 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +179 -0
- package/docs/quick-search.md +84 -0
- package/docs/row-details.md +115 -0
- package/docs/row-interaction.md +149 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +136 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +112 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +744 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +22 -46
- package/skills/appearance/SKILL.md +72 -19
- package/skills/cell-selection/SKILL.md +69 -78
- package/skills/columns/SKILL.md +90 -34
- package/skills/data/SKILL.md +86 -16
- package/skills/editing/SKILL.md +83 -50
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +31 -23
- package/skills/editing/references/editors-and-validation.md +24 -17
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +17 -15
- package/skills/grouping/SKILL.md +31 -16
- package/skills/options/SKILL.md +8 -8
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +150 -32
- package/skills/testing-components/SKILL.md +230 -0
- package/skills/testing-editing/SKILL.md +240 -0
- package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
- package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +73 -7
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +9 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +23 -63
- package/src/components/TMDataGridEntryRows.tsx +354 -0
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +9 -72
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +357 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +15 -53
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +88 -65
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +579 -165
- package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/components/generatedColumns.tsx +187 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/capabilities.ts +5 -5
- package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/{tmdatagrid/core → core}/editEngine.ts +1172 -388
- package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
- package/src/core/export.ts +704 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
- package/src/{tmdatagrid/core → core}/labels.ts +51 -6
- package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
- package/src/core/pageReset.ts +120 -0
- package/src/core/pagination.ts +81 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/{tmdatagrid/core → core}/summary.ts +20 -4
- package/src/{tmdatagrid/index.ts → index.ts} +70 -36
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +534 -123
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +0 -298
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
- /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# A server-backed search
|
|
2
|
+
|
|
3
|
+
The grid's state is filters, sorting and a page index.
|
|
4
|
+
An API takes a request body of its own: its own field names, its own operator set, its own status codes, and pages counted from 1.
|
|
5
|
+
This recipe is the layer between the two, and the grid state that goes into it is shown as the request that comes out.
|
|
6
|
+
|
|
7
|
+
```demo
|
|
8
|
+
file: recipes/ServerQuery.tsx
|
|
9
|
+
hint: Filter Amount or City and watch the request body under the grid change, then the result set follow it.
|
|
10
|
+
extraSources: data/orderSearchApi.ts
|
|
11
|
+
height: 700
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The grid does no filtering, sorting or paging here.
|
|
15
|
+
See [Server-side data](/docs/server-side) for the `manual*` options themselves; this page is about what to send them.
|
|
16
|
+
|
|
17
|
+
## What the endpoint takes
|
|
18
|
+
|
|
19
|
+
The demo's API is deliberately not grid-shaped:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
type OrderSearchRequest = {
|
|
23
|
+
filter: { and: Array<Predicate> };
|
|
24
|
+
orderBy: Array<{ field: string; direction: "ASC" | "DESC" }>;
|
|
25
|
+
page: { number: number; size: number };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type Predicate =
|
|
29
|
+
| { field: string; op: "in" | "notIn"; values: Array<string | number> }
|
|
30
|
+
| { field: string; op: "range"; from?: string | number; to?: string | number }
|
|
31
|
+
| { field: string; op: "isNull" | "isNotNull" }
|
|
32
|
+
// …and the scalar form, for every remaining operator:
|
|
33
|
+
| { field: string; op: "eq" | "like" | "lt" | "gte"; value: string | number };
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The response is a page envelope, not an array:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
type OrderSearchResponse = {
|
|
40
|
+
items: Array<OrderRecord>;
|
|
41
|
+
page: { number: number; size: number; totalPages: number; totalItems: number };
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Forwarding `columnFilters` unchanged, as [Server-side data](/docs/server-side#sending-filters) describes, works when you own the endpoint.
|
|
46
|
+
When you do not, the translation has to live somewhere, and one module that both directions pass through is easier to keep correct than a translation spread across the fetch, the columns and the cells.
|
|
47
|
+
|
|
48
|
+
## Two tables, three functions
|
|
49
|
+
|
|
50
|
+
The whole layer is `toSearchRequest` over two lookup tables, plus `toRow` on the way back.
|
|
51
|
+
|
|
52
|
+
`QUERY_FIELDS` maps a column id onto an API field, together with the cast from what the filter control writes to what the field holds.
|
|
53
|
+
Every filter control writes strings; `totalAmount` is a number and `status` an enum, so neither end is the right place for the conversion.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const QUERY_FIELDS: Record<string, { field: string; cast: (raw: string) => string | number }> = {
|
|
57
|
+
id: { field: "orderRef", cast: Number },
|
|
58
|
+
amount: { field: "totalAmount", cast: Number },
|
|
59
|
+
status: { field: "status", cast: (raw) => STATUS_CODES[raw] ?? raw },
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A column missing from the table is one the API cannot query.
|
|
64
|
+
`toPredicate` returns `undefined` for it and the filter is dropped, rather than a field the endpoint would reject being sent.
|
|
65
|
+
|
|
66
|
+
`PREDICATE_OPS` maps the grid's operators onto the endpoint's.
|
|
67
|
+
Declare it as a `Record` over `TMDataGridFilterOperator` and not a `Partial`: an operator added by a later version of the grid then fails the build here, where it can be answered, instead of arriving at the server unmapped.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const PREDICATE_OPS: Record<TMDataGridFilterOperator, PredicateOp> = {
|
|
71
|
+
contains: "like",
|
|
72
|
+
between: "range",
|
|
73
|
+
before: "lt",
|
|
74
|
+
lessThan: "lt",
|
|
75
|
+
isAnyOf: "in",
|
|
76
|
+
isEmpty: "isNull",
|
|
77
|
+
// …and one line for every remaining operator.
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Several grid operators collapse onto one API operator.
|
|
82
|
+
A date `before` and a number `lessThan` are both `lt` once the value has been cast.
|
|
83
|
+
|
|
84
|
+
## Offering only what the endpoint answers
|
|
85
|
+
|
|
86
|
+
The demo's endpoint answers every operator the grid has, which is the exception.
|
|
87
|
+
An endpoint that has `like` and `eq` but no prefix match should not offer `startsWith` in the panel, because the only honest thing to do with it there is drop it, and a filter that silently does nothing looks like a bug.
|
|
88
|
+
`meta.filter.operators` narrows a column to the operators the query can express, and the mapping table is then declared over exactly that list:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const TEXT_OPERATORS = [
|
|
92
|
+
"contains",
|
|
93
|
+
"equals",
|
|
94
|
+
"isEmpty",
|
|
95
|
+
"isNotEmpty",
|
|
96
|
+
] as const satisfies readonly TMDataGridFilterOperator[];
|
|
97
|
+
|
|
98
|
+
const TEXT_OPS: Record<(typeof TEXT_OPERATORS)[number], PredicateOp> = {
|
|
99
|
+
contains: "like",
|
|
100
|
+
equals: "eq",
|
|
101
|
+
isEmpty: "isNull",
|
|
102
|
+
isNotEmpty: "isNotNull",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
columnHelper.accessor("customer", {
|
|
106
|
+
header: "Customer",
|
|
107
|
+
meta: { filter: { operators: TEXT_OPERATORS } },
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
One list feeds both the column and the type of the table, so an operator cannot be offered without a mapping, or mapped without being offered.
|
|
112
|
+
A fresh filter on the column opens on `meta.filter.defaultOperator` when that is set, else on the type's default when the list holds it - `contains` here - else on the list's first entry.
|
|
113
|
+
|
|
114
|
+
The lookup at the boundary still returns `undefined` for an operator it has no entry for.
|
|
115
|
+
A filter restored by `persist` from before the list was narrowed can carry one, and dropping it is the same rule as dropping a column the API cannot query.
|
|
116
|
+
|
|
117
|
+
## The three value shapes
|
|
118
|
+
|
|
119
|
+
`TMDataGridFilterValue` is `{ operator, value }`, and the operator decides what `value` holds.
|
|
120
|
+
A mapping function has to branch on all four cases, in this order:
|
|
121
|
+
|
|
122
|
+
| Operator | `value` | Sent as |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `isEmpty`, `isNotEmpty` | Not used | `{ field, op }` |
|
|
125
|
+
| `isAnyOf`, `isNoneOf` | `ReadonlyArray<string>` | `{ field, op, values }` |
|
|
126
|
+
| `between` | `[min, max]`, either end possibly `""` | `{ field, op, from?, to? }` |
|
|
127
|
+
| Everything else | `string` | `{ field, op, value }` |
|
|
128
|
+
|
|
129
|
+
An empty end of a `between` pair leaves that side of the interval open, so it becomes an absent bound rather than an empty string.
|
|
130
|
+
|
|
131
|
+
## What not to send
|
|
132
|
+
|
|
133
|
+
A filter whose value is still empty stays in the grid's state so the panel keeps its row while the user types.
|
|
134
|
+
It matches every row, so sending it as a predicate would narrow the result set to nothing.
|
|
135
|
+
`activeColumnFilters` is the test, applied across the slice: it hands back the entries that narrow the grid, with their values typed as `TMDataGridFilterValue` rather than as `unknown`.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
activeColumnFilters(state.columnFilters)
|
|
139
|
+
.map((filter) => toPredicate(filter.id, filter.value))
|
|
140
|
+
.filter((predicate): predicate is Predicate => predicate !== undefined);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Keying the fetch on the request
|
|
144
|
+
|
|
145
|
+
The request is JSON, so the JSON is both what you send and what the fetch can key on:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const requestJson = useMemo(
|
|
149
|
+
() => JSON.stringify(toSearchRequest({ columnFilters, sorting, pagination }), null, 2),
|
|
150
|
+
[columnFilters, sorting, pagination],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const request = useMemo(() => JSON.parse(requestJson) as OrderSearchRequest, [requestJson]);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`request` then changes identity only when the query changes.
|
|
157
|
+
Opening the filter panel and adding an empty row moves `columnFilters` and leaves the request alone, so no request is sent.
|
|
158
|
+
With TanStack Query, the same string is the `queryKey`.
|
|
159
|
+
|
|
160
|
+
Two things the effect owes the server:
|
|
161
|
+
|
|
162
|
+
- **Debounce.** The filter value input updates on every keystroke, so a request per keystroke is what you get without it.
|
|
163
|
+
- **Cancel.** A response that arrived after the query moved on is not this query's. A `cancelled` flag in the cleanup is enough; an `AbortController` on a real `fetch` is better.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
let cancelled = false;
|
|
168
|
+
setLoading(true);
|
|
169
|
+
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
void searchOrders(request).then((response) => {
|
|
172
|
+
if (cancelled) return;
|
|
173
|
+
setRows(response.items.map(toRow));
|
|
174
|
+
setPage(response.page);
|
|
175
|
+
setLoading(false);
|
|
176
|
+
});
|
|
177
|
+
}, 300);
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
cancelled = true;
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
};
|
|
183
|
+
}, [request]);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Paging against a page envelope
|
|
187
|
+
|
|
188
|
+
Three numbers, in three places:
|
|
189
|
+
|
|
190
|
+
| The API's | The grid's | Written as |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| `page.number`, counted from 1 | `pagination.pageIndex`, counted from 0 | `number: pageIndex + 1` |
|
|
193
|
+
| `page.totalItems`, the matched count | `rowCount` | `rowCount: page?.totalItems ?? 0` |
|
|
194
|
+
| `page.totalPages` | `state.pageCount`, derived from `rowCount / pageSize` | Nothing; the grid computes it |
|
|
195
|
+
|
|
196
|
+
`pageCount` follows from `rowCount`, so a response's `totalPages` needs forwarding only when the server pages by something other than the size the grid asked for.
|
|
197
|
+
Set `pageCount: -1` when the total is unknown, as an endpoint returning a cursor rather than a count leaves it.
|
|
198
|
+
|
|
199
|
+
The footer shows the page number through the `renderPagination` slot, keeping the built-in page-size select and pager on either side of it:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<TMDataGrid.Footer
|
|
203
|
+
renderPagination={({ Controls }) => (
|
|
204
|
+
<>
|
|
205
|
+
<Controls.PageSize />
|
|
206
|
+
<Controls.PageNumber />
|
|
207
|
+
<Controls.Pager />
|
|
208
|
+
</>
|
|
209
|
+
)}
|
|
210
|
+
/>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
A filter or a sort changes what page 3 means, and under `manualPagination` the grid takes itself back to page 1 when it does - see [the page index](/docs/server-side#the-page-index).
|
|
214
|
+
The change callbacks are therefore the plain setters.
|
|
215
|
+
|
|
216
|
+
`meta.totalRowCount` is the unfiltered total, which no filtered response carries.
|
|
217
|
+
Take it from a separate count call, or from the one the page was opened with.
|
|
218
|
+
Without it, `SummaryCount` shows the matched count alone rather than comparing it against the rows of one page.
|
|
219
|
+
|
|
220
|
+
## What the client no longer knows
|
|
221
|
+
|
|
222
|
+
Holding one page costs the grid the two things it derives from holding all of them.
|
|
223
|
+
|
|
224
|
+
**Faceted options.** `meta.options: "faceted"` reads the distinct values present in `data`, which is now one page of them.
|
|
225
|
+
The grid warns once per column about it.
|
|
226
|
+
A select column declares its own set instead:
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
columnHelper.accessor("city", {
|
|
230
|
+
meta: { type: "select", options: CITIES },
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Rows off the page.** Row selection is keyed by `getRowId`, so ids selected on an earlier page stay in `rowSelection` while their rows are unmounted.
|
|
235
|
+
Read the state rather than the row models, as [Server-side data](/docs/server-side#row-selection) describes.
|
|
236
|
+
|
|
237
|
+
## Reference
|
|
238
|
+
|
|
239
|
+
The pieces this recipe composes:
|
|
240
|
+
|
|
241
|
+
| Piece | Documented on |
|
|
242
|
+
| --- | --- |
|
|
243
|
+
| `manualFiltering`, `manualSorting`, `manualPagination`, `rowCount`, `meta.loading`, `meta.totalRowCount` | [Server-side data](/docs/server-side) |
|
|
244
|
+
| `TMDataGridFilterValue`, `TMDataGridFilterOperator`, `activeColumnFilters`, `meta.filter.operators`, `meta.filter.defaultOperator` | [Filtering](/docs/filtering) |
|
|
245
|
+
| `meta.options` | [Defining columns](/docs/columns) |
|
|
246
|
+
| `Footer` `renderPagination`, `Controls.PageSize`, `Controls.PageNumber`, `Controls.Pager` | [Pagination](/docs/pagination) |
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Server-side data
|
|
2
|
+
|
|
3
|
+
When the client holds one page and the server does the work, paging, sorting and
|
|
4
|
+
filtering all become round trips and the grid stops doing them itself.
|
|
5
|
+
|
|
6
|
+
The grid reads rows through `getPaginatedRowModel()` and totals through
|
|
7
|
+
`getRowCount()` and `getPageCount()`, all of which respect TanStack's manual
|
|
8
|
+
modes. A server-driven grid therefore requires only the standard `manual*`
|
|
9
|
+
configuration. `manualPagination: true` also switches the grid's pagination flag
|
|
10
|
+
on, so `TMDataGrid.Footer` renders its pager without `enablePagination`.
|
|
11
|
+
|
|
12
|
+
When the total is unknown, declare `pageCount: -1`: the next button stays
|
|
13
|
+
enabled and the `pagination` render prop receives `pageCount: -1`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });
|
|
19
|
+
const [sorting, setSorting] = useState([]);
|
|
20
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
21
|
+
|
|
22
|
+
const { data, isFetching } = useQuery({
|
|
23
|
+
queryKey: ["employees", pagination, sorting, columnFilters],
|
|
24
|
+
queryFn: () => fetchEmployees({ pagination, sorting, columnFilters }),
|
|
25
|
+
placeholderData: keepPreviousData,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const grid = useTMDataGrid({
|
|
29
|
+
columns,
|
|
30
|
+
data: data?.rows ?? [],
|
|
31
|
+
getRowId: (row) => String(row.id),
|
|
32
|
+
|
|
33
|
+
manualPagination: true,
|
|
34
|
+
manualSorting: true,
|
|
35
|
+
manualFiltering: true,
|
|
36
|
+
rowCount: data?.total ?? 0,
|
|
37
|
+
|
|
38
|
+
state: { pagination, sorting, columnFilters },
|
|
39
|
+
onPaginationChange: setPagination,
|
|
40
|
+
onSortingChange: setSorting,
|
|
41
|
+
onColumnFiltersChange: setColumnFilters,
|
|
42
|
+
|
|
43
|
+
meta: { loading: isFetching, totalRowCount: data?.totalUnfiltered },
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```demo
|
|
48
|
+
file: data/ServerSide.tsx
|
|
49
|
+
hint: Sorting, searching and paging are all round trips against a server with 500 ms of latency.
|
|
50
|
+
extraSources: data/orders.ts
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Differences from client-side data
|
|
54
|
+
|
|
55
|
+
| Concern | Client-side | Server-side |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Rendered rows | The current page, sliced locally | The rows returned by the server |
|
|
58
|
+
| Footer total | Pre-paginated row count | `options.rowCount` |
|
|
59
|
+
| `SummaryCount` total | Pre-filtered row count | `meta.totalRowCount`; without it, the count renders alone |
|
|
60
|
+
| Loading state | Not applicable | `meta.loading` |
|
|
61
|
+
|
|
62
|
+
No additional configuration is required. Column menus, the filter panel and the
|
|
63
|
+
column manager behave identically in both modes.
|
|
64
|
+
|
|
65
|
+
## The page index
|
|
66
|
+
|
|
67
|
+
A column filter, the quick search or a sort changes what page 3 means.
|
|
68
|
+
The result set is a different one, and it may not have a page 3 at all - so the grid resets `pageIndex` to 0 whenever the query changes, on every grid.
|
|
69
|
+
The reset is applied in the same event as the change, so one request goes out, for the first page of the new query.
|
|
70
|
+
|
|
71
|
+
`resetPageOnQueryChange: false` switches it off.
|
|
72
|
+
|
|
73
|
+
TanStack's `autoResetPageIndex` is a different rule and does not cover this.
|
|
74
|
+
It fires on a change to `data` - which server-side is the response landing, after the request was sent, and under `editing.draft` every commit - so the grid switches it off everywhere and resets on the query change itself.
|
|
75
|
+
|
|
76
|
+
## Column options
|
|
77
|
+
|
|
78
|
+
`meta.options: "faceted"` reads the distinct values present in `data`, which server-side is one page of them.
|
|
79
|
+
The dropdown then offers whatever happened to be on the page the user is looking at, and looks correct while being wrong.
|
|
80
|
+
Declare the set instead, as a list or a function.
|
|
81
|
+
The grid warns once per column when a faceted column resolves under `manualFiltering` or `manualPagination`.
|
|
82
|
+
|
|
83
|
+
## Sending filters
|
|
84
|
+
|
|
85
|
+
Filter values are plain JSON:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
[
|
|
89
|
+
{ "id": "lastName", "value": { "operator": "contains", "value": "holm" } },
|
|
90
|
+
{ "id": "age", "value": { "operator": "greaterThan", "value": "30" } },
|
|
91
|
+
{ "id": "status", "value": { "operator": "isAnyOf", "value": ["Paid", "Pending"] } },
|
|
92
|
+
{ "id": "hired", "value": { "operator": "onOrAfter", "value": "2026-01-01" } }
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Forward `columnFilters` unchanged and translate it at the API boundary.
|
|
97
|
+
`activeColumnFilters` hands back the entries that are narrowing the grid, typed:
|
|
98
|
+
`ColumnFiltersState` types `value` as `unknown`, and an entry whose value is
|
|
99
|
+
still empty matches every row.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { activeColumnFilters } from "@jielga/tmdatagrid";
|
|
103
|
+
|
|
104
|
+
const active = activeColumnFilters(columnFilters);
|
|
105
|
+
// [{ id: "lastName", value: { operator: "contains", value: "holm" } }]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It takes the `columnFilters` array, or the table where the grid owns the slice.
|
|
109
|
+
`isFilterActive(value)` is the single-value test it is built on.
|
|
110
|
+
Only the grid's own `{ operator, value }` shape is read: an entry holding some
|
|
111
|
+
other value - a custom filter control writing raw values - is dropped.
|
|
112
|
+
|
|
113
|
+
Debounce requests. The filter value input updates on every keystroke.
|
|
114
|
+
|
|
115
|
+
An endpoint that answers only some operators - `contains` and `equals` but no
|
|
116
|
+
`startsWith` - should not have the rest offered. `meta.filter.operators` on the
|
|
117
|
+
column narrows the panel and the header funnel to the operators the query can
|
|
118
|
+
express; see [Operators](/docs/filtering#operators).
|
|
119
|
+
|
|
120
|
+
For an endpoint that speaks its own query language rather than taking the
|
|
121
|
+
grid's filter model, see [A server-backed search](/docs/server-query): one
|
|
122
|
+
mapping layer turning filters, sorting and the page index into a request body,
|
|
123
|
+
and the response envelope back into rows.
|
|
124
|
+
|
|
125
|
+
## Persistence
|
|
126
|
+
|
|
127
|
+
`persist` works unchanged. `dataKey` restores filters, sorting and pagination
|
|
128
|
+
before the first request, so reloading the page repeats the query the user last
|
|
129
|
+
ran.
|
|
130
|
+
|
|
131
|
+
If the same state is also held in your own `useState`, initialise it from the
|
|
132
|
+
same source or let the grid own it. Do not maintain both independently.
|
|
133
|
+
|
|
134
|
+
## Row selection
|
|
135
|
+
|
|
136
|
+
Row selection is keyed by `getRowId`, so ids must be stable across pages. With
|
|
137
|
+
`manualPagination`, rows selected on an earlier page remain in `rowSelection`
|
|
138
|
+
even though they are no longer mounted. Read the state rather than the row
|
|
139
|
+
models:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const selectedIds = Object.keys(grid.table.store.state.rowSelection);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Infinite scroll
|
|
146
|
+
|
|
147
|
+
An alternative to the pager: keep every fetched row in `data` and load more as
|
|
148
|
+
the user scrolls. `onReachEnd` on `TMDataGrid.Table` fires as the scroll nears
|
|
149
|
+
the last row. Append the next page and the virtualizer keeps its position:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
const [rows, setRows] = useState<Order[]>([]);
|
|
153
|
+
|
|
154
|
+
const grid = useTMDataGrid({
|
|
155
|
+
data: rows,
|
|
156
|
+
columns,
|
|
157
|
+
getRowId: (row) => String(row.id),
|
|
158
|
+
meta: { loading: isFetching, totalRowCount: total },
|
|
159
|
+
enableSorting: false,
|
|
160
|
+
enableColumnFilters: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
<TMDataGrid.Table onReachEnd={() => void fetchNextPage()} />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```demo
|
|
167
|
+
file: data/InfiniteScroll.tsx
|
|
168
|
+
hint: Scroll to the bottom and keep going. 100 rows arrive at a time and the scroll position holds.
|
|
169
|
+
extraSources: data/orders.ts
|
|
170
|
+
height: 460
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Fetch page zero yourself on mount. `onReachEnd` does not fire on an empty grid.
|
|
174
|
+
|
|
175
|
+
`onReachEnd` fires once per row count, so a pending fetch is not requested again
|
|
176
|
+
until its rows land. `reachEndThreshold` (default 10) sets how many rows before
|
|
177
|
+
the end it fires. `TMDataGrid.LoadingIndicator` in the toolbar shows the fetch,
|
|
178
|
+
since the body keeps showing the rows it already has.
|
|
179
|
+
|
|
180
|
+
Two constraints apply:
|
|
181
|
+
|
|
182
|
+
- **Sorting and filtering must be server-side** (`manualSorting` /
|
|
183
|
+
`manualFiltering`) or disabled. The client only holds a prefix of the data, so
|
|
184
|
+
a client-side sort would order that prefix and present it as the whole. When a
|
|
185
|
+
server-side sort or filter changes, reset the accumulated rows and start from
|
|
186
|
+
page zero.
|
|
187
|
+
- **Not compatible with `enablePagination`.** The pager slices the same scroll
|
|
188
|
+
the callback watches, so the end reached is the page's rather than the data's.
|
|
189
|
+
The grid warns once if both are set.
|
|
190
|
+
|
|
191
|
+
## Reference
|
|
192
|
+
|
|
193
|
+
| Name | Kind | Type | Default | What it does |
|
|
194
|
+
| --- | --- | --- | --- | --- |
|
|
195
|
+
| `manualPagination` | Table option | `boolean` | `false` | The server pages. Implies `enablePagination`. |
|
|
196
|
+
| `manualSorting` | Table option | `boolean` | `false` | The server sorts. |
|
|
197
|
+
| `manualFiltering` | Table option | `boolean` | `false` | The server filters, column filters and quick search alike. |
|
|
198
|
+
| `manualGrouping` | Table option | `boolean` | `false` | The rows arrive grouped. See [Grouping](/docs/grouping#server-side-grids). |
|
|
199
|
+
| `rowCount` | Table option | `number` | – | The true total. `pageCount: -1` when it is unknown. |
|
|
200
|
+
| `meta.loading` | Option | `boolean` | `false` | A fetch is in flight. See [Loading and empty states](/docs/loading-and-empty). |
|
|
201
|
+
| `meta.totalRowCount` | Option | `number` | – | The unfiltered total, for `SummaryCount`. |
|
|
202
|
+
| `resetPageOnQueryChange` | Option | `boolean` | `true` | Back to page 1 when a filter, the quick search, the sort or the grouping changes. |
|
|
203
|
+
| `onReachEnd` | Table prop | `() => void` | – | Fires as the scroll nears the last row. Latches per row count. |
|
|
204
|
+
| `reachEndThreshold` | Table prop | `number` | `10` | How many rows before the end it fires. |
|
|
205
|
+
| `activeColumnFilters` | Export | `(columnFilters \| table) => Array<{ id, value }>` | – | The filters in the grid's own value shape that narrow anything, typed. |
|
|
206
|
+
| `isFilterActive` | Export | `(value) => boolean` | – | Whether one filter value narrows anything. |
|
package/docs/sorting.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Sorting
|
|
2
|
+
|
|
3
|
+
On by default. Click a header to sort it, click again to reverse, click a third
|
|
4
|
+
time to clear. The column menu has the same three actions.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
const grid = useTMDataGrid({ data, columns });
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```demo
|
|
11
|
+
file: columns/Sorting.tsx
|
|
12
|
+
hint: Click a header to sort, Shift+click a second to append - the badge beside the arrow is its priority.
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Sorting by more than one column
|
|
16
|
+
|
|
17
|
+
Shift+click a second header to **add** it to the sort rather than replace it.
|
|
18
|
+
While more than one column sorts, each sorted header shows its priority - 1, 2,
|
|
19
|
+
… - beside the arrow, so the order they are applied in is visible.
|
|
20
|
+
|
|
21
|
+
A plain click still replaces the whole sort, and the menu's Sort items do the
|
|
22
|
+
same.
|
|
23
|
+
|
|
24
|
+
This is TanStack's own `isMultiSortEvent`, so `enableMultiSort`,
|
|
25
|
+
`maxMultiSortColCount` and a custom `isMultiSortEvent` all pass straight
|
|
26
|
+
through:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
const grid = useTMDataGrid({
|
|
30
|
+
data,
|
|
31
|
+
columns,
|
|
32
|
+
maxMultiSortColCount: 3,
|
|
33
|
+
// Ctrl rather than Shift, say.
|
|
34
|
+
isMultiSortEvent: (event) => event.ctrlKey,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Turning it off
|
|
39
|
+
|
|
40
|
+
`enableSorting: false` on the table removes click-to-sort, the indicator and
|
|
41
|
+
the menu items everywhere; on a column it removes them for that column alone.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
columnHelper.accessor("avatar", { header: "", enableSorting: false });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A column whose menu has no remaining items renders no menu button and does not
|
|
48
|
+
handle right-click, so the browser's own menu opens instead.
|
|
49
|
+
|
|
50
|
+
## Where the state lives
|
|
51
|
+
|
|
52
|
+
Sorting writes TanStack's `sorting` state, an array of `{ id, desc }` in
|
|
53
|
+
priority order. Seed it, control it, or read it like any other slice:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const grid = useTMDataGrid({
|
|
57
|
+
data,
|
|
58
|
+
columns,
|
|
59
|
+
initialState: { sorting: [{ id: "lastName", desc: false }] },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
It is a **data** slice: it names a column and a direction over the data itself,
|
|
64
|
+
so a [persisted](/docs/use-tm-data-grid#persist) grid comes back sorted the way
|
|
65
|
+
it was left, under `dataKey`. For a server that does the sorting, see
|
|
66
|
+
[Server-side data](/docs/server-side).
|
|
67
|
+
|
|
68
|
+
Grouping runs first, so a grouped grid sorts rows within each group and orders
|
|
69
|
+
the groups by their aggregated value. See
|
|
70
|
+
[Grouping](/docs/grouping#sorting-a-grouped-grid).
|
|
71
|
+
|
|
72
|
+
## Custom comparators
|
|
73
|
+
|
|
74
|
+
`sortFn` on the column takes any of TanStack's registered names, or a function
|
|
75
|
+
of two rows and the column id. It is `sortFn` in v9, not v8's `sortingFn`.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
columnHelper.accessor("priority", {
|
|
79
|
+
header: "Priority",
|
|
80
|
+
sortFn: (rowA, rowB) =>
|
|
81
|
+
RANK[rowA.original.priority] - RANK[rowB.original.priority],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## The header menu
|
|
86
|
+
|
|
87
|
+
The menu opens from the ⋮ button on the header, or from a right-click anywhere
|
|
88
|
+
on it. The items are the same either way; a right-click opens it at the
|
|
89
|
+
pointer.
|
|
90
|
+
|
|
91
|
+
## Reference
|
|
92
|
+
|
|
93
|
+
| Name | Kind | Type | Default | What it does |
|
|
94
|
+
| --- | --- | --- | --- | --- |
|
|
95
|
+
| `enableSorting` | Table option | `boolean` | `true` | Also a column option. `false` removes indicator, menu items and click-to-sort. |
|
|
96
|
+
| `enableMultiSort` | Table option | `boolean` | `true` | Whether Shift+click appends instead of replacing. |
|
|
97
|
+
| `maxMultiSortColCount` | Table option | `number` | `Infinity` | How many columns may sort at once. |
|
|
98
|
+
| `isMultiSortEvent` | Table option | `(event) => boolean` | Shift held | What counts as "append to the sort". |
|
|
99
|
+
| `sortFn` | Column option | name \| `(rowA, rowB, columnId) => number` | `"auto"` | The comparator for one column. v9's name for v8's `sortingFn`. |
|
|
100
|
+
| `initialState.sorting` | Table option | `Array<{ id, desc }>` | `[]` | Sort at mount. A settings slice, so it persists. |
|
|
101
|
+
| `manualSorting` | Table option | `boolean` | `false` | The server sorts; the grid stops. |
|
package/docs/styling.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Size, styling and theming
|
|
2
|
+
|
|
3
|
+
The grid is themed through CSS custom properties, and sized through Mantine's
|
|
4
|
+
standard `size` scale. Both are set on the root element, so a grid can be
|
|
5
|
+
themed per instance without a provider.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<TMDataGrid {...grid} size="sm" style={{ "--dg-row-selected-bg": "color-mix(in srgb, var(--mantine-color-blue-6) 12%, transparent)" }} />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```demo
|
|
12
|
+
file: customization/Styling.tsx
|
|
13
|
+
hint: Every value the controls change is a CSS variable set on the grid element.
|
|
14
|
+
extraSources: data/employeeColumns.tsx
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The size scale
|
|
18
|
+
|
|
19
|
+
`size` drives row height, header height, font size and cell padding together,
|
|
20
|
+
and selects the size of every Mantine control the grid renders - the page-size
|
|
21
|
+
select, the filter inputs, the column checkboxes.
|
|
22
|
+
|
|
23
|
+
| `size` | Row height | Header height | Font size | Cell padding |
|
|
24
|
+
| --- | --- | --- | --- | --- |
|
|
25
|
+
| `xs` | 34px | 32px | `xs` | 6px |
|
|
26
|
+
| `sm` | 42px | 38px | `sm` | 8px |
|
|
27
|
+
| `md` (default) | 52px | 44px | `sm` | 10px |
|
|
28
|
+
| `lg` | 62px | 52px | `md` | 14px |
|
|
29
|
+
| `xl` | 72px | 60px | `lg` | 18px |
|
|
30
|
+
|
|
31
|
+
```demo
|
|
32
|
+
file: getting-started/DensityAndLayout.tsx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Row height is also required by the virtualizer **as a number**, so it cannot be
|
|
36
|
+
defined in CSS alone. `SIZE_ROW_HEIGHT` is the exported source of these values,
|
|
37
|
+
and the stylesheet mirrors them. To use a height outside the scale, set
|
|
38
|
+
`meta.rowHeight` rather than the variable.
|
|
39
|
+
|
|
40
|
+
## CSS variables
|
|
41
|
+
|
|
42
|
+
`style` accepts custom properties, and `className` reaches the same element
|
|
43
|
+
from a stylesheet.
|
|
44
|
+
The wrapper components - `Toolbar`, `Spacer`, `Footer`, `FilterPanel`, `FilterPills` and `ColumnsPanel` - take Mantine's `BoxProps` on top of their own props, so `mb="sm"` or `hiddenFrom="sm"` on any of them sets the element itself; see [Toolbar](/docs/toolbar#style-props).
|
|
45
|
+
|
|
46
|
+
### Metrics
|
|
47
|
+
|
|
48
|
+
| Variable | Default | Applies to |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `--dg-row-height` | From `size` | Row height. Prefer `meta.rowHeight` - the virtualizer needs the number. |
|
|
51
|
+
| `--dg-header-height` | From `size` | Header row height |
|
|
52
|
+
| `--dg-summary-height` | From `size` | [Summary row](/docs/summary-row) height |
|
|
53
|
+
| `--dg-entry-height` | From `size` | The sticky [entry block](/docs/adding-rows#adding-rows) |
|
|
54
|
+
| `--dg-font-size` | From `size` | Cell and header font size |
|
|
55
|
+
| `--dg-padding` | From `size` | Horizontal cell padding. The generated lanes are excluded: they are fixed 36px tracks that centre their control. |
|
|
56
|
+
| `--dg-radius` | `--mantine-radius-md` | The frame's corner radius. `0` squares the grid off. The root clips its overflow, so the header and the last row follow it. |
|
|
57
|
+
|
|
58
|
+
### Colours
|
|
59
|
+
|
|
60
|
+
Both colour schemes are supported. The grid's own stylesheet resolves its
|
|
61
|
+
colours with `light-dark()`, so it follows Mantine's scheme without a prop or a
|
|
62
|
+
second import. A default of **Themed** below means exactly that: the variable
|
|
63
|
+
resolves to one value under the light scheme and another under the dark one.
|
|
64
|
+
Set such a variable and you take over both schemes, so give it a value that
|
|
65
|
+
reads in each - `light-dark()` works in your own value too.
|
|
66
|
+
|
|
67
|
+
| Variable | Default | Applies to |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `--row-bg` | – | One row's own background. Set this, never `background`. See [Row styling](/docs/row-styling#set-the-row-background). |
|
|
70
|
+
| `--dg-row-selected-bg` | `--mantine-primary-color-light` | [Selected](/docs/row-selection) rows |
|
|
71
|
+
| `--dg-row-highlight-bg` | Themed | The highlighted row |
|
|
72
|
+
| `--dg-row-striped-bg` | Themed | Every second row under `striped` |
|
|
73
|
+
| `--dg-row-group-bg` | Themed | [Group](/docs/grouping) rows |
|
|
74
|
+
| `--dg-row-new-bg` | Green tint | New rows entered into the [draft store](/docs/draft-store) |
|
|
75
|
+
| `--dg-match-highlight-bg` | Themed yellow | [Marked](/docs/quick-search#match-highlighting) text |
|
|
76
|
+
| `--dg-header-shadow-color` | Themed | The shadow under the sticky header |
|
|
77
|
+
|
|
78
|
+
### Layout internals
|
|
79
|
+
|
|
80
|
+
| Variable | Default | Applies to |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `--dg-sticky-edge-range` | `20px` | How far the pinned-lane band takes to fade in |
|
|
83
|
+
| `--dg-edge-top` · `-bottom` · `-left` · `-right` | – | Set by the grid to mark [cell-range](/docs/cell-selection) borders |
|
|
84
|
+
| `--dg-z-header` · `-pinned-cell` · `-summary-row` · … | – | The stacking order. Change these only to place something of your own between two layers. |
|
|
85
|
+
|
|
86
|
+
## The stylesheet
|
|
87
|
+
|
|
88
|
+
One import, once, anywhere in your app:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import "@jielga/tmdatagrid/styles.css";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`@jielga/tmdatagrid/styles.layer.css` is the same stylesheet wrapped in a
|
|
95
|
+
`@layer`, for an application that orders its own layers and needs the grid to
|
|
96
|
+
sit at a known place in that order. Import **one** of the two, never both.
|
|
97
|
+
|
|
98
|
+
## Layout
|
|
99
|
+
|
|
100
|
+
The grid fills the box you give it and scrolls inside it. It does not size
|
|
101
|
+
itself to its content: a virtualized grid has no content height to measure.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
|
|
105
|
+
<TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }}>
|
|
106
|
+
<TMDataGrid.Table />
|
|
107
|
+
</TMDataGrid>
|
|
108
|
+
</div>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`minHeight: 0` is required. A flex item's default `min-height: auto` will not
|
|
112
|
+
shrink below its content, so without it the grid grows past the viewport instead
|
|
113
|
+
of scrolling.
|
|
114
|
+
|
|
115
|
+
## Reference
|
|
116
|
+
|
|
117
|
+
| Name | Kind | Type | Default | What it does |
|
|
118
|
+
| --- | --- | --- | --- | --- |
|
|
119
|
+
| `size` | Prop | `MantineSize` | `"md"` | The whole density scale. |
|
|
120
|
+
| `className` | Prop | `string` | – | Added to the root element's classes. |
|
|
121
|
+
| `style` | Prop | `CSSProperties` + `--*` | – | Root element styles, including the variables above. |
|
|
122
|
+
| `id` | Prop | `string` | – | Set on the root element. |
|
|
123
|
+
| `meta.rowHeight` | Option | `number` | From `size` | A row height outside the scale. |
|
|
124
|
+
| `SIZE_ROW_HEIGHT` | Export | `Record<MantineSize, number>` | – | The row heights listed in the table above. |
|
|
125
|
+
| `SIZE_CONTROL_SIZE` | Export | `Record<MantineSize, MantineSize>` | – | Which control size each grid size uses. |
|
|
126
|
+
| `DEFAULT_TMDATAGRID_SIZE` | Export | `"md"` | – | The default. |
|