gbs-add-block 1.2.7 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -3
  2. package/index.cjs +10 -9
  3. package/package.json +1 -1
  4. package/source/components/combobox/README.md +170 -0
  5. package/source/components/combobox/__tests__/core.test.ts +134 -0
  6. package/source/components/combobox/core/filter.ts +155 -0
  7. package/source/components/combobox/core/index.ts +16 -0
  8. package/source/components/combobox/core/types.ts +48 -0
  9. package/source/components/combobox/core/virtual.ts +67 -0
  10. package/source/components/combobox/index.ts +13 -0
  11. package/source/components/combobox/react/Listbox.tsx +302 -0
  12. package/source/components/combobox/react/MultiSelect.tsx +263 -0
  13. package/source/components/combobox/react/Popover.tsx +70 -0
  14. package/source/components/combobox/react/Select.tsx +198 -0
  15. package/source/components/combobox/react/icons.tsx +34 -0
  16. package/source/components/combobox/react/locale.ts +16 -0
  17. package/source/components/combobox/react/props.ts +86 -0
  18. package/source/components/combobox/react/useCombobox.ts +399 -0
  19. package/source/components/combobox/styles.css +417 -0
  20. package/source/components/datagridbeta/README.md +232 -0
  21. package/source/components/datagridbeta/__tests__/core.test.ts +341 -0
  22. package/source/components/datagridbeta/__tests__/export.test.ts +70 -0
  23. package/source/components/datagridbeta/core/columnHelper.ts +21 -0
  24. package/source/components/datagridbeta/core/columns.ts +288 -0
  25. package/source/components/datagridbeta/core/filtering.ts +237 -0
  26. package/source/components/datagridbeta/core/grid.ts +616 -0
  27. package/source/components/datagridbeta/core/index.ts +14 -0
  28. package/source/components/datagridbeta/core/rows.ts +64 -0
  29. package/source/components/datagridbeta/core/sorting.ts +111 -0
  30. package/source/components/datagridbeta/core/state.ts +82 -0
  31. package/source/components/datagridbeta/core/store.ts +52 -0
  32. package/source/components/datagridbeta/core/types.ts +266 -0
  33. package/source/components/datagridbeta/core/values.ts +98 -0
  34. package/source/components/datagridbeta/core/virtual.ts +85 -0
  35. package/source/components/datagridbeta/export/csv.ts +32 -0
  36. package/source/components/datagridbeta/export/download.ts +12 -0
  37. package/source/components/datagridbeta/export/pdf.ts +61 -0
  38. package/source/components/datagridbeta/export/table.ts +73 -0
  39. package/source/components/datagridbeta/export/xlsx.ts +145 -0
  40. package/source/components/datagridbeta/export/zip.ts +87 -0
  41. package/source/components/datagridbeta/index.ts +7 -0
  42. package/source/components/datagridbeta/react/Cell.tsx +282 -0
  43. package/source/components/datagridbeta/react/ColumnMenu.tsx +135 -0
  44. package/source/components/datagridbeta/react/DataGrid.tsx +296 -0
  45. package/source/components/datagridbeta/react/FilterForm.tsx +142 -0
  46. package/source/components/datagridbeta/react/HeaderRow.tsx +306 -0
  47. package/source/components/datagridbeta/react/Pagination.tsx +107 -0
  48. package/source/components/datagridbeta/react/Popover.tsx +79 -0
  49. package/source/components/datagridbeta/react/Row.tsx +85 -0
  50. package/source/components/datagridbeta/react/Toolbar.tsx +266 -0
  51. package/source/components/datagridbeta/react/Viewport.tsx +167 -0
  52. package/source/components/datagridbeta/react/context.ts +57 -0
  53. package/source/components/datagridbeta/react/hooks.ts +80 -0
  54. package/source/components/datagridbeta/react/icons.tsx +72 -0
  55. package/source/components/datagridbeta/react/keyboard.ts +110 -0
  56. package/source/components/datagridbeta/react/locale.ts +128 -0
  57. package/source/components/datagridbeta/styles.css +717 -0
@@ -0,0 +1,417 @@
1
+ /*
2
+ * Combobox styles (Select and MultiSelect).
3
+ *
4
+ * Tokens default to the DataGrid's --dg-* variables when that stylesheet is
5
+ * present, so both components share one theme, and fall back to the same
6
+ * palette when used on their own. Rules live in the `components` layer, so
7
+ * utility classes passed through `className` / `classNames` override them.
8
+ */
9
+ @layer theme, base, components, utilities;
10
+
11
+ @layer components {
12
+ .cb-root {
13
+ --cb-bg: var(--dg-bg, light-dark(#ffffff, #0b0b0e));
14
+ --cb-fg: var(--dg-fg, light-dark(#18181b, #f4f4f5));
15
+ --cb-muted: var(--dg-muted, light-dark(#71717a, #a1a1aa));
16
+ --cb-border: var(--dg-border, light-dark(#e4e4e7, #27272a));
17
+ --cb-hover: var(--dg-hover, light-dark(#f4f4f5, #1f1f23));
18
+ --cb-input-bg: var(--dg-input-bg, light-dark(#ffffff, #121216));
19
+ --cb-accent: var(--dg-accent, light-dark(#2563eb, #60a5fa));
20
+ --cb-accent-fg: var(--dg-accent-fg, light-dark(#ffffff, #0b1220));
21
+ --cb-accent-soft: var(--dg-accent-soft, light-dark(#eff6ff, #172554));
22
+ --cb-accent-strong: var(--dg-accent-strong, light-dark(#1d4ed8, #bfdbfe));
23
+ --cb-focus: var(--dg-focus, light-dark(#2563eb, #60a5fa));
24
+ --cb-danger: var(--dg-danger, light-dark(#dc2626, #f87171));
25
+ --cb-shadow: var(--dg-shadow, 0 10px 30px -8px light-dark(rgb(0 0 0 / 0.18), rgb(0 0 0 / 0.6)));
26
+ --cb-radius: var(--dg-radius, 8px);
27
+ --cb-font-size: var(--dg-font-size, 13px);
28
+ --cb-height: 36px;
29
+ --cb-px: 10px;
30
+
31
+ color-scheme: inherit;
32
+ position: relative;
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: 4px;
36
+ width: 100%;
37
+ min-width: 0;
38
+ font-size: var(--cb-font-size);
39
+ line-height: 1.4;
40
+ color: var(--cb-fg);
41
+ }
42
+
43
+ :where(.dark, [data-theme="dark"]) .cb-root {
44
+ color-scheme: dark;
45
+ }
46
+ :where(.light, [data-theme="light"]) .cb-root {
47
+ color-scheme: light;
48
+ }
49
+
50
+ :where(.cb-root, .cb-popover) *,
51
+ :where(.cb-root, .cb-popover) *::before,
52
+ :where(.cb-root, .cb-popover) *::after {
53
+ box-sizing: border-box;
54
+ }
55
+
56
+ .cb-root[data-size="sm"] {
57
+ --cb-height: 30px;
58
+ --cb-font-size: 12px;
59
+ --cb-px: 8px;
60
+ }
61
+ .cb-root[data-size="lg"] {
62
+ --cb-height: 44px;
63
+ --cb-font-size: 14px;
64
+ --cb-px: 12px;
65
+ }
66
+
67
+ .cb-label {
68
+ font-weight: 500;
69
+ }
70
+ .cb-label[data-required]::after {
71
+ content: " *";
72
+ color: var(--cb-danger);
73
+ }
74
+ .cb-description {
75
+ color: var(--cb-muted);
76
+ font-size: 0.92em;
77
+ }
78
+ .cb-error {
79
+ color: var(--cb-danger);
80
+ font-size: 0.92em;
81
+ }
82
+
83
+ /* -------------------------------------------------------------- trigger */
84
+
85
+ .cb-control {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 6px;
89
+ width: 100%;
90
+ min-height: var(--cb-height);
91
+ padding: 3px var(--cb-px);
92
+ border: 1px solid var(--cb-border);
93
+ border-radius: var(--cb-radius);
94
+ background: var(--cb-input-bg);
95
+ color: inherit;
96
+ font: inherit;
97
+ text-align: start;
98
+ cursor: pointer;
99
+ }
100
+ .cb-control:hover:not([data-disabled]) {
101
+ border-color: color-mix(in oklab, var(--cb-border), var(--cb-fg) 25%);
102
+ }
103
+ .cb-control:focus-visible,
104
+ .cb-root[data-state="open"] .cb-control {
105
+ outline: 2px solid var(--cb-focus);
106
+ outline-offset: -1px;
107
+ }
108
+ .cb-control[data-invalid] {
109
+ border-color: var(--cb-danger);
110
+ }
111
+ .cb-control[data-invalid]:focus-visible,
112
+ .cb-root[data-invalid][data-state="open"] .cb-control {
113
+ outline-color: var(--cb-danger);
114
+ }
115
+ .cb-control[data-disabled] {
116
+ opacity: 0.55;
117
+ cursor: not-allowed;
118
+ }
119
+
120
+ .cb-value {
121
+ flex: 1 1 auto;
122
+ min-width: 0;
123
+ overflow: hidden;
124
+ text-overflow: ellipsis;
125
+ white-space: nowrap;
126
+ }
127
+ .cb-placeholder {
128
+ color: var(--cb-muted);
129
+ }
130
+ .cb-value-icon {
131
+ display: inline-flex;
132
+ margin-inline-end: 6px;
133
+ vertical-align: -2px;
134
+ color: var(--cb-muted);
135
+ }
136
+
137
+ .cb-tags {
138
+ display: flex;
139
+ flex: 1 1 auto;
140
+ flex-wrap: wrap;
141
+ align-items: center;
142
+ gap: 4px;
143
+ min-width: 0;
144
+ }
145
+ .cb-tag {
146
+ display: inline-flex;
147
+ align-items: center;
148
+ gap: 2px;
149
+ max-width: 100%;
150
+ padding-inline: 8px 3px;
151
+ border-radius: 999px;
152
+ background: var(--cb-accent-soft);
153
+ color: var(--cb-accent-strong);
154
+ font-size: 0.92em;
155
+ line-height: 20px;
156
+ }
157
+ .cb-tag-label {
158
+ overflow: hidden;
159
+ text-overflow: ellipsis;
160
+ white-space: nowrap;
161
+ }
162
+ .cb-tag-remove {
163
+ display: inline-grid;
164
+ place-items: center;
165
+ width: 16px;
166
+ height: 16px;
167
+ padding: 0;
168
+ border: 0;
169
+ border-radius: 50%;
170
+ background: transparent;
171
+ color: inherit;
172
+ cursor: pointer;
173
+ }
174
+ .cb-tag-remove:hover {
175
+ background: color-mix(in oklab, var(--cb-accent-strong), transparent 85%);
176
+ }
177
+ .cb-tag-overflow {
178
+ color: var(--cb-muted);
179
+ font-size: 0.92em;
180
+ white-space: nowrap;
181
+ }
182
+
183
+ .cb-actions {
184
+ display: flex;
185
+ flex: none;
186
+ align-items: center;
187
+ gap: 2px;
188
+ color: var(--cb-muted);
189
+ }
190
+ .cb-icon-button {
191
+ display: inline-grid;
192
+ place-items: center;
193
+ width: 22px;
194
+ height: 22px;
195
+ padding: 0;
196
+ border: 0;
197
+ border-radius: 4px;
198
+ background: transparent;
199
+ color: inherit;
200
+ cursor: pointer;
201
+ }
202
+ .cb-icon-button:hover {
203
+ background: var(--cb-hover);
204
+ color: var(--cb-fg);
205
+ }
206
+ .cb-icon-button:focus-visible {
207
+ outline: 2px solid var(--cb-focus);
208
+ outline-offset: 1px;
209
+ }
210
+ .cb-chevron {
211
+ transition: transform 120ms ease;
212
+ }
213
+ .cb-root[data-state="open"] .cb-chevron {
214
+ transform: rotate(180deg);
215
+ }
216
+ @media (prefers-reduced-motion: reduce) {
217
+ .cb-chevron {
218
+ transition: none;
219
+ }
220
+ }
221
+
222
+ /* -------------------------------------------------------------- popover */
223
+
224
+ .cb-popover {
225
+ position: fixed;
226
+ inset: auto;
227
+ margin: 0;
228
+ padding: 0;
229
+ overflow: hidden;
230
+ border: 1px solid var(--cb-border);
231
+ border-radius: var(--cb-radius);
232
+ background: var(--cb-bg);
233
+ color: var(--cb-fg);
234
+ box-shadow: var(--cb-shadow);
235
+ font-size: var(--cb-font-size);
236
+ }
237
+ .cb-search {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 6px;
241
+ padding: 6px 10px;
242
+ border-bottom: 1px solid var(--cb-border);
243
+ color: var(--cb-muted);
244
+ }
245
+ .cb-search input {
246
+ width: 100%;
247
+ min-width: 0;
248
+ border: 0;
249
+ outline: 0;
250
+ background: transparent;
251
+ color: var(--cb-fg);
252
+ font: inherit;
253
+ }
254
+
255
+ .cb-list {
256
+ position: relative;
257
+ overflow: auto;
258
+ overscroll-behavior: contain;
259
+ padding: 4px;
260
+ outline: none;
261
+ }
262
+ .cb-list:focus-visible {
263
+ outline: 2px solid var(--cb-focus);
264
+ outline-offset: -2px;
265
+ }
266
+ .cb-list[data-virtual] .cb-item,
267
+ .cb-list[data-virtual] .cb-group {
268
+ position: absolute;
269
+ inset-inline: 4px;
270
+ }
271
+
272
+ .cb-group {
273
+ display: flex;
274
+ align-items: center;
275
+ padding: 6px 8px 2px;
276
+ color: var(--cb-muted);
277
+ font-size: 0.85em;
278
+ font-weight: 600;
279
+ letter-spacing: 0.04em;
280
+ text-transform: uppercase;
281
+ }
282
+
283
+ .cb-item {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 8px;
287
+ width: 100%;
288
+ padding: 6px 8px;
289
+ border: 0;
290
+ border-radius: 6px;
291
+ background: transparent;
292
+ color: inherit;
293
+ font: inherit;
294
+ text-align: start;
295
+ cursor: pointer;
296
+ }
297
+ .cb-item[data-active] {
298
+ background: var(--cb-hover);
299
+ }
300
+ .cb-item[data-selected] {
301
+ color: var(--cb-accent-strong);
302
+ font-weight: 500;
303
+ }
304
+ .cb-item[data-disabled] {
305
+ opacity: 0.45;
306
+ cursor: not-allowed;
307
+ }
308
+ .cb-item-check {
309
+ flex: none;
310
+ width: 16px;
311
+ color: var(--cb-accent);
312
+ opacity: 0;
313
+ }
314
+ .cb-item[data-selected] .cb-item-check {
315
+ opacity: 1;
316
+ }
317
+ .cb-item-icon {
318
+ flex: none;
319
+ display: inline-flex;
320
+ color: var(--cb-muted);
321
+ }
322
+ .cb-item-body {
323
+ flex: 1 1 auto;
324
+ min-width: 0;
325
+ }
326
+ .cb-item-label {
327
+ overflow: hidden;
328
+ text-overflow: ellipsis;
329
+ white-space: nowrap;
330
+ }
331
+ .cb-item-description {
332
+ overflow: hidden;
333
+ color: var(--cb-muted);
334
+ font-size: 0.9em;
335
+ text-overflow: ellipsis;
336
+ white-space: nowrap;
337
+ }
338
+ .cb-mark {
339
+ background: color-mix(in oklab, var(--cb-accent), transparent 82%);
340
+ border-radius: 2px;
341
+ color: inherit;
342
+ font-weight: 600;
343
+ }
344
+
345
+ .cb-empty,
346
+ .cb-status {
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ gap: 8px;
351
+ padding: 16px 8px;
352
+ color: var(--cb-muted);
353
+ }
354
+ .cb-footer {
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: space-between;
358
+ gap: 8px;
359
+ padding: 4px 6px;
360
+ border-top: 1px solid var(--cb-border);
361
+ }
362
+ .cb-link-button {
363
+ padding: 4px 6px;
364
+ border: 0;
365
+ border-radius: 4px;
366
+ background: none;
367
+ color: var(--cb-accent);
368
+ font: inherit;
369
+ cursor: pointer;
370
+ }
371
+ .cb-link-button:hover {
372
+ background: var(--cb-hover);
373
+ }
374
+ .cb-link-button:disabled {
375
+ opacity: 0.5;
376
+ cursor: default;
377
+ }
378
+ .cb-footer-count {
379
+ color: var(--cb-muted);
380
+ }
381
+
382
+ .cb-spinner {
383
+ animation: cb-spin 0.7s linear infinite;
384
+ }
385
+ @keyframes cb-spin {
386
+ to {
387
+ transform: rotate(360deg);
388
+ }
389
+ }
390
+ @media (prefers-reduced-motion: reduce) {
391
+ .cb-spinner {
392
+ animation-duration: 2s;
393
+ }
394
+ }
395
+
396
+ .cb-sr-only {
397
+ position: absolute;
398
+ width: 1px;
399
+ height: 1px;
400
+ margin: -1px;
401
+ padding: 0;
402
+ overflow: hidden;
403
+ clip-path: inset(50%);
404
+ white-space: nowrap;
405
+ border: 0;
406
+ }
407
+
408
+ @media (forced-colors: active) {
409
+ .cb-item[data-active] {
410
+ outline: 1px solid Highlight;
411
+ }
412
+ .cb-mark {
413
+ background: Highlight;
414
+ color: HighlightText;
415
+ }
416
+ }
417
+ }
@@ -0,0 +1,232 @@
1
+ # DataGrid
2
+
3
+ A virtualized data grid for React 19 with no runtime dependencies besides React.
4
+ Works in Vite/SPA apps and in the Next.js App Router.
5
+
6
+ - **Fast by design:** rows and columns are virtualized; state lives in an
7
+ external store with per-row subscriptions, so selecting a row or moving focus
8
+ re-renders only the rows involved; column resizing rewrites CSS variables
9
+ without rendering; filtering and sorting run at low priority via
10
+ `useDeferredValue`.
11
+ - **Features:** typed columns, multi-column sort, typed column filters, global
12
+ search, pagination, row selection (single, multiple, shift-range), column
13
+ resize / reorder (drag or menu) / pin / hide, inline editing with validation and
14
+ async saves, CSV / Excel / PDF export, clipboard copy, density, i18n, RTL, dark
15
+ mode, WAI-ARIA grid keyboard navigation.
16
+ - **Client or server mode:** let the grid sort/filter/paginate, or receive a
17
+ query and fetch pages yourself.
18
+
19
+ ## Setup
20
+
21
+ Import the stylesheet once (e.g. in your root CSS or layout):
22
+
23
+ ```css
24
+ @import "../component-lib/data-grid/styles.css";
25
+ ```
26
+
27
+ or from JS: `import "component-lib/data-grid/styles.css";`
28
+
29
+ ## Basic usage
30
+
31
+ ```tsx
32
+ import { createColumnHelper, DataGrid } from "component-lib/data-grid";
33
+
34
+ interface Employee { id: number; name: string; salary: number; startDate: string; active: boolean }
35
+
36
+ const col = createColumnHelper<Employee>();
37
+
38
+ // Define columns outside the component (or memoize them).
39
+ const columns = [
40
+ col.field("id", { header: "ID", type: "number", width: 80, pin: "left" }),
41
+ col.field("name", { width: 200 }),
42
+ col.field("salary", { type: "number", format: (v) => `$${v.toLocaleString()}` }),
43
+ col.field("startDate", { type: "date" }),
44
+ col.field("active", { type: "boolean" }),
45
+ ];
46
+
47
+ export function Employees({ data }: { data: Employee[] }) {
48
+ return <DataGrid data={data} columns={columns} getRowId="id" enableRowSelection height={600} />;
49
+ }
50
+ ```
51
+
52
+ `createColumnHelper` infers the value type for `cell`, `format`, `validate` and
53
+ `sortFn`. Plain `ColumnDef<T>[]` objects work too.
54
+
55
+ ### Stable inputs
56
+
57
+ The grid memoizes on the identity of `data`, `columns` and `getRowId`. Keep
58
+ them stable: define columns at module level or with `useMemo`, and pass
59
+ `getRowId` as a property name (`getRowId="id"`). With React Compiler enabled,
60
+ inline values are memoized for you.
61
+
62
+ ## Columns
63
+
64
+ | Option | Purpose |
65
+ | --- | --- |
66
+ | `field` / `accessor` / `id` | Where the value comes from. Display-only columns need just `id` and `cell`. |
67
+ | `header`, `width`, `minWidth`, `maxWidth`, `align` | Presentation. |
68
+ | `type` | `string` (default), `number`, `date`, `boolean`. Drives filter operators, sorting, alignment, editors and Excel cell types. |
69
+ | `options` | `{ label, value }[]` for enum columns: "is any of" filter, select editor, label display. |
70
+ | `format(value, row)` | Display text. Also used by search, CSV, PDF and copy. |
71
+ | `cell(ctx)` | Custom renderer. Clicks on buttons/inputs inside cells don't trigger `onRowClick`. |
72
+ | `pin`, `hidden` | Initial pin side / visibility. |
73
+ | `sortable`, `filterable`, `resizable`, `reorderable`, `pinnable`, `hideable`, `searchable` | Per-column feature switches (default `true`). |
74
+ | `sortFn`, `filterFn` | Custom comparison / matching. |
75
+ | `editable`, `editor`, `validate` | Inline editing (below). |
76
+ | `exportable`, `exportValue(row)` | Export control. |
77
+ | `headerClassName`, `cellClassName` | Styling hooks. |
78
+
79
+ ## Server mode
80
+
81
+ ```tsx
82
+ const [query, setQuery] = useState<GridQuery>(initialQuery);
83
+ const { data, isFetching } = useQuery({ queryKey: ["orders", query], queryFn: () => fetchOrders(query) });
84
+
85
+ <DataGrid
86
+ mode="server"
87
+ data={data?.rows ?? []}
88
+ rowCount={data?.total ?? 0}
89
+ loading={isFetching}
90
+ columns={columns}
91
+ initialState={initialQuery}
92
+ onQueryChange={setQuery}
93
+ />
94
+ ```
95
+
96
+ `onQueryChange` receives `{ sorting, filters, globalFilter, pagination }`
97
+ whenever one of them changes. Changing filters, search or sorting resets to the
98
+ first page. The previous rows stay visible while `loading`.
99
+
100
+ The framework-free core (`component-lib/data-grid/core`) exports `filterRows`,
101
+ `sortRows` and `paginate`, so a Node or Next.js route handler can apply exactly
102
+ the same semantics on the server.
103
+
104
+ ## State
105
+
106
+ All grid state can be controlled or left internal:
107
+
108
+ ```ts
109
+ interface GridState {
110
+ sorting; filters; globalFilter; pagination; rowSelection;
111
+ columnOrder; columnVisibility; columnSizing; columnPinning; density;
112
+ }
113
+ ```
114
+
115
+ - `initialState` seeds internal state.
116
+ - `state` controls any subset of keys; the grid then calls `onStateChange(next, prev)`
117
+ instead of updating those keys itself.
118
+ - Example: persist column layout with
119
+ `onStateChange={(s) => save(pick(s, ["columnOrder", "columnSizing", "columnPinning", "columnVisibility"]))}`.
120
+
121
+ ## Editing
122
+
123
+ ```ts
124
+ col.field("salary", {
125
+ type: "number",
126
+ editable: (row) => !row.locked,
127
+ validate: (v) => (v < 0 ? "Must be positive" : null),
128
+ });
129
+
130
+ <DataGrid onCellEdit={async ({ rowId, columnId, value }) => { await save(rowId, columnId, value); }} />
131
+ ```
132
+
133
+ Start editing with double-click, Enter or F2. Enter commits and moves down,
134
+ Tab commits and moves right, and Escape cancels. The grid never mutates `data`:
135
+ update it in `onCellEdit`. If `onCellEdit` returns a promise, the cell shows the
136
+ pending value until it settles, and a rejection marks the cell invalid.
137
+ Custom editors: `editor: (props) => <MyInput value={props.value} onChange={props.onChange} onBlur={() => props.commit()} />`.
138
+
139
+ ## Imperative API
140
+
141
+ ```tsx
142
+ const api = useRef<GridApi<Employee>>(null);
143
+ <DataGrid ref={api} ... />
144
+
145
+ api.current?.setFilter("status", { operator: "in", value: ["active"] });
146
+ api.current?.exportExcel({ scope: "selected", fileName: "people" });
147
+ api.current?.focusCell(0, "name");
148
+ ```
149
+
150
+ Methods include `toggleSort`, `setSorting`, `setFilter`, `clearFilters`,
151
+ `setGlobalFilter`, `setPageIndex`, `setPageSize`, `toggleRowSelected`,
152
+ `toggleAllRowsSelected`, `getSelectedRows`, `setColumnVisibility`,
153
+ `setColumnWidth`, `pinColumn`, `moveColumn`, `resetColumns`, `scrollToRow`,
154
+ `focusCell`, `startEditing`, `getRows`, `exportCsv`, `exportExcel`, `exportPdf`
155
+ and `copyToClipboard`.
156
+
157
+ ## Export
158
+
159
+ Export code is split into chunks loaded on first use.
160
+
161
+ | Format | Notes |
162
+ | --- | --- |
163
+ | CSV | UTF-8 with BOM (opens correctly in Excel). Cells starting with `= + - @` are prefixed with `'` to prevent formula injection. |
164
+ | Excel | Real `.xlsx`: typed numbers, booleans and dates, bold frozen header, auto-filter, column widths. Written without a library. |
165
+ | PDF | Opens the browser print dialog with a print-formatted table; users choose "Save as PDF". Best for up to a few thousand rows. |
166
+
167
+ Scopes: `filtered` (default), `all`, `selected`, `page`, or pass `rows`, e.g. a
168
+ full result set fetched from the server.
169
+
170
+ ## Keyboard
171
+
172
+ | Keys | Action |
173
+ | --- | --- |
174
+ | Arrows, Home/End, Ctrl+Home/End, PageUp/PageDown | Move between cells (header row included). |
175
+ | Enter (header) | Sort; Shift+Enter adds to the sort. |
176
+ | Alt+↓ or the Menu key (header) | Open the column menu. |
177
+ | Enter / F2 | Edit the cell, or focus the widget inside a custom cell. |
178
+ | Space | Toggle row selection (Shift for a range). |
179
+ | Ctrl/Cmd+A | Select all rows. |
180
+ | Ctrl/Cmd+C | Copy selected rows (TSV) or the active cell. |
181
+ | Escape | Cancel editing / leave a widget inside a cell. |
182
+
183
+ ## Theming
184
+
185
+ Styles live in `@layer components`, so Tailwind utilities passed via
186
+ `classNames` / `className` win. Override the `--dg-*` variables for a theme:
187
+
188
+ ```css
189
+ .dg-root { --dg-accent: #7c3aed; --dg-radius: 12px; --dg-font-size: 14px; }
190
+ ```
191
+
192
+ Colors follow the page's `color-scheme`. A `.dark` or `[data-theme="dark"]`
193
+ ancestor forces a scheme. State is exposed as attributes for styling:
194
+ `data-selected`, `data-active`, `data-editing`, `data-pinned`, `data-density`,
195
+ and `aria-sort` on header cells.
196
+
197
+ Slots for `classNames`: `root`, `toolbar`, `viewport`, `header`, `headerCell`,
198
+ `row`, `cell`, `pagination`.
199
+
200
+ ## Next.js
201
+
202
+ Components carry `"use client"`; import `DataGrid` from a Server Component and
203
+ pass serializable props (`data`, `initialState`). Column definitions contain
204
+ functions, so define them in a client module. For server mode, fetch in a
205
+ Server Component or Route Handler using `core` functions, and sync the query to
206
+ the URL (e.g. with `nuqs`) through `state` + `onQueryChange`.
207
+
208
+ ## Known limits
209
+
210
+ - Rows have a fixed height (per density or `rowHeight`); variable-height rows are not supported.
211
+ - Updates from the grid's store render synchronously; heavy client-side filtering is
212
+ deferred with `useDeferredValue`, but a single filter pass over very large data
213
+ (1M+ rows) still runs on the main thread. For that scale, use server mode or a worker.
214
+ - Row grouping, tree data and pivoting are not implemented.
215
+
216
+ ## Migrating from `component-lib/datagrid`
217
+
218
+ | Old prop | New |
219
+ | --- | --- |
220
+ | `dataSource` (array) | `data` |
221
+ | `dataSource` (URL string) | Fetch in your app; pass `data` (the grid no longer fetches). |
222
+ | `lazy` + `pageSettings.totalCount` | `mode="server"` + `rowCount` |
223
+ | `pageSettings.pageNumber` / `pageSize` | `initialState={{ pagination: { pageIndex: 0, pageSize } }}` |
224
+ | `enableSearch`, `enableExcelExport`, `enablePdfExport` | `toolbar={{ search, export }}` |
225
+ | `selectAll`, `onSelectRow` | `enableRowSelection`, `onStateChange` / `api.getSelectedRows()` |
226
+ | `rowChange` / `onRowClick` | `onRowClick` |
227
+ | `pageStatus` / `onPageChange`, `onFilterChange`, `onSearchChange` | `onQueryChange` |
228
+ | `initialFilters`, `initialSearchParam` | `initialState={{ filters, globalFilter }}` |
229
+ | `isFetching` | `loading` |
230
+ | `column.headerText`, `column.template` | `header`, `cell` |
231
+ | `grid*Class` props | `classNames` slots |
232
+ | `ref.goToPage(n)` etc. | `ref.setPageIndex(n)` and the API above |