gbs-add-block 2.0.0 → 2.0.2

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,132 @@
1
+ ---
2
+ name: gbs-components
3
+ description: Builds UI with the GBS headless component library (gbs-add-block). Use when adding or changing UI in apps that have a component-lib/ folder.
4
+ autoAttach: ["src/**/*.tsx", "src/**/*.jsx", "app/**/*.tsx", "components/**/*.tsx"]
5
+ ---
6
+
7
+ # GBS components (2.0 beta)
8
+
9
+ ## Install before you import
10
+
11
+ Not an npm dependency — the CLI **copies source into the repo**:
12
+
13
+ ```bash
14
+ npx gbs-add-block -a Button,Input,Modal --beta
15
+ ```
16
+
17
+ Writes `component-lib/<folder>/` plus `component-lib/shared/`. Always pass
18
+ `--beta`. Import the folder barrel **and its stylesheet** (or the repo's alias):
19
+
20
+ ```ts
21
+ import { Button } from "component-lib/button";
22
+ import "component-lib/button/styles.css";
23
+ ```
24
+
25
+ Folder = lowercased name, except `data-grid`, `date-picker`, `file-uploader`,
26
+ `number-input`, `radio-group`.
27
+
28
+ ## Rules most often missed
29
+
30
+ 1. **Change handlers are named per component.** `onValueChange`: Input,
31
+ OtpInput, Textarea, NumberInput, CheckboxGroup, RadioGroup, Tabs, Accordion,
32
+ MenuRadioGroup. `onCheckedChange`: Checkbox, Switch, MenuCheckboxItem.
33
+ `onChange`: Select, MultiSelect, DatePicker, DateRangePicker, FileUploader.
34
+ `onOpenChange`: Modal, Popover, Menu.
35
+ 2. **Never hand-roll `<label>`, hint or error markup.** Fields take `label`,
36
+ `description` and `error`; `error` also marks the control invalid and wires
37
+ `aria-describedby`. A `<label>` wrapper breaks it.
38
+ 3. **Style via `classNames` slots** (`classNames={{ root, label }}`);
39
+ `className` hits the root only. Theme globally with `--gbs-*` on `:root`.
40
+ 4. **Mount `<Toaster />` and `<DialogHost />` once at the app root**, or
41
+ `toast()` and `dialog.confirm()` do nothing.
42
+ 5. **All are client components** (`"use client"`); a Next.js Server Component
43
+ can render them but not pass function props.
44
+ 6. **Compound families throw outside their parent**: Tab/TabList/TabPanel need
45
+ Tabs, AccordionItem needs Accordion, MenuItem needs Menu.
46
+
47
+ ## Inventory (required props in parens)
48
+
49
+ **Forms** — Input, Textarea, NumberInput (locale-aware), OtpInput; Checkbox +
50
+ CheckboxGroup; Radio (`value`) + RadioGroup; Switch (applies immediately);
51
+ Select and MultiSelect (`options`; searchable, client or server); DatePicker and
52
+ DateRangePicker; FileUploader (chunked).
53
+
54
+ **Actions** — Button: variants, sizes, icons, auto-loading from a returned
55
+ promise; `render` draws a router link.
56
+
57
+ **Overlays** — Modal (native `<dialog>`, also drawers); `dialog` + DialogHost
58
+ (`await dialog.confirm/alert/prompt`); Popover (`trigger`); Menu (`trigger`) with
59
+ MenuItem, MenuCheckboxItem, MenuRadioGroup, MenuRadioItem, MenuGroup,
60
+ MenuSeparator, MenuSub; Tooltip (`content`); `toast` + Toaster.
61
+
62
+ **Data** — DataGrid (`data`, `columns`) + createColumnHelper: virtualized;
63
+ sort, filter, edit, CSV/Excel/PDF export.
64
+
65
+ **Display** — Tabs/TabList/Tab (`value`)/TabPanel (`value`); Accordion +
66
+ AccordionItem (`value`); Card/CardHeader/CardBody/CardFooter/Stat; Alert; Badge
67
+ and Tag; Avatar and AvatarGroup; Progress and CircularProgress (`value={null}`
68
+ is indeterminate); Skeleton and Empty; Spinner; Breadcrumb (`items`,
69
+ `renderLink`).
70
+
71
+ ## Example
72
+
73
+ ```tsx
74
+ "use client";
75
+ import { useState } from "react";
76
+ import { Button } from "component-lib/button";
77
+ import { Input } from "component-lib/input";
78
+ import { Select } from "component-lib/combobox";
79
+ import { Modal } from "component-lib/modal";
80
+ import { toast } from "component-lib/toaster";
81
+ // plus each styles.css, once
82
+
83
+ const ROLES = [{ value: "admin", label: "Admin" }, { value: "dev", label: "Dev" }];
84
+
85
+ export function InviteButton() {
86
+ const [open, setOpen] = useState(false);
87
+ const [email, setEmail] = useState("");
88
+ const [role, setRole] = useState<string | null>(null);
89
+ const invalid = !!email && !email.includes("@");
90
+
91
+ return (
92
+ <>
93
+ <Button onClick={() => setOpen(true)}>Invite</Button>
94
+ <Modal open={open} onOpenChange={setOpen} title="Invite a teammate"
95
+ footer={({ close }) => (
96
+ <Button disabled={!email || invalid} onClick={async () => {
97
+ await invite({ email, role });
98
+ toast.success("Invitation sent");
99
+ close();
100
+ }}>Send</Button>
101
+ )}>
102
+ <Input label="Email" type="email" value={email} onValueChange={setEmail}
103
+ required error={invalid ? "Enter a valid email" : undefined} />
104
+ <Select label="Role" options={ROLES} value={role} onChange={setRole} />
105
+ </Modal>
106
+ </>
107
+ );
108
+ }
109
+ ```
110
+
111
+ ## Don't
112
+
113
+ - Raw `<button>`, `<input>`, `<select>`, `<table>`, `<dialog>`, or a hand-built
114
+ modal/menu/tooltip, when a component exists.
115
+ - Deep imports (`.../input/react/Input`); import the barrel. Only `<name>/core`
116
+ is also public.
117
+ - Editing `component-lib/shared/`; the CLI replaces it on update.
118
+ - Invented props: no `asChild`, no `variant` on Input, no `onChange` on Switch.
119
+ - `!important` or internal class selectors; set `--gbs-*` or pass `classNames`.
120
+ - A Tooltip as a name; icon buttons need `aria-label`.
121
+
122
+ ## Details (in `references/`)
123
+
124
+ - `install.md` — CLI flags, folders, `shared/`, 1.x vs beta.
125
+ - `forms.md` — text and choice fields, controlled state, form posting.
126
+ - `pickers.md` — Select, MultiSelect, DatePicker, FileUploader.
127
+ - `overlays.md` — Modal, dialog, Popover, Menu, Tooltip.
128
+ - `toaster.md` — `toast()` and `<Toaster />`.
129
+ - `data-grid.md` — columns, API, export.
130
+ - `data-display.md` — Card, Tabs, Accordion, Badge, Avatar, Progress.
131
+ - `styling.md` — tokens, layers, Tailwind, slots, `data-*`, dark.
132
+ - `accessibility.md` — what is supplied, what you add.
@@ -0,0 +1,102 @@
1
+ # Accessibility contract
2
+
3
+ Each component supplies its own roles, states and keyboard behaviour. A short
4
+ list of things stays yours — mostly names and headings, which only the page
5
+ knows. Do not duplicate what is already supplied: a second `aria-describedby`
6
+ or your own `<label htmlFor>` will fight the built-in wiring.
7
+
8
+ ## What the components supply
9
+
10
+ | Area | Supplied |
11
+ | --- | --- |
12
+ | **Form fields** | A real native control; `<label>` linked to it via a generated `useId`; `aria-describedby` assembled from the hint, description and error (`{id}-description`, `{id}-error`); `aria-invalid` when `error` is set; `aria-required`. |
13
+ | **CheckboxGroup / RadioGroup** | `role="group"` / `role="radiogroup"` with the `label` as the legend, one shared `name`, one tab stop, arrow-key roving. |
14
+ | **Switch** | `role="switch"` on a real checkbox, so it is announced "on"/"off"; Space toggles, → on and ← off (mirrored in RTL). |
15
+ | **NumberInput** | `role="spinbutton"` with `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-valuetext`. |
16
+ | **Select / MultiSelect** | `role="combobox"` owning a `role="listbox"`; `aria-expanded`, `aria-controls`, `aria-activedescendant` so focus stays in the search box; `aria-selected` per option. |
17
+ | **Modal** | Native `<dialog>`: top layer, the page behind inert, focus trap, focus return on close. `title` names it. |
18
+ | **Popover** | `aria-expanded` and `aria-controls` on the trigger; `title` names the panel; light dismiss and Escape from the browser; focus returns to the trigger. |
19
+ | **Menu** | The WAI-ARIA menu button pattern: `aria-haspopup`, `aria-expanded`, `role="menu"` / `menuitem`, arrow keys, typeahead, Home/End, submenu arrows, Escape. |
20
+ | **Tooltip** | `aria-describedby` on the child; Escape dismisses from anywhere. |
21
+ | **Toaster** | A labelled landmark, polite live region; error toasts use `role="alert"`; **Alt+T** moves focus to the stack. |
22
+ | **Alert** | `role="alert"` for `danger` / `warning`, `role="status"` otherwise. |
23
+ | **Progress** | `role="progressbar"` with `aria-valuenow` / `min` / `max`, omitted entirely when `value={null}` so it reads as busy. |
24
+ | **Tabs** | `role="tablist"` / `tab` / `tabpanel`, `aria-controls`, arrow keys (mirrored in RTL), Home/End, disabled tabs skipped. |
25
+ | **Accordion** | Native `<details>`/`<summary>`: focusable header, Enter and Space, panel out of the accessibility tree while closed. |
26
+ | **DataGrid** | The WAI-ARIA grid pattern: `role="row"` / `rowgroup`, `aria-colindex`, `aria-rowindex`, `aria-sort` on headers, `aria-multiselectable`, full cell keyboard navigation. |
27
+ | **Skeleton** | Bars are `aria-hidden` — a screen reader gains nothing from a grey box. |
28
+ | **Icons** | Decorative icons inside components are `aria-hidden`. |
29
+
30
+ ## What you must still provide
31
+
32
+ 1. **A `label` on every field.** It is the accessible name. Without it the
33
+ control is unnamed, whatever the placeholder says.
34
+
35
+ 2. **`aria-label` on icon-only buttons.**
36
+
37
+ ```tsx
38
+ <Button variant="ghost" icon={<TrashIcon />} aria-label="Delete" />
39
+ ```
40
+
41
+ A Tooltip does **not** supply this: it is wired with `aria-describedby`, so
42
+ the control keeps its own name. An icon button inside a Tooltip still needs
43
+ `aria-label`.
44
+
45
+ 3. **A name for every overlay.** `Modal` needs `title`, or `aria-label` when it
46
+ has no visible heading. `Popover` needs `title` or `aria-label` (it defaults
47
+ to "More information", which says nothing). `Menu` needs `label` (defaults to
48
+ "Menu").
49
+
50
+ 4. **`aria-label` on `TabList`** — `<TabList aria-label="Project">` — and on
51
+ `DataGrid` where the grid's purpose is not obvious from the page.
52
+
53
+ 5. **Your own heading levels.** `CardHeader` takes a `title` node, not a level:
54
+ pass `<h3>Revenue</h3>` so the page owns the document outline.
55
+
56
+ 6. **A name for a set of avatars.** `<AvatarGroup label="Assigned to">` — a row
57
+ of faces with no name says nothing.
58
+
59
+ 7. **`alt` semantics for Avatar** come from `name`. Set `decorative` when the
60
+ person's name is already printed next to it, so it is not announced twice.
61
+
62
+ 8. **An accessible route to anything that only appears on hover.** Tooltips are
63
+ never shown on touch devices and never take focus.
64
+
65
+ 9. **Keyboard bindings for `shortcut` hints.** `MenuItem shortcut="⌘E"` only
66
+ draws the hint; binding the key is yours.
67
+
68
+ 10. **A visible label for `Stat`** — it takes `label` as a required prop for
69
+ that reason; a figure alone means nothing.
70
+
71
+ ## Errors and validation
72
+
73
+ Pass the message as `error`. The component sets `aria-invalid`, renders the
74
+ message with an `{id}-error` id and appends that id to `aria-describedby`.
75
+ Do not add `aria-invalid` or `aria-describedby` yourself.
76
+
77
+ Order matters and is handled for you: any `aria-describedby` you pass is kept
78
+ first, then the hint, then the description, then the error — the error last
79
+ because it is the part that changes and the part a listener is waiting for.
80
+
81
+ ## Live regions
82
+
83
+ - `Alert` announces only when its contents change. An alert rendered on first
84
+ paint says nothing — use a toast for something that just happened.
85
+ - `Toaster` is always in the page as a polite region, so new toasts are read
86
+ without interrupting; `type: "error"` interrupts.
87
+ - `Spinner` and `Skeleton` both take a `label`. Use one of them, or mark the
88
+ region busy — not both, or the wait is announced twice.
89
+
90
+ ## Right-to-left
91
+
92
+ Stylesheets use logical properties (`padding-inline`, `margin-inline`,
93
+ `inset-inline`, `border-inline`, `text-align: start`), so layout follows the
94
+ document's `dir` with no configuration. Arrow-key behaviour mirrors in Menu,
95
+ Switch, Tabs and the DataGrid. `Toaster` accepts an explicit `dir` prop
96
+ (`ltr` `rtl` `auto`) for a region that must differ from the page.
97
+
98
+ ## Reduced motion
99
+
100
+ Most stylesheets (21 of 27) disable their animations under
101
+ `@media (prefers-reduced-motion: reduce)`. Do not add motion of your own that
102
+ ignores it.
@@ -0,0 +1,185 @@
1
+ # Data display
2
+
3
+ The DataGrid has its own file: `data-grid.md`.
4
+
5
+ ## Tabs
6
+
7
+ ```tsx
8
+ <Tabs defaultValue="overview">
9
+ <TabList aria-label="Project">
10
+ <Tab value="overview">Overview</Tab>
11
+ <Tab value="activity" badge={3}>Activity</Tab>
12
+ <Tab value="billing" disabled>Billing</Tab>
13
+ </TabList>
14
+ <TabPanel value="overview">…</TabPanel>
15
+ <TabPanel value="activity">…</TabPanel>
16
+ </Tabs>
17
+ ```
18
+
19
+ **Tabs:** `value`/`defaultValue`/`onValueChange`, `orientation` (`horizontal`
20
+ `vertical`), `activation` (`automatic` `manual`), `variant` (`line` `pills`
21
+ `enclosed`), `size`, `keepMounted`.
22
+ **Tab:** `value` (required), `disabled`, `icon`, `badge`.
23
+ **TabPanel:** `value` (required), `keepMounted`.
24
+
25
+ `TabList`, `Tab` and `TabPanel` throw outside `<Tabs>`. Arrow keys move between
26
+ tabs (swapped in RTL), Home/End jump, disabled tabs are skipped. `keepMounted`
27
+ preserves hidden panels' state using React's `<Activity>`.
28
+
29
+ ## Accordion
30
+
31
+ Built on native `<details>`/`<summary>`.
32
+
33
+ ```tsx
34
+ <Accordion defaultValue={["shipping"]} items={[
35
+ { value: "shipping", title: "Shipping", content: <p>Two to five days.</p> },
36
+ { value: "returns", title: "Returns", content: <p>Thirty days.</p> },
37
+ ]} />
38
+
39
+ <Accordion multiple variant="contained">
40
+ <AccordionItem value="one" title="Details" meta={<Badge count={3} />}>…</AccordionItem>
41
+ </Accordion>
42
+ ```
43
+
44
+ **Accordion:** `value`/`defaultValue`/`onValueChange` (string arrays), `items`
45
+ **or** `<AccordionItem>` children, `multiple`, `collapsible`, `variant`
46
+ (`separated` `contained` `plain`), `size`, `iconPosition`, `classNames`, `ref`.
47
+ **AccordionItem:** `value` (required), `title`, `description`, `children`,
48
+ `meta`, `icon`, `disabled`, `classNames` (`root` `header` `title` `description`
49
+ `icon` `content` `body`), `ref` (the `<details>`).
50
+
51
+ `AccordionItem` throws outside `<Accordion>`.
52
+
53
+ ## Card and Stat
54
+
55
+ ```tsx
56
+ <Card>
57
+ <CardHeader title={<h3>Revenue</h3>} actions={<Menu trigger={…}>…</Menu>} />
58
+ <CardBody>
59
+ <Stat label="This month" value="£48,120"
60
+ trend={{ direction: trendDirection(12.4), label: "12.4%", description: "vs last month" }} />
61
+ </CardBody>
62
+ </Card>
63
+ ```
64
+
65
+ **Card:** `variant` (`outlined` `elevated` `plain`), `padding` (`none` `sm` `md`
66
+ `lg`), `href`/`target`/`rel` (renders an `<a>`), `interactive`, `className`,
67
+ `classNames` (`root` `header` `title` `description` `actions` `body` `footer`),
68
+ `style`.
69
+ **CardHeader:** `title`, `description`, `actions`. Pass your own heading element
70
+ as `title` so the page owns the outline level.
71
+ **Stat:** `label` and `value` (both required), `trend`, `help`, `icon`,
72
+ `loading`, plus styling props.
73
+
74
+ With `href` the card is an `<a>` — keep other links and buttons out of it; use
75
+ `interactive` plus your own handler instead.
76
+
77
+ ## Alert
78
+
79
+ ```tsx
80
+ <Alert variant="warning" title="Your trial ends in 3 days">
81
+ After that the workspace becomes read-only.
82
+ </Alert>
83
+ <Alert variant="danger" title="We could not save your changes" onDismiss={hide} />
84
+ ```
85
+
86
+ `variant` (`info` `success` `warning` `danger` `neutral`), `size` (`sm` `md`),
87
+ `title`, `description` or `children`, `icon` (a node, or `false`), `actions`,
88
+ `onDismiss`, `classNames` (`root` `icon` `content` `title` `description`
89
+ `actions` `dismiss`), `localeText`, `ref`, and every `<div>` attribute.
90
+
91
+ Problems get `role="alert"` and interrupt; everything else gets `role="status"`.
92
+ An alert present from first render announces nothing — live regions only speak
93
+ on change.
94
+
95
+ ## Badge and Tag
96
+
97
+ ```tsx
98
+ <Badge variant="success">Active</Badge>
99
+ <Badge variant="danger" appearance="solid" count={128} />
100
+ <Tag onRemove={() => remove("berlin")}>Berlin</Tag>
101
+ ```
102
+
103
+ **Badge:** `children` or `count`, `variant` (`neutral` `accent` `success`
104
+ `warning` `danger` `info`), `appearance` (`soft` `solid` `outline`), `size`
105
+ (`sm` `md`), `max`, `showZero`, `formatValue`, `dot`, `icon`, `classNames`
106
+ (`root` `dot` `icon` `label`), `ref`, and every `<span>` attribute.
107
+ **Tag:** the same variants plus `onRemove`, `label` (names the remove button when
108
+ the children are not a plain string), `disabled`, `classNames` (`root` `icon`
109
+ `label` `remove`), `localeText`.
110
+
111
+ A `count` of zero renders nothing unless `showZero`.
112
+
113
+ ## Avatar and AvatarGroup
114
+
115
+ **Avatar:** `name` (alt text, initials and a stable colour), `src`, `initials`,
116
+ `children`, `size` (`md`, 32px), `shape` (`circle`), `status`, `decorative`,
117
+ `className`, `classNames`, `style`, `localeText`, `ref`, and every `<span>`
118
+ attribute. A failed image falls back to initials.
119
+ **AvatarGroup:** `children` (required), `max` (4, counting the overflow bubble),
120
+ `size`, `shape`, `label` (names the set), plus the usual styling props.
121
+
122
+ ## Progress and CircularProgress
123
+
124
+ ```tsx
125
+ <Progress label="Uploading report.pdf" value={62} showValue />
126
+ <Progress value={null} label="Preparing export" />
127
+ <CircularProgress value={62} size={56} showValue />
128
+ ```
129
+
130
+ **Progress:** `value` (`number | null`), `max`, `label`, `showValue`,
131
+ `valueText`, `variant` (`accent` `success` `warning` `danger`), `size` (`sm`
132
+ `md` `lg`), `classNames` (`root` `header` `label` `value` `track` `bar`),
133
+ `localeText`, `ref`, and every `<div>` attribute.
134
+ **CircularProgress:** the same value props plus `size` (pixels), `thickness`,
135
+ `showValue` or `children` for the middle.
136
+
137
+ `value={null}` is indeterminate: `aria-valuenow` is omitted, so a screen reader
138
+ says "busy". Use a Spinner when there is nothing to measure at all.
139
+
140
+ ## Skeleton and Empty
141
+
142
+ ```tsx
143
+ {loading ? <Skeleton lines={3} label="Loading activity" />
144
+ : items.length === 0 ? <Empty title="No activity yet" description="…" actions={<Button…/>} />
145
+ : <ActivityList items={items} />}
146
+ ```
147
+
148
+ **Skeleton:** `variant` (`text` `circle` `rect`), `lines` (1),
149
+ `lastLineWidth` (60), `width`, `height`, `radius` (number = pixels, string used
150
+ as given), `animation` (`pulse` `wave` `none`), `label`, `className`,
151
+ `classNames` (`root` `line`), `style`. Bars are hidden from assistive tech;
152
+ `label` is what gets announced.
153
+ **Empty:** `title`, `description`, `actions`.
154
+
155
+ ## Spinner
156
+
157
+ ```tsx
158
+ <Spinner />
159
+ <Spinner size="sm" showLabel label="Saving…" />
160
+ <Spinner loading={isPending} delay={250} minDuration={600}><Chart /></Spinner>
161
+ ```
162
+
163
+ `loading` (true; with children, covers them), `size` (`xs` 12 · `sm` 16 · `md`
164
+ 24 · `lg` 32 · `xl` 48, or a pixel number), `variant` (`ring` `dots`), `label`
165
+ ("Loading"), `showLabel`, `delay` (0), `minDuration` (0), `className`,
166
+ `classNames` (`root` `status` `indicator` `label` `content` `overlay`), `style`,
167
+ `localeText`. `useDelayedLoading(loading, { delay, minDuration })` applies the
168
+ same timing to any loading UI.
169
+
170
+ ## Breadcrumb
171
+
172
+ ```tsx
173
+ <Breadcrumb
174
+ items={[{ label: "Home", href: "/" }, { label: "Invoices", href: "/invoices" },
175
+ { label: "INV-2026-0042" }]}
176
+ maxItems={4}
177
+ renderLink={(props) => <Link {...props} />}
178
+ structuredData={{ baseUrl: "https://app.example.com" }} />
179
+ ```
180
+
181
+ `items` (required; `{ label, href?, icon?, name?, onClick? }`, top level first,
182
+ last is the current page), `separator`, `maxItems`, `itemsBeforeCollapse` (1),
183
+ `itemsAfterCollapse` (1), `renderLink`, `structuredData`, `size` (`md`),
184
+ `className`, `classNames` (`root` `list` `item` `link` `current` `separator`
185
+ `ellipsis`), `localeText`. Use `renderLink` for a router link — no `asChild`.
@@ -0,0 +1,117 @@
1
+ # DataGrid
2
+
3
+ A virtualized grid. Import `DataGrid` and `createColumnHelper` from
4
+ `component-lib/data-grid` and the stylesheet once.
5
+
6
+ ```tsx
7
+ import { createColumnHelper, DataGrid } from "component-lib/data-grid";
8
+ import "component-lib/data-grid/styles.css";
9
+
10
+ interface Employee { id: number; name: string; salary: number; active: boolean }
11
+
12
+ const col = createColumnHelper<Employee>();
13
+
14
+ // Define columns at module level, or memoize them.
15
+ const columns = [
16
+ col.field("id", { header: "ID", type: "number", width: 80, pin: "left" }),
17
+ col.field("name", { width: 200 }),
18
+ col.field("salary", { type: "number", format: (v) => `$${v.toLocaleString()}` }),
19
+ col.field("active", { type: "boolean" }),
20
+ ];
21
+
22
+ export function Employees({ data }: { data: Employee[] }) {
23
+ return <DataGrid data={data} columns={columns} getRowId="id" enableRowSelection height={600} />;
24
+ }
25
+ ```
26
+
27
+ **Keep `data`, `columns` and `getRowId` stable.** The grid memoizes on their
28
+ identity. Define columns at module level or in `useMemo`, and pass `getRowId` as
29
+ a property name (`getRowId="id"`). With React Compiler on, inline values are
30
+ memoized for you.
31
+
32
+ ## Props
33
+
34
+ From `GridOptions<T>`: `data` (required), `columns` (required), `getRowId`,
35
+ `mode` (`client` | `server`), `rowCount` (server mode), `state`, `initialState`,
36
+ `onStateChange`, `onQueryChange`, `enableRowSelection`, `enableMultiSort`,
37
+ `exportFileName`, and the feature toggles.
38
+
39
+ From `DataGridProps<T>`: `ref` (`Ref<GridApi<T>>`), `height` (`number | string`,
40
+ 520; ignored with `autoHeight`), `autoHeight`, `rowHeight` (defaults to the
41
+ density: compact 32, standard 40, comfortable 52), `headerHeight`, `loading`,
42
+ `toolbar` (`boolean | ToolbarOptions` — search, columns, export, density),
43
+ `pageSizeOptions` (`[25, 50, 100, 250]`), `emptyState`, `getRowClassName`,
44
+ `locale` (BCP 47), `localeText`, `className`, `classNames`, `style`,
45
+ `aria-label`.
46
+
47
+ Slots: `root`, `toolbar`, `viewport`, `header`, `headerCell`, `row`, `cell`,
48
+ `pagination`.
49
+
50
+ ## Columns
51
+
52
+ | Option | Purpose |
53
+ | --- | --- |
54
+ | `field` / `accessor` / `id` | Where the value comes from. Display-only columns need just `id` and `cell`. |
55
+ | `header`, `width`, `minWidth`, `maxWidth`, `align` | Presentation. |
56
+ | `type` | `string` (default), `number`, `date`, `boolean`. Drives filter operators, sorting, alignment, editors, Excel cell types. |
57
+ | `options` | `{ label, value }[]` for enum columns: "is any of" filter, select editor, label display. |
58
+ | `format(value, row)` | Display text. Also used by search, CSV, PDF and copy. |
59
+ | `cell` | Custom renderer. |
60
+ | `editable`, `validate` | Inline editing with validation. |
61
+ | `pin` | `"left"` or `"right"`. |
62
+ | `exportable` | `false` keeps the column out of exports and copy. |
63
+
64
+ `createColumnHelper<T>()` infers the value type for `cell`, `format`, `validate`
65
+ and `sortFn`. Plain `ColumnDef<T>[]` objects also work.
66
+
67
+ ## Imperative API
68
+
69
+ ```tsx
70
+ const api = useRef<GridApi<Employee>>(null);
71
+ <DataGrid ref={api} … />
72
+
73
+ api.current?.setFilter("status", { operator: "in", value: ["active"] });
74
+ api.current?.exportExcel({ scope: "selected", fileName: "people" });
75
+ api.current?.focusCell(0, "name");
76
+ ```
77
+
78
+ Methods: `getState`, `setState`, `toggleSort`, `setSorting`, `setFilter`,
79
+ `clearFilters`, `setGlobalFilter`, `setPageIndex`, `setPageSize`,
80
+ `toggleRowSelected`, `toggleAllRowsSelected`, `clearSelection`,
81
+ `getSelectedRowIds`, `getSelectedRows`, `setColumnVisibility`, `setColumnWidth`,
82
+ `pinColumn`, `moveColumn`, `resetColumns`, `scrollToRow`, `focusCell`,
83
+ `startEditing`, `cancelEditing`, `getRows`, `exportCsv`, `exportExcel`,
84
+ `exportPdf`, `print`, `copyToClipboard`.
85
+
86
+ ## Export
87
+
88
+ Export code is split into chunks loaded on first use. Scopes: `filtered`
89
+ (default), `all`, `selected`, `page`, or pass `rows`.
90
+
91
+ - `exportCsv` — UTF-8 with BOM; cells starting with `= + - @` get a `'` prefix
92
+ against formula injection.
93
+ - `exportExcel` — real `.xlsx`: typed numbers, booleans and dates, bold frozen
94
+ header, auto-filter, column widths.
95
+ - `exportPdf` — writes a real `.pdf` and downloads it: paper size, orientation,
96
+ repeated header row, page numbers, your own header/footer bands.
97
+ - `print` — opens the browser's print dialog instead, for paper or for text the
98
+ PDF's Latin-1 fonts cannot encode.
99
+
100
+ ```tsx
101
+ api.current?.exportPdf({
102
+ title: "Q3 headcount",
103
+ orientation: "landscape",
104
+ paperSize: "A4", // A3 A4 A5 letter legal
105
+ footer: { left: "Confidential", right: "Page {page} of {pages}" },
106
+ });
107
+ ```
108
+
109
+ Band tokens: `{title}`, `{page}`, `{pages}`, `{date}`, `{time}`. Also `margin`
110
+ (points), `fontSize`, `header`, and `theme` (`text` `muted` `border` `headerBg`
111
+ `headerText` `stripe`). PDF text is WinAnsi/Latin-1; other scripts become `?` —
112
+ use `print()` for those.
113
+
114
+ ## Server mode
115
+
116
+ Set `mode="server"`, pass the current page as `data` and the total as
117
+ `rowCount`, and fetch in `onQueryChange`.