react-gridix 0.1.0 → 0.1.1
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.html +1742 -0
- package/README.md +1043 -0
- package/dist/index.d.ts +76 -70
- package/dist/react-gridix.cjs +1 -1
- package/dist/react-gridix.js +482 -428
- package/dist/style.css +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,1043 @@
|
|
|
1
|
+
# react-gridix
|
|
2
|
+
|
|
3
|
+
A React `DataGrid` with per-column filters, in-cell editing, nested fields, themes and custom components, plus the `Checkbox` it uses.
|
|
4
|
+
|
|
5
|
+
**Contents**
|
|
6
|
+
|
|
7
|
+
1. [Overview](#1-overview)
|
|
8
|
+
2. [Installation](#2-installation)
|
|
9
|
+
3. [Quick start](#3-quick-start)
|
|
10
|
+
4. [Theming](#4-theming)
|
|
11
|
+
5. [Checkbox](#5-checkbox)
|
|
12
|
+
6. [DataGrid](#6-datagrid)
|
|
13
|
+
7. [Demos](#7-demos)
|
|
14
|
+
8. [Development, building and publishing](#8-development-building-and-publishing)
|
|
15
|
+
9. [Troubleshooting](#9-troubleshooting)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Overview
|
|
20
|
+
|
|
21
|
+
| Component | Purpose |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `DataGrid` | Data table with sorting, search, per-column filters, pagination, in-cell editing, nested fields, striping, themes and custom column components |
|
|
24
|
+
| `Checkbox` | Checkbox in three looks (button with a span, tick, native), toggles with one click. The grid uses it for boolean columns, and you can use it on its own |
|
|
25
|
+
|
|
26
|
+
Everything is written in TypeScript, ships its own type definitions, and uses plain CSS (no CSS-in-JS runtime). The grid is 100% of its container's width by default.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Installation
|
|
31
|
+
|
|
32
|
+
**Requirements:** React 18 or newer and `react-dom`.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install react-gridix
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The stylesheet is included in the package's `style.css`. Import it once, near the root of your app:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import "react-gridix/style.css";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The library does not inject CSS by itself. If the grid looks unstyled, this import is missing.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. Quick start
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { useState } from "react";
|
|
52
|
+
import { DataGrid, type ColumnDef } from "react-gridix";
|
|
53
|
+
import "react-gridix/style.css";
|
|
54
|
+
|
|
55
|
+
interface Person {
|
|
56
|
+
id: number;
|
|
57
|
+
name: string;
|
|
58
|
+
contact: { email: string; phone: string };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const columns: ColumnDef<Person>[] = [
|
|
62
|
+
{ field: "id", headerName: "ID", type: "number", editable: false },
|
|
63
|
+
{ field: "name", headerName: "Name", filterable: true },
|
|
64
|
+
{ field: "contact.email", headerName: "Email" },
|
|
65
|
+
{ field: "contact.phone", headerName: "Phone" },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export default function App() {
|
|
69
|
+
const [rows, setRows] = useState<Person[]>([
|
|
70
|
+
{ id: 1, name: "Amina", contact: { email: "amina@example.com", phone: "+256 700 100 200" } },
|
|
71
|
+
{ id: 2, name: "Brian", contact: { email: "brian@example.com", phone: "+256 700 100 237" } },
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<DataGrid
|
|
76
|
+
columns={columns}
|
|
77
|
+
rows={rows}
|
|
78
|
+
getRowId={(r) => r.id}
|
|
79
|
+
editable
|
|
80
|
+
onRowsChange={setRows}
|
|
81
|
+
theme="ocean"
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 4. Theming
|
|
90
|
+
|
|
91
|
+
### 4.1 Built-in themes
|
|
92
|
+
|
|
93
|
+
Pass a theme name to the grid. **Without a `theme` prop nothing changes**: the grid keeps its plain default look.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<DataGrid theme="midnight" ... />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Theme | Look |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `"ocean"` | Light, cool blues, blue gradient header, soft shadow |
|
|
102
|
+
| `"midnight"` | Dark slate, indigo accent, dark popups and inputs |
|
|
103
|
+
| `"sunset"` | Warm cream, orange-to-pink gradient header, serif font, rounded corners |
|
|
104
|
+
| `"forest"` | Calm greens, solid dark-green header, sharper corners |
|
|
105
|
+
|
|
106
|
+
A theme is only a set of CSS variables on `[data-rk-theme="name"]`, written with `:where()` (zero specificity). So you can still override any variable or any class on top of a theme. The filter popup is drawn in `document.body`, and the grid copies the theme attribute onto it, so the popup follows the theme too.
|
|
107
|
+
|
|
108
|
+
**Making your own theme.** Any other string works as a theme name. Define the variables you want:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
[data-rk-theme="brand"] {
|
|
112
|
+
--rk-header-bg: #7c3aed;
|
|
113
|
+
--rk-header-text: #fff;
|
|
114
|
+
--rk-row-hover: #f5f3ff;
|
|
115
|
+
--rk-primary: #7c3aed;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<DataGrid theme="brand" ... />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 4.2 CSS variables
|
|
124
|
+
|
|
125
|
+
Every variable is optional and is read with a fallback, so nothing is declared on the grid itself. Set them on `:root`, on any parent element, in your own theme, or in the grid's `style`.
|
|
126
|
+
|
|
127
|
+
| Variable | Default | Used for |
|
|
128
|
+
| --- | --- | --- |
|
|
129
|
+
| `--rk-font` | system UI stack | Font family |
|
|
130
|
+
| `--rk-text` | `#111827` | Main text |
|
|
131
|
+
| `--rk-muted` | `#6b7280` | Secondary text, empty state |
|
|
132
|
+
| `--rk-border` | `#d1d5db` | Borders |
|
|
133
|
+
| `--rk-surface` | `#ffffff` | Background of the grid, inputs, buttons |
|
|
134
|
+
| `--rk-bg-subtle` | `#f3f4f6` | Header and footer background |
|
|
135
|
+
| `--rk-primary` | `#4f46e5` | Focus rings, active editor border |
|
|
136
|
+
| `--rk-danger` | `#dc2626` | Invalid editor border |
|
|
137
|
+
| `--rk-invalid-bg` | `#fef2f2` | Invalid editor background |
|
|
138
|
+
| `--rk-grid-bg` | `--rk-surface` | Grid background |
|
|
139
|
+
| `--rk-grid-radius` | `10px` | Grid corner radius |
|
|
140
|
+
| `--rk-grid-shadow` | `none` | Grid shadow |
|
|
141
|
+
| `--rk-toolbar-bg` | `transparent` | Toolbar background |
|
|
142
|
+
| `--rk-input-bg`, `--rk-input-border`, `--rk-input-text` | surface, border, inherit | Search box, filter box, editor, page controls |
|
|
143
|
+
| `--rk-header-bg` | `--rk-bg-subtle` | Header background. Any CSS background, so gradients work |
|
|
144
|
+
| `--rk-header-text` | inherit | Header text colour |
|
|
145
|
+
| `--rk-header-hover-bg` | `rgba(0,0,0,.06)` | Header hover |
|
|
146
|
+
| `--rk-header-border` | `--rk-border` | Line under the header |
|
|
147
|
+
| `--rk-cell-border` | `#eef0f3` | Lines between rows |
|
|
148
|
+
| `--rk-row-hover` | `#f3f4f6` | Row hover |
|
|
149
|
+
| `--rk-stripe-a`, `--rk-stripe-b` | `transparent`, `#fafafa` | Alternating row colours (see [stripes](#striped-rows)) |
|
|
150
|
+
| `--rk-footer-bg`, `--rk-footer-text` | subtle, muted | Pagination bar |
|
|
151
|
+
| `--rk-btn-hover` | `#e5e7eb` | Page button and Clear button hover |
|
|
152
|
+
| `--rk-filter-active` | `--rk-primary` | Filter dot colour while a filter is set |
|
|
153
|
+
| `--rk-filter-glow` | `rgba(79,70,229,.25)` | Ring around an active filter dot |
|
|
154
|
+
| `--rk-popup-bg`, `--rk-popup-text`, `--rk-popup-border` | white, dark, grey | Filter popup |
|
|
155
|
+
| `--rk-popup-radius`, `--rk-popup-shadow` | `10px`, soft shadow | Filter popup shape |
|
|
156
|
+
| `--rk-row-duration` | `0.38s` | Row entrance animation length |
|
|
157
|
+
| `--rk-row-stagger` | `28ms` | Extra delay per row in the entrance animation |
|
|
158
|
+
| `--rk-checkbox-size` | `20px` | Checkbox width and height |
|
|
159
|
+
| `--rk-checkbox-border-width` | `1px` | Checkbox border width |
|
|
160
|
+
| `--rk-checkbox-radius` | `0` | Checkbox corner radius (no rounding by default) |
|
|
161
|
+
| `--rk-checkbox-color` | `#4f46e5` | Checked colour (also the focus ring and the native accent) |
|
|
162
|
+
| `--rk-checkbox-border` | `#9ca3af` | Border colour of an unchecked `"button"` / `"tick"` checkbox |
|
|
163
|
+
| `--rk-checkbox-bg` | `#ffffff` | Background of an unchecked `"button"` / `"tick"` checkbox |
|
|
164
|
+
| `--rk-checkbox-tick` | `#ffffff` | Colour of the tick, and of a custom character in the `"tick"` variant |
|
|
165
|
+
|
|
166
|
+
```css
|
|
167
|
+
:root {
|
|
168
|
+
--rk-primary: #0d9488;
|
|
169
|
+
--rk-row-hover: #f0fdfa;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5. Checkbox
|
|
176
|
+
|
|
177
|
+
A checkbox in three looks. Every variant toggles with **one click** (or the Space key when it has focus), and clicking the label text toggles it too.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<Checkbox variant="button" checked={a} onChange={setA} label="Button + span" />
|
|
181
|
+
<Checkbox variant="tick" checked={b} onChange={setB} label="Tick" />
|
|
182
|
+
<Checkbox variant="native" checked={c} onChange={setC} label="Native" />
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Variant | What it is |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `"button"` | A `<button role="checkbox">` drawn as a box, with a `<span>` inside that is hidden when unchecked and shown when checked |
|
|
188
|
+
| `"tick"` | The same button, but the box fills with the checked colour and the span holds a tick icon |
|
|
189
|
+
| `"native"` | The browser's own `<input type="checkbox">` |
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Default | Description |
|
|
192
|
+
| --- | --- | --- | --- |
|
|
193
|
+
| `checked` | `boolean` | required | Current value (controlled) |
|
|
194
|
+
| `indeterminate` | `boolean` | `false` | Shows the mixed ("some selected") state: a dash instead of a tick. Use it with `checked={false}`. A click then calls `onChange(true)` |
|
|
195
|
+
| `onChange` | `(checked: boolean) => void` | required | Called once per toggle with the new value |
|
|
196
|
+
| `variant` | `"button" \| "tick" \| "native"` | `"native"` | Which look to use |
|
|
197
|
+
| `disabled` | `boolean` | `false` | Disables the checkbox and dims it |
|
|
198
|
+
| `label` | `ReactNode` | | Text next to the box. Clicking it toggles the checkbox. An empty string, `null` or `false` renders no label |
|
|
199
|
+
| `aria-label` | `string` | | Accessible name when there is no visible `label` |
|
|
200
|
+
| `id` | `string` | | `id` of the control |
|
|
201
|
+
| `name` | `string` | | `name` of the input (`"native"` variant only) |
|
|
202
|
+
| `indicator` | `ReactNode` | | A custom character or icon drawn inside the `"button"` and `"tick"` variants when checked, for example `"★"` |
|
|
203
|
+
| `className` | `string` | | Classes for the root element (same as `classNames.root`) |
|
|
204
|
+
| `style` | `CSSProperties` | | Inline styles for the root element (same as `styles.root`) |
|
|
205
|
+
| `classNames` | `Partial<Record<CheckboxSlot, string>>` | | Classes per part |
|
|
206
|
+
| `styles` | `Partial<Record<CheckboxSlot, CSSProperties>>` | | Inline styles per part |
|
|
207
|
+
|
|
208
|
+
**Parts (`CheckboxSlot`):** `root` (the wrapping `<label>`), `control` (the `<button>` or `<input>`), `indicator` (the span inside, button variants only) and `label` (the text).
|
|
209
|
+
|
|
210
|
+
**Customising the look from outside**
|
|
211
|
+
|
|
212
|
+
1. **Classes and inline styles per part**, including Tailwind:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<Checkbox
|
|
216
|
+
variant="tick"
|
|
217
|
+
checked={on}
|
|
218
|
+
onChange={setOn}
|
|
219
|
+
classNames={{ control: "rounded-full border-emerald-500 aria-checked:bg-emerald-500" }}
|
|
220
|
+
styles={{ root: { gap: 12 } }}
|
|
221
|
+
/>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
(`aria-checked:` works for the `"button"` and `"tick"` variants. The native input has no `aria-checked`, so use the `--rk-checkbox-color` variable for it.)
|
|
225
|
+
2. **CSS variables**: `--rk-checkbox-size`, `--rk-checkbox-border-width`, `--rk-checkbox-radius`, `--rk-checkbox-color`, `--rk-checkbox-border`, `--rk-checkbox-bg` and `--rk-checkbox-tick` (see [Theming](#42-css-variables)). Set them anywhere above the checkbox:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<Checkbox styles={{ root: { "--rk-checkbox-color": "#dc2626" } as CSSProperties }} ... />
|
|
229
|
+
```
|
|
230
|
+
3. **A custom character**, for example `indicator="★"` (any text, emoji or React node). It shows when the box is checked, in the `"button"` and `"tick"` variants. The default fill is dropped; the character is sized to the box (70% of `--rk-checkbox-size`) and coloured with `--rk-checkbox-color` in the button variant and `--rk-checkbox-tick` in the tick variant. Restyle it through `classNames.indicator` / `styles.indicator`.
|
|
231
|
+
4. **Plain CSS** with these stable classes: `.rk-checkbox`, `.rk-checkbox--button`, `.rk-checkbox--tick`, `.rk-checkbox--native`, `.rk-checkbox--checked`, `.rk-checkbox--indeterminate`, `.rk-checkbox--disabled`, `.rk-checkbox__control`, `.rk-checkbox__indicator`, `.rk-checkbox__indicator--custom` and `.rk-checkbox__label`. State is also exposed as `data-state="checked"`, `"unchecked"` or `"indeterminate"` and, on the button variants, `aria-checked` (`"true"`, `"false"` or `"mixed"`).
|
|
232
|
+
|
|
233
|
+
**Defaults:** a 1px border and square corners (`--rk-checkbox-border-width: 1px`, `--rk-checkbox-radius: 0`). Round them with `--rk-checkbox-radius: 999px` or a Tailwind `rounded-*` class on `classNames.control`.
|
|
234
|
+
|
|
235
|
+
The checkbox CSS is written with `:where()` (zero specificity), so any class you add wins without `!important`.
|
|
236
|
+
|
|
237
|
+
**Accessibility:** the button variants have `role="checkbox"` and `aria-checked`, and the native variant is a real checkbox. When you don't pass a `label`, pass an `aria-label`.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 6. DataGrid
|
|
244
|
+
|
|
245
|
+
### 6.1 Concepts
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<DataGrid columns={columns} rows={rows} />
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- **`rows`** is an array of plain objects.
|
|
252
|
+
- **`columns`** is an array of column definitions. Each one names a `field` (a key of the row object) and describes how to show, sort, filter and edit it.
|
|
253
|
+
- **Row ids.** Pass `getRowId` when your rows have a stable id. Without it the grid uses each row's index in `rows`, which is fine for static data.
|
|
254
|
+
- **`rowIndex`** in callbacks and params is always the row's index in your original `rows` array, not its position on screen.
|
|
255
|
+
- **The grid keeps its own copy of the rows** so edits appear immediately. It re-syncs whenever the `rows` prop changes identity. See [section 6.8](#68-editing).
|
|
256
|
+
|
|
257
|
+
The grid is generic: `DataGrid<T>` infers `T` from `rows`, and `ColumnDef<T>` checks that each `field` is a real key of `T` or a dot path into a nested object (see [nested fields](#611-nested-fields)).
|
|
258
|
+
|
|
259
|
+
### 6.2 DataGrid props
|
|
260
|
+
|
|
261
|
+
| Prop | Type | Default | Description |
|
|
262
|
+
| --- | --- | --- | --- |
|
|
263
|
+
| `columns` | `ColumnDef<T>[]` | required | Column definitions |
|
|
264
|
+
| `rows` | `T[]` | required | Data |
|
|
265
|
+
| `getRowId` | `(row, index) => string \| number` | row index | Stable id for each row |
|
|
266
|
+
| `editable` | `boolean` | `false` | Allow editing cells. Columns can override this |
|
|
267
|
+
| `enterMovesDown` | `boolean` | `true` | After Enter saves a cell, edit the cell below |
|
|
268
|
+
| `onCellEdit` | `(event: CellEditEvent<T>) => void` | | Called after a cell edit is saved |
|
|
269
|
+
| `onRowsChange` | `(rows: T[]) => void` | | Called with the full updated array after an edit |
|
|
270
|
+
| `sortable` | `boolean` | `true` | Click headers to sort. Columns can override |
|
|
271
|
+
| `defaultSort` | `SortModel \| null` | `null` | Initial sort. Read once, on mount |
|
|
272
|
+
| `onSortChange` | `(sort: SortModel \| null) => void` | | Called when the sort changes |
|
|
273
|
+
| `searchable` | `boolean` | `true` | Show the global search box |
|
|
274
|
+
| `searchPlaceholder` | `string` | `"Search…"` | Placeholder of the search box |
|
|
275
|
+
| `pagination` | `boolean` | `true` | Split rows into pages |
|
|
276
|
+
| `paginationPosition` | `"bottom" \| "top" \| "both"` | `"bottom"` | Where the row count and page controls go |
|
|
277
|
+
| `pageSize` | `number` | `10` | Initial rows per page. Read once, on mount |
|
|
278
|
+
| `pageSizeOptions` | `number[]` | `[5, 10, 25, 50]` | Choices in the page size menu. The current page size is added if missing |
|
|
279
|
+
| `onRowClick` | `(row, rowIndex) => void` | | Called when a row is clicked |
|
|
280
|
+
| `toolbar` | `ReactNode` | | Extra content on the right of the toolbar |
|
|
281
|
+
| `emptyMessage` | `ReactNode` | `"No rows to show"` | Shown when no rows match |
|
|
282
|
+
| `striped` | `boolean` | `false` | Alternate row shading |
|
|
283
|
+
| `stripeColors` | `[string, string]` | | Two colours for the alternating rows. Implies `striped`. See [striped rows](#striped-rows) |
|
|
284
|
+
| `animateRows` | `boolean` | `true` | Rows slide up and fade in when they render. See [row animation](#row-animation) |
|
|
285
|
+
| `theme` | `"ocean" \| "midnight" \| "sunset" \| "forest" \| string` | | Applies a theme. Nothing changes without it. See [Theming](#4-theming) |
|
|
286
|
+
| `checkboxVariant` | `"button" \| "tick" \| "native"` | `"native"` | Checkbox look for `type: "boolean"` columns. Columns can override it. See [Checkbox columns](#checkbox-columns-type-boolean) |
|
|
287
|
+
| `maxHeight` | `number \| string` | | Makes the body scroll, with a sticky header. Example: `400` or `"60vh"` |
|
|
288
|
+
| `className` | `string` | | Classes for the root element (Tailwind works). The grid is `width: 100%` of its container by default |
|
|
289
|
+
| `style` | `CSSProperties` | | Inline styles for the root element |
|
|
290
|
+
| `classNames` | `Partial<Record<GridSlot, string>>` | | Classes for individual parts. See [6.10](#610-styling) |
|
|
291
|
+
| `styles` | `Partial<Record<GridSlot, CSSProperties>>` | | Inline styles for individual parts |
|
|
292
|
+
| `rowClassName` | `string \| (row, rowIndex) => string` | | Classes for rows |
|
|
293
|
+
| `rowStyle` | `CSSProperties \| (row, rowIndex) => CSSProperties` | | Inline styles for rows |
|
|
294
|
+
|
|
295
|
+
### 6.3 Column definitions
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const columns: ColumnDef<Employee>[] = [
|
|
299
|
+
{ field: "name", headerName: "Name" },
|
|
300
|
+
{ field: "salary", headerName: "Salary", type: "number", align: "right" },
|
|
301
|
+
];
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
| Option | Type | Description |
|
|
305
|
+
| --- | --- | --- |
|
|
306
|
+
| `field` | `FieldPath<T>` | **Required.** Key of the row object this column reads and writes. Use dot notation for nested objects: `"contact.phone"` |
|
|
307
|
+
| `headerName` | `string` | Header text. Defaults to the field name, except in `type: "boolean"` columns, where it defaults to an empty string |
|
|
308
|
+
| `type` | `"text" \| "number" \| "boolean"` | `"number"` columns edit and parse as numbers. `"boolean"` columns show a checkbox. Default `"text"` |
|
|
309
|
+
| `width` | `number \| string` | Header cell width |
|
|
310
|
+
| `minWidth` | `number \| string` | Header cell minimum width |
|
|
311
|
+
| `align` | `"left" \| "center" \| "right"` | Text alignment for the header, cells and editor |
|
|
312
|
+
| `sortable` | `boolean` | Overrides the grid-level `sortable` |
|
|
313
|
+
| `filterable` | `boolean` | Show a filter indicator in this column's header (default `false`). Clicking it opens the filter popup. See [6.5](#65-searching-and-filtering) |
|
|
314
|
+
| `filterPlaceholder` | `string` | Placeholder of the default filter text box |
|
|
315
|
+
| `filterIndicator` | `ComponentType<FilterIndicatorProps<T>>` | Your own component drawn instead of the default dot |
|
|
316
|
+
| `filterIndicatorClassName` | `string` | Classes for this column's indicator button |
|
|
317
|
+
| `filterIndicatorStyle` | `CSSProperties` | Inline styles for this column's indicator button |
|
|
318
|
+
| `filterPopupClassName` | `string` | Classes for this column's filter popup |
|
|
319
|
+
| `filterPopupStyle` | `CSSProperties` | Inline styles for this column's filter popup |
|
|
320
|
+
| `editable` | `boolean \| (params: CellEditableParams<T>) => boolean` | Overrides the grid-level `editable`. A function decides per cell |
|
|
321
|
+
| `valueFormatter` | `(value, row) => string` | Text used for display, search, filtering. Does not change the stored value |
|
|
322
|
+
| `valueParser` | `(text, row) => unknown` | Turns typed text into the value to store |
|
|
323
|
+
| `validate` | `(value, row) => boolean` | Return `false` to reject an edit |
|
|
324
|
+
| `comparator` | `(a, b, rowA, rowB) => number` | Custom sort order |
|
|
325
|
+
| `cellRenderer` | `ComponentType<CellParams<T>>` | Component that renders the cell |
|
|
326
|
+
| `cellEditor` | `ComponentType<CellEditorProps<T>>` | Component used instead of the text box when editing |
|
|
327
|
+
| `headerRenderer` | `ComponentType<HeaderRendererProps<T>>` | Component that renders the header label |
|
|
328
|
+
| `filterRenderer` | `ComponentType<FilterRendererProps<T>>` | Component that replaces the filter text box inside the popup |
|
|
329
|
+
| `className` | `string` | Classes for the whole column: its header cell and every body cell (Tailwind works) |
|
|
330
|
+
| `style` | `CSSProperties` | Inline styles for the whole column: its header cell and every body cell |
|
|
331
|
+
| `headerClassName` | `string` | Classes for this column's header cell only |
|
|
332
|
+
| `headerStyle` | `CSSProperties` | Inline styles for this column's header cell |
|
|
333
|
+
| `cellClassName` | `string \| (params) => string` | Classes for this column's cells |
|
|
334
|
+
| `cellStyle` | `CSSProperties \| (params) => CSSProperties` | Inline styles for this column's cells |
|
|
335
|
+
| `checkbox` | `ColumnCheckboxOptions` | Look of the checkbox in a `type: "boolean"` column: `variant`, `indicator`, `className`, `style`, `classNames`, `styles` |
|
|
336
|
+
| `headerCheckbox` | `boolean` | On a `type: "boolean"` column, draws a select-all checkbox in the header, labelled with the header name (no label when it is empty). See [Select-all header checkbox](#select-all-header-checkbox) |
|
|
337
|
+
|
|
338
|
+
### 6.4 Sorting
|
|
339
|
+
|
|
340
|
+
- Click a header to sort ascending, click again for descending, click a third time to clear the sort.
|
|
341
|
+
- Only one column is sorted at a time.
|
|
342
|
+
- Numbers sort numerically, dates by time, booleans false before true. Everything else sorts as text with natural number ordering (`"item 2"` before `"item 10"`), ignoring case.
|
|
343
|
+
- Empty values (`null`, `undefined`, `""`) always sort last, in both directions.
|
|
344
|
+
- Add a `comparator` to sort any other way:
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
{ field: "priority", comparator: (a, b) => order.indexOf(a) - order.indexOf(b) }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
- Set an initial sort with `defaultSort={{ field: "salary", direction: "desc" }}` and react to changes with `onSortChange`.
|
|
351
|
+
- Headers are keyboard accessible (Tab to a header, Enter or Space to sort) and expose `aria-sort`.
|
|
352
|
+
- Turn sorting off for the whole grid with `sortable={false}` or per column with `sortable: false`.
|
|
353
|
+
|
|
354
|
+
### 6.5 Searching and filtering
|
|
355
|
+
|
|
356
|
+
**Global search.** The box above the grid matches rows where any column's displayed text contains the typed text (case-insensitive). "Displayed text" means the result of `valueFormatter` if there is one.
|
|
357
|
+
|
|
358
|
+
**Column filters.** Filters are set per column, in the column definition. Only columns with `filterable: true` get one:
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
const columns: ColumnDef<Person>[] = [
|
|
362
|
+
{ field: "name", filterable: true },
|
|
363
|
+
{ field: "department", filterable: true, filterPlaceholder: "Which team?" },
|
|
364
|
+
{ field: "salary" }, // no filter
|
|
365
|
+
];
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
- A filterable column shows a small **indicator** in its header, next to the label. By default it is a dot. It is dim while no filter is set, and lit (with a glow) while one is active.
|
|
369
|
+
- **Clicking the indicator** opens a popup right below it with a text box. The popup is not there until you click. It closes on Escape, on a click outside, or on Enter in the text box. Clicking the indicator does not sort the column.
|
|
370
|
+
- The popup shows a **Clear** button while a filter is set.
|
|
371
|
+
- Filters work as a case-insensitive "contains" on the displayed text, and combine with each other and with the global search.
|
|
372
|
+
- The popup is drawn in `document.body` and positioned next to the indicator, so a scrolling grid never clips it. It follows scrolling and resizing, and stays inside the window.
|
|
373
|
+
- There is no grid-level filter prop: a column without `filterable: true` has no filter, and a filter value is ignored if its column stops being filterable.
|
|
374
|
+
|
|
375
|
+
**Styling each indicator separately.** Every column has its own options:
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
{
|
|
379
|
+
field: "status",
|
|
380
|
+
filterable: true,
|
|
381
|
+
filterIndicatorClassName: "text-amber-300", // Tailwind on this indicator only
|
|
382
|
+
filterIndicatorStyle: { color: "#f59e0b" }, // or inline styles
|
|
383
|
+
filterPopupStyle: { borderColor: "#f59e0b" }, // style this column's popup
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The dot takes its colour from the button's text colour, so `color` (or `text-*`) restyles it. The colour while a filter is active comes from `--rk-filter-active`. To style all indicators or popups at once, use `classNames.filterIndicator`, `classNames.filterPopup`, `classNames.filterInput` (or the matching `styles`).
|
|
388
|
+
|
|
389
|
+
**A custom indicator component.** Replace the dot with any component. It receives `{ column, active, open }`:
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
function FunnelIndicator({ active }: FilterIndicatorProps<Employee>) {
|
|
393
|
+
return (
|
|
394
|
+
<svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true">
|
|
395
|
+
<path d="M1.5 2h13l-5 6v5l-3 1.5V8z" fill={active ? "currentColor" : "none"} stroke="currentColor" strokeWidth="1.5" />
|
|
396
|
+
</svg>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
{ field: "department", filterable: true, filterIndicator: FunnelIndicator }
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The component is drawn inside the indicator `<button>`, so do not put another button in it.
|
|
404
|
+
|
|
405
|
+
**A custom filter control.** `filterRenderer` replaces the text box inside the popup (see [filterRenderer](#filterrenderer-custom-filter-control)).
|
|
406
|
+
|
|
407
|
+
Notes:
|
|
408
|
+
|
|
409
|
+
- Searching and filtering use the displayed text, so a salary shown as `$52,000` is found by `52,000` and not by `52000` unless your formatter says so.
|
|
410
|
+
- Filtering is a substring match. Filtering a status column for `active` also matches `inactive`. For exact choices, use a `filterRenderer` and a distinct display text, or format the values so they don't overlap.
|
|
411
|
+
- Changing the search, a filter, the sort or the page size sends the grid back to page 1.
|
|
412
|
+
- Turn off the search box with `searchable={false}`. Leave out `filterable` (or set it to `false`) to give a column no filter.
|
|
413
|
+
|
|
414
|
+
### 6.6 Pagination
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
<DataGrid pageSize={20} pageSizeOptions={[10, 20, 50, 100]} paginationPosition="both" />
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The pagination bar shows the row range ("11–20 of 200"), a rows-per-page menu, first/previous/next/last buttons and "Page x of y".
|
|
421
|
+
|
|
422
|
+
| `paginationPosition` | Result |
|
|
423
|
+
| --- | --- |
|
|
424
|
+
| `"bottom"` | Bar under the table (default) |
|
|
425
|
+
| `"top"` | Bar above the table, below the search box |
|
|
426
|
+
| `"both"` | Both bars, kept in sync |
|
|
427
|
+
|
|
428
|
+
With `pagination={false}` every row is rendered and only the row count shows, at the bottom. Combine it with `maxHeight` to get a scrolling grid with a sticky header:
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
<DataGrid pagination={false} maxHeight={400} />
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`pageSize` is only read when the grid first mounts. The user's choice in the menu is then kept inside the grid.
|
|
435
|
+
|
|
436
|
+
### 6.7 Row click, toolbar and empty state
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
<DataGrid
|
|
440
|
+
onRowClick={(row, rowIndex) => navigate(`/people/${row.id}`)}
|
|
441
|
+
toolbar={<button onClick={exportCsv}>Export</button>}
|
|
442
|
+
emptyMessage="Nothing matches your search"
|
|
443
|
+
striped
|
|
444
|
+
/>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### 6.8 Editing
|
|
448
|
+
|
|
449
|
+
Turn editing on with `editable` on the grid, then lock or unlock columns:
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
<DataGrid editable columns={[
|
|
453
|
+
{ field: "id", editable: false }, // locked column
|
|
454
|
+
{ field: "name" }, // editable (grid default)
|
|
455
|
+
{ field: "status", editable: ({ row }) => row.department !== "HR" } // decided per cell
|
|
456
|
+
]} />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
A column can also be the only editable one: leave the grid's `editable` off and set `editable: true` on that column.
|
|
460
|
+
|
|
461
|
+
**Starting an edit**
|
|
462
|
+
|
|
463
|
+
| Action | Result |
|
|
464
|
+
| --- | --- |
|
|
465
|
+
| Double-click a cell | Opens the editor, cursor at the end |
|
|
466
|
+
| Select a cell (click or Tab) and press **Enter** or **F2** | Opens the editor, cursor at the end |
|
|
467
|
+
| Select a cell and type a character | Opens the editor with that character replacing the old value |
|
|
468
|
+
|
|
469
|
+
Typing to start does not apply to columns with a custom `cellEditor`. Those open with double-click, Enter or F2.
|
|
470
|
+
|
|
471
|
+
**While editing**
|
|
472
|
+
|
|
473
|
+
| Key | Result |
|
|
474
|
+
| --- | --- |
|
|
475
|
+
| **Enter** | Save, then edit the cell below (the text is pre-selected so typing replaces it) |
|
|
476
|
+
| **Shift+Enter** | Save, then edit the cell above |
|
|
477
|
+
| **Tab** / **Shift+Tab** | Save, then edit the next / previous editable cell, wrapping across rows |
|
|
478
|
+
| **Esc** | Cancel without saving |
|
|
479
|
+
| Click elsewhere | Save |
|
|
480
|
+
|
|
481
|
+
Details:
|
|
482
|
+
|
|
483
|
+
- When there is no editable cell in that direction (for example Enter on the last row of the page), the value is saved and the editor closes. Focus returns to the cell.
|
|
484
|
+
- Set `enterMovesDown={false}` to make Enter save and stay put.
|
|
485
|
+
- Cells locked by `editable` are skipped when moving.
|
|
486
|
+
- If sorting, filtering or paging changes the position of rows while you move between cells, editing follows the row, not the screen position.
|
|
487
|
+
|
|
488
|
+
**How the typed text becomes a value**
|
|
489
|
+
|
|
490
|
+
1. If the column has a `valueParser`, its return value is stored.
|
|
491
|
+
2. Otherwise, in a `type: "number"` column, an empty text stores `null`, a valid number stores that number, and anything else is rejected.
|
|
492
|
+
3. Otherwise the text is stored as a string.
|
|
493
|
+
|
|
494
|
+
**Validation.** After parsing, `validate(value, row)` is called. If it returns `false` (or a number could not be parsed), the value is not saved, the editor stays open, and it gets the `rk-grid__editor--invalid` class and `aria-invalid="true"`. Pressing Esc or clicking away discards the invalid text.
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
{ field: "email", validate: (v) => /^\S+@\S+\.\S+$/.test(String(v)) }
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
A value that equals the old value is treated as "no change" and fires no events.
|
|
501
|
+
|
|
502
|
+
**Events.** When a value is saved the grid calls, in this order:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
onCellEdit({ rowId, rowIndex, field, oldValue, newValue, row }) // row is the updated row
|
|
506
|
+
onRowsChange(nextRows) // the whole updated array
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**Keeping your data in sync.** The simplest pattern is state plus `onRowsChange`:
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
const [rows, setRows] = useState(initialRows);
|
|
513
|
+
<DataGrid rows={rows} onRowsChange={setRows} editable />
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Important: the grid copies `rows` into its own state and re-syncs whenever the `rows` prop *changes identity*. If your parent re-creates the array on every render (for example `rows={data.map(...)}` written inline), the grid resets to it each time and edits are lost. Keep `rows` in state or memoize it with `useMemo`.
|
|
517
|
+
|
|
518
|
+
**Saving to a server:**
|
|
519
|
+
|
|
520
|
+
```tsx
|
|
521
|
+
<DataGrid
|
|
522
|
+
rows={rows}
|
|
523
|
+
onRowsChange={setRows}
|
|
524
|
+
onCellEdit={async (e) => {
|
|
525
|
+
await fetch(`/api/people/${e.rowId}`, {
|
|
526
|
+
method: "PATCH",
|
|
527
|
+
headers: { "Content-Type": "application/json" },
|
|
528
|
+
body: JSON.stringify({ [e.field]: e.newValue }),
|
|
529
|
+
});
|
|
530
|
+
}}
|
|
531
|
+
/>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 6.9 Custom components per column
|
|
535
|
+
|
|
536
|
+
Four hooks let you replace parts of a column with your own React components. They are rendered as real components, so they can use hooks and state.
|
|
537
|
+
|
|
538
|
+
> **Define these components at module level**, not inline inside your render function. An inline component gets a new identity on every render, so React unmounts and remounts it each time (inputs lose focus, state resets).
|
|
539
|
+
|
|
540
|
+
#### `cellRenderer`: custom cell content
|
|
541
|
+
|
|
542
|
+
Receives `CellParams<T>`:
|
|
543
|
+
|
|
544
|
+
| Prop | Description |
|
|
545
|
+
| --- | --- |
|
|
546
|
+
| `value` | The raw value from the row |
|
|
547
|
+
| `formattedValue` | The value as text, after `valueFormatter` |
|
|
548
|
+
| `row` | The whole row |
|
|
549
|
+
| `rowIndex` | Index of the row in `rows` |
|
|
550
|
+
| `field` | The column's field |
|
|
551
|
+
| `column` | The column definition |
|
|
552
|
+
| `editable` | `true` if the cell may be changed (the column's `editable`, or the grid's) |
|
|
553
|
+
| `updateValue(value)` | Saves a new value for this cell right away, without opening the editor. It runs `validate` and fires `onCellEdit` / `onRowsChange`. Returns `false` if the value was rejected or the cell is not editable |
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
function StatusCell({ value }: CellParams<Employee>) {
|
|
557
|
+
return <span className={value === "active" ? "text-green-700" : "text-gray-500"}>{String(value)}</span>;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
{ field: "status", cellRenderer: StatusCell }
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Searching, filtering and sorting still use the underlying value and `valueFormatter`, not what your component draws.
|
|
564
|
+
|
|
565
|
+
#### `cellEditor`: custom editor
|
|
566
|
+
|
|
567
|
+
Replaces the text box when the cell is being edited. Receives `CellEditorProps<T>`:
|
|
568
|
+
|
|
569
|
+
| Prop | Description |
|
|
570
|
+
| --- | --- |
|
|
571
|
+
| `value` | The current draft value. Starts as the cell's raw value |
|
|
572
|
+
| `row`, `rowIndex`, `field`, `column` | Same as in `CellParams` |
|
|
573
|
+
| `onChange(value)` | Update the draft without saving |
|
|
574
|
+
| `onCommit(value?, move?)` | Save and close. Pass a value to save it, or nothing to save the latest draft. `move` can be `"down" \| "up" \| "left" \| "right" \| "none"` (default `"none"`) and also starts editing the neighbouring cell |
|
|
575
|
+
| `onCancel()` | Close without saving |
|
|
576
|
+
|
|
577
|
+
Rules your editor should follow:
|
|
578
|
+
|
|
579
|
+
- Focus something when it mounts (`autoFocus` or a ref) so the keyboard works straight away.
|
|
580
|
+
- Esc is handled by the grid. Clicking or tabbing out of the editor saves the current draft.
|
|
581
|
+
- A value passed to `onCommit` is stored as is. `valueParser` and the `type: "number"` conversion are not applied, but `validate` still is.
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
function StatusEditor({ value, onCommit }: CellEditorProps<Employee>) {
|
|
585
|
+
return (
|
|
586
|
+
<select autoFocus value={String(value)} onChange={(e) => onCommit(e.target.value)}>
|
|
587
|
+
<option>active</option>
|
|
588
|
+
<option>pending</option>
|
|
589
|
+
<option>inactive</option>
|
|
590
|
+
</select>
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
{ field: "status", cellEditor: StatusEditor }
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
An editor with its own keyboard handling, saving and moving down on Enter:
|
|
598
|
+
|
|
599
|
+
```tsx
|
|
600
|
+
function SliderEditor({ value, onChange, onCommit }: CellEditorProps<Employee>) {
|
|
601
|
+
return (
|
|
602
|
+
<input
|
|
603
|
+
autoFocus
|
|
604
|
+
type="range"
|
|
605
|
+
value={Number(value) || 0}
|
|
606
|
+
onChange={(e) => onChange(Number(e.target.value))}
|
|
607
|
+
onKeyDown={(e) => e.key === "Enter" && onCommit(undefined, "down")}
|
|
608
|
+
/>
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### `headerRenderer`: custom header label
|
|
614
|
+
|
|
615
|
+
Receives `{ column, sortDirection }`. The filter indicator (if `filterable`) and the sort arrow are still added next to it. The whole header cell is clickable for sorting, so call `event.stopPropagation()` on any interactive element you put inside.
|
|
616
|
+
|
|
617
|
+
```tsx
|
|
618
|
+
function SalaryHeader({ column }: HeaderRendererProps<Employee>) {
|
|
619
|
+
return <span>{column.headerName} <small>USD</small></span>;
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
#### `filterRenderer`: custom filter control
|
|
624
|
+
|
|
625
|
+
Shown inside the filter popup instead of the text box (the column still needs `filterable: true`). Receives `{ column, value, onChange }`, where `value` is the filter text. Whatever string you pass to `onChange` is matched with "contains" against the displayed text.
|
|
626
|
+
|
|
627
|
+
```tsx
|
|
628
|
+
function DepartmentFilter({ value, onChange }: FilterRendererProps<Employee>) {
|
|
629
|
+
return (
|
|
630
|
+
<select value={value} onChange={(e) => onChange(e.target.value)}>
|
|
631
|
+
<option value="">All</option>
|
|
632
|
+
<option>Engineering</option>
|
|
633
|
+
<option>Design</option>
|
|
634
|
+
</select>
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### Checkbox columns (`type: "boolean"`)
|
|
640
|
+
|
|
641
|
+
Set `type: "boolean"` on a column and each cell shows a [Checkbox](#checkbox). One click saves the new `true` / `false` value.
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
const columns: ColumnDef<Task>[] = [
|
|
645
|
+
{ field: "title", headerName: "Task" },
|
|
646
|
+
{ field: "done", type: "boolean", align: "center", checkbox: { variant: "button" } }, // no headerName: the header is empty
|
|
647
|
+
{ field: "starred", headerName: "Starred", type: "boolean", checkbox: { variant: "button", indicator: "★" } }, // custom character
|
|
648
|
+
{ field: "urgent", headerName: "Urgent", type: "boolean", align: "center", checkbox: { variant: "tick" } },
|
|
649
|
+
{ field: "archived", headerName: "Archived", type: "boolean", align: "center" }, // grid default: native
|
|
650
|
+
{ field: "approved", headerName: "Approved", type: "boolean", editable: false }, // shown disabled
|
|
651
|
+
];
|
|
652
|
+
|
|
653
|
+
<DataGrid columns={columns} rows={rows} editable onRowsChange={setRows} checkboxVariant="native" />
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
How it behaves:
|
|
657
|
+
|
|
658
|
+
- **One click saves.** The change goes through the same path as a typed edit: `validate` runs, then `onCellEdit` and `onRowsChange` fire. Space toggles a focused checkbox, and Tab reaches them.
|
|
659
|
+
- **Who can click.** A checkbox is active only where the cell is editable: grid `editable`, overridden per column by `editable: true | false` or a function. Otherwise it is shown disabled.
|
|
660
|
+
- **No text editor.** Boolean cells never open the text editor, and Enter and Tab navigation skip them. A click on the checkbox does not trigger `onRowClick`.
|
|
661
|
+
- **Sorting, search and filters.** Unchecked sorts before checked. Search and filters match the text `Yes` / `No`, or whatever your `valueFormatter` returns. A missing value (`null`, `undefined`) shows unchecked.
|
|
662
|
+
- **Header label.** The header of a boolean column is empty unless you set `headerName`. Screen readers still get the field name.
|
|
663
|
+
- **Custom character.** `checkbox: { indicator: "★" }` draws that character instead of the default fill or tick when checked. Colour it with `--rk-checkbox-color` (button variant) or `--rk-checkbox-tick` (tick variant), for example through `checkbox.styles.root`.
|
|
664
|
+
- **Defaults.** 1px border, no rounded corners. See [Checkbox](#5-checkbox).
|
|
665
|
+
- **A column with its own `cellEditor`** is treated as a normal editable column instead.
|
|
666
|
+
|
|
667
|
+
**Choosing and styling the checkbox**
|
|
668
|
+
|
|
669
|
+
| Where | How |
|
|
670
|
+
| --- | --- |
|
|
671
|
+
| Whole grid | `checkboxVariant="tick"` |
|
|
672
|
+
| One column | `checkbox: { variant: "tick" }` |
|
|
673
|
+
| Whole grid, classes or inline styles | `classNames` / `styles` slots `checkbox`, `checkboxControl`, `checkboxIndicator` |
|
|
674
|
+
| One column, classes or inline styles | `checkbox: { className, style, classNames, styles, indicator }` (same parts as the standalone `Checkbox`) |
|
|
675
|
+
| Colours and size | CSS variables such as `--rk-checkbox-color`, set on the grid's `className` / `style`, or per column in `checkbox.styles.root` |
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
<DataGrid
|
|
679
|
+
checkboxVariant="tick"
|
|
680
|
+
classNames={{
|
|
681
|
+
checkboxControl: "rounded-full border-emerald-500 aria-checked:bg-emerald-500",
|
|
682
|
+
checkboxIndicator: "text-white",
|
|
683
|
+
}}
|
|
684
|
+
...
|
|
685
|
+
/>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
When both are given, the column's options are merged after the grid-level ones, so the column wins.
|
|
689
|
+
|
|
690
|
+
**Fully custom checkbox in a cell.** Any `cellRenderer` can toggle the value itself through `updateValue`. This example is the "button with a span inside" written by hand:
|
|
691
|
+
|
|
692
|
+
```tsx
|
|
693
|
+
function PinCell({ value, editable, updateValue }: CellParams<Task>) {
|
|
694
|
+
return (
|
|
695
|
+
<button
|
|
696
|
+
type="button"
|
|
697
|
+
aria-pressed={!!value}
|
|
698
|
+
disabled={!editable}
|
|
699
|
+
onClick={(e) => {
|
|
700
|
+
e.stopPropagation(); // keep onRowClick from also firing
|
|
701
|
+
updateValue(!value);
|
|
702
|
+
}}
|
|
703
|
+
>
|
|
704
|
+
<span style={{ visibility: value ? "visible" : "hidden" }}>✓</span>
|
|
705
|
+
</button>
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
{ field: "pinned", cellRenderer: PinCell }
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
#### Select-all header checkbox
|
|
713
|
+
|
|
714
|
+
Add `headerCheckbox: true` to a boolean column. The header label becomes a checkbox (the header name is its label, or no label if the name is empty) that checks or clears the whole column.
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
{ field: "done", headerName: "Done", type: "boolean", align: "center", headerCheckbox: true, checkbox: { variant: "tick" } }
|
|
718
|
+
{ field: "done", type: "boolean", headerCheckbox: true } // just the checkbox, no text
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
- **Click:** if every target row is checked, it unchecks them all. Otherwise (none or only some are checked) it checks them all.
|
|
722
|
+
- **Which rows:** every row that matches the current search and column filters, on all pages, and whose cell is editable. Rows the filters hide are left alone.
|
|
723
|
+
- **State:** checked when all target rows are checked, a dash (mixed) when only some are, unchecked when none are. It updates as you toggle individual rows.
|
|
724
|
+
- **Events:** `onCellEdit` fires once for each row that changed, and `onRowsChange` fires once with the whole updated array. Rows already at the target value, and rows where `validate` rejects the value, are skipped.
|
|
725
|
+
- **Locked columns:** if no cell in the column is editable, the header checkbox is disabled but still shows the real state.
|
|
726
|
+
- **Look:** it uses the same variant and styling as the column's cell checkboxes (`checkbox` options, `checkboxVariant`, and the `checkbox`, `checkboxControl` and `checkboxIndicator` slots).
|
|
727
|
+
- **Sorting:** clicking the checkbox or its label does not sort. Click the sort arrow beside it (or the empty part of the header cell) to sort.
|
|
728
|
+
- **Custom header:** if the column also has a `headerRenderer`, that is used and `headerCheckbox` is ignored. `headerCheckbox` does nothing on columns that are not `type: "boolean"`.
|
|
729
|
+
|
|
730
|
+
### 6.10 Styling
|
|
731
|
+
|
|
732
|
+
#### Whole grid
|
|
733
|
+
|
|
734
|
+
```tsx
|
|
735
|
+
<DataGrid className="rounded-xl shadow-lg" style={{ maxWidth: 900 }} ... />
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
#### Parts of the grid
|
|
739
|
+
|
|
740
|
+
`classNames` and `styles` take the same keys. Each key is a part ("slot") of the grid:
|
|
741
|
+
|
|
742
|
+
| Slot | Element |
|
|
743
|
+
| --- | --- |
|
|
744
|
+
| `root` | Outer container |
|
|
745
|
+
| `toolbar` | Bar holding the search box and your `toolbar` content |
|
|
746
|
+
| `search` | Global search input |
|
|
747
|
+
| `scroll` | Scrolling wrapper around the table |
|
|
748
|
+
| `table` | The `<table>` |
|
|
749
|
+
| `thead` | The `<thead>` |
|
|
750
|
+
| `headerRow` | The row of column headers |
|
|
751
|
+
| `headerCell` | Each column header (`<th>`) |
|
|
752
|
+
| `filterIndicator` | Each column's filter indicator button |
|
|
753
|
+
| `filterPopup` | The filter popup (drawn in `document.body`) |
|
|
754
|
+
| `filterInput` | The default filter text box in the popup |
|
|
755
|
+
| `tbody` | The `<tbody>` |
|
|
756
|
+
| `row` | Each body row |
|
|
757
|
+
| `cell` | Each body cell (`<td>`) |
|
|
758
|
+
| `editor` | The default text editor input |
|
|
759
|
+
| `empty` | The "no rows" cell |
|
|
760
|
+
| `footer` | The pagination bar (both top and bottom) |
|
|
761
|
+
| `rowCount` | The "1–10 of 200" text |
|
|
762
|
+
| `pageInfo` | The "Page 1 of 20" text |
|
|
763
|
+
| `pageSize` | The rows-per-page `<select>` |
|
|
764
|
+
| `pagination` | Wrapper around the page-size menu and buttons |
|
|
765
|
+
| `pageButton` | Each first/previous/next/last button |
|
|
766
|
+
| `checkbox` | Root of each checkbox in a `type: "boolean"` column |
|
|
767
|
+
| `checkboxControl` | The checkbox's button or input |
|
|
768
|
+
| `checkboxIndicator` | The span inside the `"button"` / `"tick"` checkboxes |
|
|
769
|
+
|
|
770
|
+
#### Tailwind
|
|
771
|
+
|
|
772
|
+
```tsx
|
|
773
|
+
<DataGrid
|
|
774
|
+
className="rounded-2xl border-0 shadow-xl ring-1 ring-slate-200"
|
|
775
|
+
classNames={{
|
|
776
|
+
toolbar: "bg-slate-50 px-4 py-3",
|
|
777
|
+
search: "rounded-full border-slate-300 px-4",
|
|
778
|
+
headerCell: "bg-slate-900 text-xs uppercase tracking-wider text-slate-100",
|
|
779
|
+
row: "hover:bg-indigo-50",
|
|
780
|
+
footer: "bg-slate-50 text-slate-500",
|
|
781
|
+
pageButton: "rounded-full hover:bg-indigo-100",
|
|
782
|
+
}}
|
|
783
|
+
rowClassName={(row) => (row.status === "inactive" ? "opacity-50" : "")}
|
|
784
|
+
/>
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
You do not need to add the library to Tailwind's `content` setting, because the class names live in your own code.
|
|
788
|
+
|
|
789
|
+
The grid's built-in CSS is written with `:where()`, which gives it zero specificity. That means any class you add, Tailwind utilities included, overrides it without `!important`.
|
|
790
|
+
|
|
791
|
+
#### Inline styles
|
|
792
|
+
|
|
793
|
+
```tsx
|
|
794
|
+
<DataGrid
|
|
795
|
+
style={{ border: "2px solid #111", borderRadius: 0 }}
|
|
796
|
+
styles={{
|
|
797
|
+
headerCell: { background: "#111", color: "#ffe66d" },
|
|
798
|
+
footer: { background: "#ffe66d" },
|
|
799
|
+
}}
|
|
800
|
+
rowStyle={(row) => (row.status === "pending" ? { background: "#fff7cc" } : {})}
|
|
801
|
+
/>
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
When both are given, `style` wins over `styles.root`.
|
|
805
|
+
|
|
806
|
+
#### Per column and per cell
|
|
807
|
+
|
|
808
|
+
`className` and `style` style a whole column (its header and every cell). The `header*` and `cell*` options target only one part:
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
{
|
|
812
|
+
field: "salary",
|
|
813
|
+
className: "font-mono text-slate-700", // header and every cell (Tailwind)
|
|
814
|
+
style: { letterSpacing: 0.3 }, // header and every cell (inline)
|
|
815
|
+
headerClassName: "text-right",
|
|
816
|
+
headerStyle: { minWidth: 120 },
|
|
817
|
+
cellClassName: ({ value }) => (Number(value) > 100000 ? "font-bold text-green-600" : ""),
|
|
818
|
+
cellStyle: ({ value }) => (value === null ? { color: "#9ca3af" } : {}),
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
#### Striped rows
|
|
823
|
+
|
|
824
|
+
```tsx
|
|
825
|
+
<DataGrid striped /> // default shading
|
|
826
|
+
<DataGrid stripeColors={["#fef3c7", "#e0f2fe"]} /> // your two colours (implies striped)
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
Rows alternate between the two colours, starting with the first. `stripeColors` sets `--rk-stripe-a` and `--rk-stripe-b` on the grid. You can also set those variables in your own CSS or theme, and turn on `striped`. Hovering a row still highlights it (`--rk-row-hover`).
|
|
830
|
+
|
|
831
|
+
#### Row animation
|
|
832
|
+
|
|
833
|
+
When rows render (the first load, a new page, a sort, a search or a filter), they slide up and fade in, one after another. It is on by default.
|
|
834
|
+
|
|
835
|
+
```tsx
|
|
836
|
+
<DataGrid animateRows={false} /> // turn it off
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Tune it with `--rk-row-duration` (default `0.38s`) and `--rk-row-stagger` (default `28ms` per row, up to 24 rows). Changing page or sort also scrolls the grid's own scroll area smoothly back to the top. Visitors who ask their system for reduced motion get no animation (`prefers-reduced-motion`).
|
|
840
|
+
|
|
841
|
+
#### Width
|
|
842
|
+
|
|
843
|
+
The grid is `width: 100%` of its container by default (it never overflows it: `max-width: 100%; min-width: 0`). To make it narrower, give it a width through `className` (`max-w-3xl`) or `style` (`{ maxWidth: 900 }`).
|
|
844
|
+
|
|
845
|
+
#### Plain CSS class reference
|
|
846
|
+
|
|
847
|
+
If you prefer writing CSS, these class names are stable.
|
|
848
|
+
|
|
849
|
+
| Class | Element |
|
|
850
|
+
| --- | --- |
|
|
851
|
+
| `.rk-grid` | Root |
|
|
852
|
+
| `.rk-grid__toolbar`, `.rk-grid__search` | Toolbar and search box |
|
|
853
|
+
| `.rk-grid__scroll`, `.rk-grid__table`, `.rk-grid__thead`, `.rk-grid__tbody` | Table structure |
|
|
854
|
+
| `.rk-grid__tr` | Every row |
|
|
855
|
+
| `.rk-grid__th`, `.rk-grid__th--sortable`, `.rk-grid__th-content`, `.rk-grid__sort` | Header cells and sort arrow |
|
|
856
|
+
| `.rk-grid__filter-btn`, `.rk-grid__filter-btn--active`, `.rk-grid__filter-btn--open`, `.rk-grid__filter-dot` | Filter indicator button and its default dot |
|
|
857
|
+
| `.rk-grid-popup`, `.rk-grid__filter-input`, `.rk-grid__filter-clear` | Filter popup (in `document.body`), its text box and Clear button |
|
|
858
|
+
| `.rk-grid__row`, `.rk-grid__row--stripe-a`, `.rk-grid__row--stripe-b`, `.rk-grid__row--enter`, `.rk-grid__row--clickable` | Body rows |
|
|
859
|
+
| `.rk-grid__td`, `.rk-grid__td--editable`, `.rk-grid__td--editing` | Body cells |
|
|
860
|
+
| `.rk-grid__editor`, `.rk-grid__editor--invalid`, `.rk-grid__custom-editor` | Editors |
|
|
861
|
+
| `.rk-grid__empty` | Empty state |
|
|
862
|
+
| `.rk-grid__checkbox-cell` | Wrapper around a checkbox in a boolean column (the checkbox's own classes start with `.rk-checkbox`, see [Checkbox](#checkbox)) |
|
|
863
|
+
| `.rk-grid__footer`, `.rk-grid__footer--top` | Pagination bar (`data-position="top"` or `"bottom"`) |
|
|
864
|
+
| `.rk-grid__count`, `.rk-grid__pagination`, `.rk-grid__page-size-label`, `.rk-grid__page-size`, `.rk-grid__page-btn`, `.rk-grid__page-info` | Pagination parts |
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
### 6.11 Nested fields
|
|
868
|
+
|
|
869
|
+
A column's `field` can point into nested objects with dot notation:
|
|
870
|
+
|
|
871
|
+
```tsx
|
|
872
|
+
interface Person {
|
|
873
|
+
id: number;
|
|
874
|
+
name: string;
|
|
875
|
+
contact: { email: string; phone: string };
|
|
876
|
+
address: { city: string };
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const columns: ColumnDef<Person>[] = [
|
|
880
|
+
{ field: "name" },
|
|
881
|
+
{ field: "contact.email", headerName: "Email", filterable: true },
|
|
882
|
+
{ field: "contact.phone", headerName: "Phone" },
|
|
883
|
+
{ field: "address.city", headerName: "City" },
|
|
884
|
+
];
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
- **TypeScript checks the path.** `field` is typed `FieldPath<Person>`: `"contact.phone"` is accepted, `"contact.fax"` is a type error. It follows up to 5 levels deep, and arrays and dates are treated as plain values.
|
|
888
|
+
- **Everything uses the path:** display, global search, filters, sorting, editing, `valueFormatter`, `cellRenderer` (`value`, `field`), and boolean columns and select-all headers (`"prefs.newsletter"`).
|
|
889
|
+
- **Editing never mutates your data.** Saving `contact.phone` copies `row` and `row.contact` and replaces the value, so the original objects are left alone. A missing level on the way is created. `onCellEdit` reports `field: "contact.phone"` and the updated row.
|
|
890
|
+
- **A missing level reads as empty.** If `row.contact` is `undefined`, the cell is blank and sorts last. No error is thrown.
|
|
891
|
+
- A key that really contains a dot (`{ "a.b": 1 }`) is read as that literal key first.
|
|
892
|
+
- `__proto__`, `prototype` and `constructor` are never read or written through a path.
|
|
893
|
+
- `getRowId`, `onRowClick` and other callbacks work as before.
|
|
894
|
+
|
|
895
|
+
### 6.12 Keyboard reference
|
|
896
|
+
|
|
897
|
+
| Where | Key | Action |
|
|
898
|
+
| --- | --- | --- |
|
|
899
|
+
| Header | Enter / Space | Sort by this column |
|
|
900
|
+
| Selected cell | Enter / F2 | Start editing |
|
|
901
|
+
| Selected cell | Any character | Start editing, replacing the value |
|
|
902
|
+
| Editor | Enter | Save, edit cell below |
|
|
903
|
+
| Editor | Shift+Enter | Save, edit cell above |
|
|
904
|
+
| Editor | Tab / Shift+Tab | Save, edit next / previous cell |
|
|
905
|
+
| Editor | Esc | Cancel |
|
|
906
|
+
|
|
907
|
+
Arrow-key navigation between cells is not implemented. Use Tab, or the mouse, to select a cell.
|
|
908
|
+
|
|
909
|
+
### 6.13 Accessibility
|
|
910
|
+
|
|
911
|
+
- The table has `role="grid"`. Sortable headers are focusable, expose `aria-sort`, and work with Enter and Space.
|
|
912
|
+
- Editable cells are focusable (`tabindex="0"`).
|
|
913
|
+
- The search box, filter indicators (`aria-haspopup="dialog"`, `aria-expanded`), filter popups (`role="dialog"`), editors, page buttons and page-size menu have `aria-label`s. Checkboxes in columns with an empty header are named after the field.
|
|
914
|
+
- Invalid editors set `aria-invalid="true"`.
|
|
915
|
+
- Custom components you supply are responsible for their own accessibility.
|
|
916
|
+
|
|
917
|
+
### 6.14 TypeScript
|
|
918
|
+
|
|
919
|
+
All types are exported:
|
|
920
|
+
|
|
921
|
+
```ts
|
|
922
|
+
import type {
|
|
923
|
+
ColumnDef,
|
|
924
|
+
DataGridProps,
|
|
925
|
+
CellParams,
|
|
926
|
+
CellEditEvent,
|
|
927
|
+
CellEditorProps,
|
|
928
|
+
HeaderRendererProps,
|
|
929
|
+
FilterRendererProps,
|
|
930
|
+
GridSlot,
|
|
931
|
+
SortModel,
|
|
932
|
+
SortDirection,
|
|
933
|
+
MoveDirection,
|
|
934
|
+
PaginationPosition,
|
|
935
|
+
CellEditableParams,
|
|
936
|
+
ColumnCheckboxOptions,
|
|
937
|
+
FilterIndicatorProps,
|
|
938
|
+
FieldPath,
|
|
939
|
+
GridTheme,
|
|
940
|
+
CheckboxProps,
|
|
941
|
+
CheckboxVariant,
|
|
942
|
+
CheckboxSlot,
|
|
943
|
+
} from "react-gridix";
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
Typing tips:
|
|
947
|
+
|
|
948
|
+
```tsx
|
|
949
|
+
const columns: ColumnDef<Employee>[] = [ ... ]; // field is checked against Employee (dot paths too)
|
|
950
|
+
function Cell({ row }: CellParams<Employee>) { ... } // row is typed as Employee
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
### 6.15 Recipes
|
|
954
|
+
|
|
955
|
+
**Read-only grid**
|
|
956
|
+
|
|
957
|
+
```tsx
|
|
958
|
+
<DataGrid columns={columns} rows={rows} /> // editable defaults to false
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
**Currency column that accepts "$1,200" when typed**
|
|
962
|
+
|
|
963
|
+
```tsx
|
|
964
|
+
{
|
|
965
|
+
field: "salary",
|
|
966
|
+
type: "number",
|
|
967
|
+
align: "right",
|
|
968
|
+
valueFormatter: (v) => (v == null ? "" : money.format(v)),
|
|
969
|
+
valueParser: (text) => Number(text.replace(/[$,]/g, "")),
|
|
970
|
+
validate: (v) => Number.isFinite(v),
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
(The editor opens with the raw number, such as `52000`. The parser only matters if the user types symbols.)
|
|
975
|
+
|
|
976
|
+
**Only one column editable**
|
|
977
|
+
|
|
978
|
+
```tsx
|
|
979
|
+
<DataGrid columns={[{ field: "id" }, { field: "notes", editable: true }]} />
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
**Lock rows after approval**
|
|
983
|
+
|
|
984
|
+
```tsx
|
|
985
|
+
{ field: "amount", editable: ({ row }) => !row.approved }
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**Scrolling grid with sticky header**
|
|
989
|
+
|
|
990
|
+
```tsx
|
|
991
|
+
<DataGrid pagination={false} maxHeight="60vh" />
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**Highlight a row based on its data**
|
|
995
|
+
|
|
996
|
+
```tsx
|
|
997
|
+
<DataGrid rowClassName={(row) => (row.overdue ? "bg-red-50" : "")} />
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
### 6.16 Known limitations
|
|
1001
|
+
|
|
1002
|
+
Not included (yet):
|
|
1003
|
+
|
|
1004
|
+
- Column resizing, reordering, hiding or pinning
|
|
1005
|
+
- Row selection as its own concept (a set of selected rows with `onSelectionChange`). True/false checkbox columns, including a select-all header (`headerCheckbox`), are supported, see [Checkbox columns](#checkbox-columns-type-boolean)
|
|
1006
|
+
- Multi-column sorting
|
|
1007
|
+
- Row virtualization (very large pages render every row, so keep `pageSize` moderate)
|
|
1008
|
+
- Server-side sorting, filtering and paging (the grid works on the array you give it)
|
|
1009
|
+
- Arrow-key navigation between cells
|
|
1010
|
+
- Controlled search, filter or page state (only `defaultSort` and `pageSize` initial values)
|
|
1011
|
+
|
|
1012
|
+
Behaviours to be aware of:
|
|
1013
|
+
|
|
1014
|
+
- Column filters match by substring, so `active` also matches `inactive`.
|
|
1015
|
+
- Only one filter popup is open at a time (opening another closes the first by its outside click).
|
|
1016
|
+
- Stripes and the row animation restart on every page, sort, search or filter change.
|
|
1017
|
+
- `defaultSort` and `pageSize` are read once, on mount. Changing them later has no effect.
|
|
1018
|
+
- `rows` must keep a stable identity between renders, otherwise edits are discarded (see [6.8](#68-editing)).
|
|
1019
|
+
- `onRowClick` also fires for clicks inside a cell that is being edited.
|
|
1020
|
+
- A `valueParser` that returns `NaN` stores `NaN`. Add `validate: (v) => Number.isFinite(v)` to reject it.
|
|
1021
|
+
- Date values display with `toLocaleDateString()`. Editing a `Date` cell shows its `String()` form, so store dates as ISO strings (`"2026-11-05"`) or provide a `cellEditor`.
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
## 7. Troubleshooting
|
|
1026
|
+
|
|
1027
|
+
| Problem | Cause and fix |
|
|
1028
|
+
| --- | --- |
|
|
1029
|
+
| Everything looks unstyled | Import `react-gridix/style.css` once in your app |
|
|
1030
|
+
| `theme="ocean"` does nothing | The theme CSS ships in `style.css`: make sure it is imported. The attribute `data-rk-theme` should appear on the grid root |
|
|
1031
|
+
| I can't find the filter input | Filters are per column: set `filterable: true` in the column definition, then click the dot in the header. The old grid-level `columnFilters` prop no longer exists |
|
|
1032
|
+
| Type error on `field: "contact.phone"` | The path must exist in your row type (optional objects are fine). If `rows` is typed loosely (`any`), type the columns as `ColumnDef<Person>[]` |
|
|
1033
|
+
| My checkbox column header is empty | By design: boolean columns default to no label. Set `headerName` to show one |
|
|
1034
|
+
| No row animation | `animateRows` is off, or the system has "reduce motion" turned on |
|
|
1035
|
+
| My Tailwind class doesn't change the grid | Put it in the right slot (`classNames.headerCell`, and so on). The library CSS has zero specificity, so a class in the right place wins |
|
|
1036
|
+
| Edits disappear on the next render | `rows` gets a new identity every render. Keep it in `useState` or `useMemo` |
|
|
1037
|
+
| A custom editor or renderer loses focus or resets while typing | The component is defined inline inside render. Move it to module level |
|
|
1038
|
+
| Typing in a custom editor column doesn't start editing | By design: custom editors open with double-click, Enter or F2 |
|
|
1039
|
+
| Enter doesn't move to the next row | The cell below is locked, you are on the last row of the page, or `enterMovesDown` is `false` |
|
|
1040
|
+
| Filtering "active" also shows "inactive" | Filters are substring matches. Use a `filterRenderer` with distinct values or different display text |
|
|
1041
|
+
| Changing `pageSize` or `defaultSort` after mount does nothing | They are initial values only |
|
|
1042
|
+
| A checkbox in a boolean column is greyed out and won't toggle | The cell isn't editable. Set `editable` on the grid, or `editable: true` on that column |
|
|
1043
|
+
| Type error: `field` not assignable | `field` must be a key of your row type. Check the spelling, and that `ColumnDef<T>` uses the same `T` as `rows` |
|