@jielga/tmdatagrid 0.1.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.
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: columns
3
+ description: >
4
+ Define TMDataGrid columns with createTMDataGridColumnHelper: column meta
5
+ (label, type, flex, align, enableOrdering), fluid minmax sizing versus fixed
6
+ width, minSize and maxSize, per-column
7
+ enableSorting/enableColumnFilter/enableHiding/enablePinning/ enableResizing,
8
+ the generated SELECT_COLUMN_ID checkbox column, and the shared filter function
9
+ with its operator list and isFilterActive. Load when adding or changing
10
+ columns, controlling widths, or wiring column filters.
11
+ metadata:
12
+ type: core
13
+ library: '@jielga/tmdatagrid'
14
+ library_version: '0.1.0'
15
+ sources:
16
+ - 'Jielga/TMDataGrid:src/docs/columns.md'
17
+ - 'Jielga/TMDataGrid:src/tmdatagrid/filterOperators.ts'
18
+ - 'Jielga/TMDataGrid:src/tmdatagrid/columnUtils.ts'
19
+ ---
20
+
21
+ # TMDataGrid — Columns
22
+
23
+ `createTMDataGridColumnHelper<TData>()` returns a TanStack column helper bound to
24
+ the grid feature set, which gives `meta` and `filterFn` their correct types.
25
+
26
+ ```tsx
27
+ import { createTMDataGridColumnHelper } from "@jielga/tmdatagrid";
28
+
29
+ const columnHelper = createTMDataGridColumnHelper<Employee>();
30
+
31
+ const columns = columnHelper.columns([
32
+ columnHelper.accessor("salary", {
33
+ header: "Salary",
34
+ minSize: 130,
35
+ meta: { type: "number", align: "right" },
36
+ cell: (info) => formatSek(info.getValue()),
37
+ }),
38
+ columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
39
+ id: "fullName",
40
+ header: "Full name",
41
+ meta: { label: "Full name" },
42
+ }),
43
+ ]);
44
+ ```
45
+
46
+ Define columns at module scope. A new array on every render rebuilds the table's
47
+ column model.
48
+
49
+ ## meta
50
+
51
+ | Field | Type | Default | Description |
52
+ | --- | --- | --- | --- |
53
+ | `label` | `string` | String header, or column id | Name used in menus and the column manager. Required when `header` is a component. |
54
+ | `type` | `"string" \| "number"` | `"string"` | Determines which filter operators are offered. |
55
+ | `flex` | `number` | `1` | Share of the remaining width. |
56
+ | `align` | `"left" \| "right" \| "center"` | `"left"` | Applied to both header and cells. |
57
+ | `enableOrdering` | `boolean` | `true` | `false` keeps the column where it is. |
58
+
59
+ `enableOrdering` lives in `meta` because column ordering is the one feature
60
+ TanStack defines no column option for. See the `features` skill.
61
+
62
+ ## Sizing
63
+
64
+ Columns are fluid. Each track is `minmax(minSize, flex fr)`.
65
+
66
+ | Option | Effect |
67
+ | --- | --- |
68
+ | `minSize` | Minimum width, and the column's contribution to the grid minimum width. Defaults to `80`. |
69
+ | `meta.flex` | Share of the remaining width. |
70
+ | `minSize === maxSize` | Fixed width. The column is never fluid. |
71
+ | `size` | Applied once the column becomes fixed by resizing or pinning. |
72
+
73
+ A column switches to a fixed pixel width when resized or pinned. Pinning requires
74
+ it because sticky offsets are calculated from `getSize()`, which cannot resolve
75
+ an `fr` value. The grid stores the column's rendered width in `columnSizing` at
76
+ the moment it is pinned, so its width does not change.
77
+
78
+ ```tsx
79
+ // Fixed 64px action column that never flexes.
80
+ columnHelper.display({
81
+ id: "actions",
82
+ minSize: 64,
83
+ maxSize: 64,
84
+ cell: (info) => <RowMenu id={info.row.original.id} />,
85
+ });
86
+ ```
87
+
88
+ ## Column-level feature options
89
+
90
+ Standard TanStack column options. Each also removes the corresponding interface.
91
+
92
+ | Option | Effect when `false` |
93
+ | --- | --- |
94
+ | `enableSorting` | No sort indicator and no sort menu items. |
95
+ | `enableColumnFilter` | No filter menu item. Excluded from the filter panel column list. |
96
+ | `enableHiding` | No hide menu item. Checkbox disabled in the column manager. |
97
+ | `enablePinning` | No pin menu items. |
98
+ | `enableResizing` | The divider is displayed but cannot be dragged. |
99
+
100
+ `meta.enableOrdering: false` belongs to the same set and removes the column's
101
+ header dragging and move menu items. A column inside a header group is never
102
+ movable whatever it says, because `columnOrder` sequences leaf columns.
103
+
104
+ A column whose menu has no remaining items renders no menu button.
105
+
106
+ ## Checkbox column
107
+
108
+ Unless `enableRowSelection` is `false`, a column with the id `SELECT_COLUMN_ID`
109
+ is added as the first column and pinned to the left. It is listed as "Checkbox
110
+ selection" in the column manager and can be hidden like any other column. It has
111
+ no column menu and cannot be sorted, filtered, resized, re-pinned or moved.
112
+
113
+ Because it cannot be moved, it also anchors the left pinned region: no column
114
+ can be placed in front of it.
115
+
116
+ ## Filtering
117
+
118
+ All columns share one filter function. The operator lives in the filter value
119
+ rather than being selected through `filterFn`:
120
+
121
+ ```ts
122
+ type TMDataGridFilterValue = {
123
+ operator: TMDataGridFilterOperator;
124
+ value: string;
125
+ };
126
+ ```
127
+
128
+ The filter model is therefore plain JSON and can be forwarded to a server without
129
+ transformation. See the `server-side` skill.
130
+
131
+ ### Operators
132
+
133
+ | Operator | Label | Column type |
134
+ | --- | --- | --- |
135
+ | `contains` | contains | `string` |
136
+ | `equals` | equals | `string`, `number` |
137
+ | `notEquals` | does not equal | `string`, `number` |
138
+ | `startsWith` | starts with | `string` |
139
+ | `endsWith` | ends with | `string` |
140
+ | `greaterThan` | is greater than | `number` |
141
+ | `greaterThanOrEqual` | is greater than or equal to | `number` |
142
+ | `lessThan` | is less than | `number` |
143
+ | `lessThanOrEqual` | is less than or equal to | `number` |
144
+ | `isEmpty` | is empty | `string`, `number` |
145
+ | `isNotEmpty` | is not empty | `string`, `number` |
146
+
147
+ `meta.type` selects the list. String comparisons are case-insensitive.
148
+ `FILTER_OPERATOR_LABELS` maps operators to their labels.
149
+
150
+ To give a column its own matching logic, set `filterFn` on the column definition.
151
+ The grid only provides `"tmDataGrid"` as the default.
152
+
153
+ ## Common mistakes
154
+
155
+ ### Component header without meta.label
156
+
157
+ `getColumnLabel(column)` falls back to a *string* header, then the column id.
158
+ When `header` is a component there is no string to fall back to, so the column
159
+ manager and column menus show the raw id (`"fullName"`) instead of a name.
160
+
161
+ ```tsx
162
+ // Wrong — menus show "fullName".
163
+ columnHelper.accessor("fullName", { header: () => <Icon /> });
164
+
165
+ // Right.
166
+ columnHelper.accessor("fullName", {
167
+ header: () => <Icon />,
168
+ meta: { label: "Full name" },
169
+ });
170
+ ```
171
+
172
+ ### Treating an empty filter as no filter
173
+
174
+ A filter with an empty value stays in state so the panel keeps showing its row
175
+ while the user types. It matches all rows and does not light up the header
176
+ indicator. Test with `isFilterActive(value)` rather than checking for presence:
177
+
178
+ ```ts
179
+ import { isFilterActive } from "@jielga/tmdatagrid";
180
+
181
+ const active = columnFilters.filter((filter) => isFilterActive(filter.value));
182
+ ```
183
+
184
+ ### Numeric operators on a column without meta.type
185
+
186
+ `getColumnType(column)` defaults to `"string"`, so a numeric column that omits
187
+ `meta: { type: "number" }` offers only the string operators — `greaterThan` and
188
+ the other comparisons never appear in the panel, and comparisons run as text.
189
+
190
+ ### Setting size to control width
191
+
192
+ `size` is only applied once the column becomes fixed by resizing or pinning.
193
+ On a fluid column it is ignored; the track is `minmax(minSize, flex fr)`. Use
194
+ `minSize` for a floor and `meta.flex` for the share, or `minSize === maxSize`
195
+ for a genuinely fixed column.
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: features
3
+ description: >
4
+ Enable or disable TMDataGrid interface elements through standard TanStack
5
+ table and column options, and reuse the same checks in your own components.
6
+ Covers the enableSorting/enableColumnFilters/enableHiding/enableColumnPinning/
7
+ enableColumnResizing/enableColumnOrdering/enableRowSelection matrix,
8
+ getGridCapabilities and getColumnCapabilities, why the features argument is
9
+ required for reactivity under the React Compiler, column pinning, column
10
+ ordering with moveColumn and moveColumnByStep, and always-on virtualization.
11
+ Load when building a read-only grid, hiding grid chrome, reordering columns
12
+ from code, or writing a custom toolbar button.
13
+ metadata:
14
+ type: core
15
+ library: '@jielga/tmdatagrid'
16
+ library_version: '0.1.0'
17
+ sources:
18
+ - 'Jielga/TMDataGrid:src/docs/features.md'
19
+ - 'Jielga/TMDataGrid:src/tmdatagrid/capabilities.ts'
20
+ - 'Jielga/TMDataGrid:src/tmdatagrid/columnOrdering.ts'
21
+ ---
22
+
23
+ # TMDataGrid — Features and capabilities
24
+
25
+ Almost every control is bound to the TanStack capability check for that feature,
26
+ so setting the standard table or column option removes the corresponding
27
+ interface. Empty menus and inactive buttons are never rendered.
28
+
29
+ Column ordering is the one exception. TanStack's `columnOrderingFeature` ships
30
+ state and APIs but no `enable` option, so the grid defines
31
+ `enableColumnOrdering` and `meta.enableOrdering` itself. They behave like the
32
+ options around them.
33
+
34
+ ## Option reference
35
+
36
+ | Option | Level | Interface removed |
37
+ | --- | --- | --- |
38
+ | `enableSorting: false` | Table, column | Sort indicator, sort menu items, click-to-sort |
39
+ | `enableColumnFilters: false` | Table | Filter menu item, `FilterButton`, filter panel |
40
+ | `enableColumnFilter: false` | Column | That column's filter menu item and panel entry |
41
+ | `enableHiding: false` | Table, column | Hide column, Manage columns, `ColumnsButton` |
42
+ | `enableColumnPinning: false` | Table | Pin and unpin menu items |
43
+ | `enablePinning: false` | Column | That column's pin menu items |
44
+ | `enableColumnResizing: false` | Table | Resize dragging. The divider remains as a separator |
45
+ | `enableResizing: false` | Column | That column's resize dragging |
46
+ | `enableColumnOrdering: false` | Table | Header dragging and the move menu items |
47
+ | `meta.enableOrdering: false` | Column | That column's header dragging and move menu items |
48
+ | `enableRowSelection: false` | Table | The checkbox column |
49
+
50
+ A column whose menu has no remaining items renders no menu button. Dividers are
51
+ never left at the end of a menu.
52
+
53
+ ```tsx
54
+ // Read-only grid: no selection, hiding, pinning or filtering.
55
+ const grid = useTMDataGrid({
56
+ data,
57
+ columns,
58
+ enableRowSelection: false,
59
+ enableHiding: false,
60
+ enableColumnPinning: false,
61
+ enableColumnFilters: false,
62
+ });
63
+ ```
64
+
65
+ Pagination is not controlled by an option — omit `TMDataGrid.Footer` and no
66
+ pagination controls are rendered.
67
+
68
+ ## Capability helpers
69
+
70
+ Use these to apply the same checks in your own components.
71
+
72
+ ```tsx
73
+ import { getGridCapabilities, useTMDataGridContext } from "@jielga/tmdatagrid";
74
+
75
+ function ExportButton() {
76
+ const { table, features } = useTMDataGridContext();
77
+ const { canFilterAny } = getGridCapabilities(table, features);
78
+
79
+ if (!canFilterAny) return null;
80
+ return <Button onClick={exportRows}>Export</Button>;
81
+ }
82
+ ```
83
+
84
+ `getGridCapabilities(table, features)`:
85
+
86
+ | Field | Description |
87
+ | --- | --- |
88
+ | `canSortAny` | At least one leaf column can be sorted. |
89
+ | `canFilterAny` | At least one leaf column can be filtered. |
90
+ | `canHideAny` | At least one leaf column can be hidden. |
91
+ | `canPinAny` | At least one leaf column can be pinned. |
92
+ | `canReorderAny` | At least one leaf column can be moved. |
93
+ | `canSelectRows` | `enableRowSelection` is not `false`. |
94
+
95
+ `getColumnCapabilities(column, features)` returns the same information for a
96
+ single column as `canSort`, `canFilter`, `canHide`, `canPin`, `canResize` and
97
+ `canReorder`.
98
+
99
+ `readFeatureFlags(options)` derives the flags from a table options object.
100
+
101
+ ## Why capabilities take a features argument
102
+
103
+ `features` is returned by `useTMDataGrid` and re-derived from the options object
104
+ on every render. It is required *in addition to* TanStack's `getCanX()` methods
105
+ because it is what makes the result reactive.
106
+
107
+ `column.getCanSort()` is a method call on a column object whose identity is
108
+ preserved across an options change. Under the React Compiler the call is
109
+ memoized, so a grid whose `enableSorting` flips to `false` would keep rendering
110
+ sort indicators. Passing `features` supplies a value that actually changes, while
111
+ `getCanX()` still determines the outcome and applies per-column overrides.
112
+
113
+ The same rule applies to any value derived from the table elsewhere in an
114
+ application: read state through `useSelector(table.store, …)` and options through
115
+ `features`, rather than calling methods on a long-lived object.
116
+
117
+ ## Column pinning
118
+
119
+ Pin to left and Pin to right appear in each column menu. The current position is
120
+ marked, and selecting it again unpins. Pinned columns are sticky within the
121
+ scroll container, and the boundary is marked with a divider and a short gradient.
122
+
123
+ Headers, cells and grid tracks are ordered left, centre, right from the same
124
+ source, so pinning does not change a column's position relative to its group.
125
+
126
+ Pinning is stored in `columnPinning`, one of the slices covered by `settingsKey`
127
+ in the `options` skill.
128
+
129
+ ## Column ordering
130
+
131
+ Enabled by default. Drag a column header sideways to move it; "Move left" and
132
+ "Move right" in the column menu do the same one step at a time, and are the path
133
+ that works without a pointer.
134
+
135
+ A column only moves within its own pinned region, so a header in another region
136
+ never accepts the drop. That follows TanStack's pipeline: pinning splits the
137
+ grid into left, centre and right, then `columnOrder` sequences the centre while
138
+ `columnPinning.left` / `.right` sequence the pinned lanes. Unpin first to move a
139
+ column out of a pinned region.
140
+
141
+ Ordering writes `columnOrder` as the complete leaf order — hidden and pinned
142
+ columns included — so a column keeps its position when it is later shown or
143
+ unpinned; moving a pinned column also rewrites its `columnPinning` array. Both
144
+ slices are covered by `settingsKey`.
145
+
146
+ A neighbour that cannot be moved acts as a wall rather than being stepped over,
147
+ which is what keeps the checkbox column at the start of the left region.
148
+ Columns inside a header group are never movable: `columnOrder` sequences leaf
149
+ columns, so moving one would leave the group header spanning columns that no
150
+ longer belong to it.
151
+
152
+ ```tsx
153
+ import {
154
+ getStepTargetColumn,
155
+ moveColumn,
156
+ moveColumnByStep,
157
+ } from "@jielga/tmdatagrid";
158
+
159
+ moveColumn({ table, columnId: "salary", targetId: "age", side: "before" });
160
+ moveColumnByStep({ table, columnId: "salary", direction: 1 });
161
+
162
+ // null at the edge of a region — what the move menu items disable themselves on.
163
+ const next = getStepTargetColumn({ table, columnId: "salary", direction: 1 });
164
+ ```
165
+
166
+ Both movers are no-ops for a move that is not allowed, including one across
167
+ regions.
168
+
169
+ ## Virtualization
170
+
171
+ Always enabled and not configurable. Only rows within the viewport, plus a small
172
+ overscan, are mounted, so page size does not affect how much is rendered.
173
+
174
+ Row height comes from `meta.rowHeight`, or from the `size` prop when unset. Rows
175
+ are fixed height; an accurate value keeps scrolling precise.
176
+
177
+ ## Common mistakes
178
+
179
+ ### Calling getCanX() in a custom component
180
+
181
+ ```tsx
182
+ // Wrong — memoized under the React Compiler, so the button
183
+ // never reappears when enableColumnFilters flips back to true.
184
+ const { table } = useTMDataGridContext();
185
+ if (!table.getAllLeafColumns().some((c) => c.getCanFilter())) return null;
186
+
187
+ // Right — features is a value that changes.
188
+ const { table, features } = useTMDataGridContext();
189
+ const { canFilterAny } = getGridCapabilities(table, features);
190
+ if (!canFilterAny) return null;
191
+ ```
192
+
193
+ ### Setting a custom row height in CSS only
194
+
195
+ The virtualizer needs the height as a number to position rows. Overriding row
196
+ height in a stylesheet leaves the virtualizer measuring the old value, so rows
197
+ overlap or leave gaps as you scroll. Set `meta.rowHeight` instead.
198
+
199
+ ### Reordering a pinned column through columnOrder
200
+
201
+ ```tsx
202
+ // Wrong — the left and right regions are sequenced by columnPinning,
203
+ // so this moves nothing on screen.
204
+ table.setColumnOrder(["__select__", "department", "id", …]);
205
+
206
+ // Right — moveColumn writes whichever slice the column's region uses.
207
+ moveColumn({ table, columnId: "department", targetId: "id", side: "before" });
208
+ ```
209
+
210
+ ### Expecting a table-level flag to override a column
211
+
212
+ Table and column options compose — the table flag gates the feature, and the
213
+ column flag narrows it further. `enableSorting: true` on a column does not
214
+ re-enable sorting for a table that set `enableSorting: false`.
@@ -0,0 +1,209 @@
1
+ ---
2
+ name: getting-started
3
+ description: >
4
+ Set up TMDataGrid, a compound React data grid built on TanStack Table v9 and
5
+ Mantine. Covers useTMDataGrid, the TMDataGrid root, context, the component
6
+ catalog (Table, Footer, Toolbar, Spacer, SummaryCount, FilterButton,
7
+ ColumnsButton, FilterPanel, ColumnsPanel), the size scale and the
8
+ bounded-height layout requirement. Load when adding a grid, choosing which
9
+ parts to render, or when rows do not appear.
10
+ metadata:
11
+ type: core
12
+ library: '@jielga/tmdatagrid'
13
+ library_version: '0.1.0'
14
+ sources:
15
+ - 'Jielga/TMDataGrid:src/docs/getting-started.md'
16
+ - 'Jielga/TMDataGrid:src/docs/components.md'
17
+ - 'Jielga/TMDataGrid:src/tmdatagrid/TMDataGrid.tsx'
18
+ - 'Jielga/TMDataGrid:src/tmdatagrid/sizes.ts'
19
+ ---
20
+
21
+ # TMDataGrid — Getting started
22
+
23
+ `useTMDataGrid` creates the table, `TMDataGrid` provides it through context, and
24
+ the parts rendered inside read what they need from that context. Only the parts
25
+ you render exist: leave out `TMDataGrid.Footer` and there is no pagination.
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ npm install @jielga/tmdatagrid
31
+ ```
32
+
33
+ Peer dependencies: `react` and `react-dom` (19.1+), `@mantine/core`,
34
+ `@tanstack/react-table` (v9), `@tanstack/react-store`, `@tanstack/store`,
35
+ `@tanstack/react-virtual`, `@tabler/icons-react`.
36
+
37
+ Import both stylesheets once, Mantine's first, and render inside a
38
+ `MantineProvider`:
39
+
40
+ ```ts
41
+ import "@mantine/core/styles.css";
42
+ import "@jielga/tmdatagrid/styles.css";
43
+ ```
44
+
45
+ ## Setup
46
+
47
+ ```tsx
48
+ import {
49
+ createTMDataGridColumnHelper,
50
+ TMDataGrid,
51
+ useTMDataGrid,
52
+ } from "@jielga/tmdatagrid";
53
+
54
+ type Employee = { id: number; firstName: string; age: number };
55
+
56
+ const columnHelper = createTMDataGridColumnHelper<Employee>();
57
+
58
+ const columns = columnHelper.columns([
59
+ columnHelper.accessor("id", { header: "ID", meta: { type: "number" } }),
60
+ columnHelper.accessor("firstName", { header: "First name" }),
61
+ columnHelper.accessor("age", {
62
+ header: "Age",
63
+ meta: { type: "number", align: "right" },
64
+ }),
65
+ ]);
66
+
67
+ export function Employees({ data }: { data: Employee[] }) {
68
+ const grid = useTMDataGrid({
69
+ data,
70
+ columns,
71
+ getRowId: (row) => String(row.id),
72
+ });
73
+
74
+ return (
75
+ <TMDataGrid {...grid} size="md" style={{ flex: 1, minHeight: 0 }}>
76
+ <TMDataGrid.Toolbar>
77
+ <TMDataGrid.SummaryCount />
78
+ <TMDataGrid.Spacer />
79
+ <TMDataGrid.FilterButton />
80
+ <TMDataGrid.ColumnsButton />
81
+ </TMDataGrid.Toolbar>
82
+
83
+ <TMDataGrid.Table<Employee> />
84
+ <TMDataGrid.Footer />
85
+ </TMDataGrid>
86
+ );
87
+ }
88
+ ```
89
+
90
+ Spread the hook result (`{...grid}`) rather than assigning `table`, `ui` and
91
+ `features` individually.
92
+
93
+ ## Defaults
94
+
95
+ | Behaviour | Notes |
96
+ | --- | --- |
97
+ | Virtualized rows | Always enabled, not configurable. Only rows in view are mounted. |
98
+ | Resizable columns | Drag the divider on a header's trailing edge. |
99
+ | Reorderable columns | Drag a header sideways, or use the column menu. |
100
+ | Sorting | Click a header, or use the column menu. |
101
+ | Column menu | On hover: sort, filter, pin, move, hide, manage columns. |
102
+ | Filter panel | Column, operator and value rows. |
103
+ | Column manager | Search, toggle, show/hide all, reset. |
104
+ | Row selection | Checkbox column pinned to the left. |
105
+ | Pagination | Only when `TMDataGrid.Footer` is rendered. |
106
+ | Sizing | `size="xs"` to `size="xl"` scales rows, type and controls. |
107
+
108
+ Each is bound to a capability check — disabling the standard table or column
109
+ option also removes its interface. Column ordering is the one switch the grid
110
+ defines itself, as `enableColumnOrdering`. See the `features` skill.
111
+
112
+ ## Layout
113
+
114
+ `TMDataGrid` renders a flex column with `overflow: hidden`. It needs a bounded
115
+ height, either `style={{ flex: 1, minHeight: 0 }}` inside a flex parent or a
116
+ fixed height. The table area then fills the remaining space.
117
+
118
+ Columns are fluid by default: each track is `minmax(minSize, flex fr)`. A column
119
+ takes a fixed pixel width once resized or pinned.
120
+
121
+ ## Components
122
+
123
+ All read the grid from context and must be rendered inside `TMDataGrid`. Order
124
+ and presence are up to you — the root is a plain flex column.
125
+
126
+ | Component | Props | Notes |
127
+ | --- | --- | --- |
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.Toolbar` | `children` | Flex row above the grid. |
132
+ | `TMDataGrid.Spacer` | — | Pushes later toolbar items right. |
133
+ | `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
134
+ | `TMDataGrid.FilterButton` | — | Toggles filter panel. Renders nothing if no column is filterable. |
135
+ | `TMDataGrid.ColumnsButton` | — | Opens column manager. Renders nothing if no column is hideable. |
136
+ | `TMDataGrid.FilterPanel` | — | Rendered by `.Table`; exported for custom layouts. |
137
+ | `TMDataGrid.ColumnsPanel` | — | Rendered by `.ColumnsButton`; exported for custom layouts. |
138
+
139
+ Pass the row type so `onRowClick` stays typed:
140
+
141
+ ```tsx
142
+ <TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
143
+ ```
144
+
145
+ `TMDataGrid.Footer` reads totals from `table.getRowCount()`, which prefers
146
+ `options.rowCount`. `TMDataGrid.SummaryCount` uses `meta.totalRowCount` when
147
+ provided, otherwise the pre-filtered row count.
148
+
149
+ ## size
150
+
151
+ Standard Mantine scale. Controls row height, header height, font size and cell
152
+ padding, and selects the size of the Mantine controls the grid renders.
153
+
154
+ | `size` | Row height | Header height | Font size | Cell padding |
155
+ | --- | --- | --- | --- | --- |
156
+ | `xs` | 34px | 32px | `xs` | 6px |
157
+ | `sm` | 42px | 38px | `sm` | 8px |
158
+ | `md` | 52px | 44px | `sm` | 10px |
159
+ | `lg` | 62px | 52px | `md` | 14px |
160
+ | `xl` | 72px | 60px | `lg` | 18px |
161
+
162
+ The virtualizer needs row height as a number, so it cannot come from CSS alone.
163
+ `SIZE_ROW_HEIGHT` is the exported source of these values and the stylesheet
164
+ mirrors them. Set `meta.rowHeight` for a height outside the scale.
165
+
166
+ ## Helpers
167
+
168
+ | Export | Description |
169
+ | --- | --- |
170
+ | `useTMDataGridContext()` | The enclosing grid context. Throws outside `TMDataGrid`. |
171
+ | `openColumnFilter(api, columnId)` | Adds an empty filter for a column and opens the panel. |
172
+ | `getColumnLabel(column)` | `meta.label`, a string header, or the column id. |
173
+ | `getColumnType(column)` | `meta.type`, defaulting to `"string"`. |
174
+ | `moveColumn({ table, columnId, targetId, side })` | Moves a column next to another. See the `features` skill. |
175
+ | `moveColumnByStep({ table, columnId, direction })` | Moves a column one position within its region. |
176
+ | `SELECT_COLUMN_ID` | Id of the generated checkbox column. |
177
+
178
+ ## Common mistakes
179
+
180
+ ### Columns or data defined during render
181
+
182
+ A new array on every render rebuilds the table's column model. Define `columns`
183
+ at module scope; keep `data` stable with `useMemo`.
184
+
185
+ ```tsx
186
+ // Wrong — rebuilds the column model every render.
187
+ export function Employees({ data }: { data: Employee[] }) {
188
+ const columns = columnHelper.columns([...]);
189
+ const grid = useTMDataGrid({ data, columns });
190
+ }
191
+ ```
192
+
193
+ ### Root with no bounded height
194
+
195
+ The root is `overflow: hidden`. Without `flex: 1; minHeight: 0` in a flex parent
196
+ or an explicit height, the table area collapses and no rows are visible — there
197
+ is no error.
198
+
199
+ ### Assigning the hook result field by field
200
+
201
+ `<TMDataGrid table={grid.table}>` omits `ui` and `features`. Panels stop opening
202
+ and feature toggles stop being reactive. Spread instead: `<TMDataGrid {...grid}>`.
203
+
204
+ ### Expecting Footer to control pagination
205
+
206
+ Pagination is not an option. Omitting `TMDataGrid.Footer` removes the controls
207
+ but the table still paginates at `pageSize: 25` — rows beyond the first page
208
+ simply become unreachable. Set `initialState.pagination.pageSize` if you want
209
+ every row on one page.