@cdx-ui/components 0.3.1 → 0.3.3

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 (94) hide show
  1. package/README.md +2 -2
  2. package/coverage.svg +1 -1
  3. package/docs/components/bottom-sheet/content.md +2 -2
  4. package/docs/components/content.md +2 -0
  5. package/docs/components/currency-input/content.md +94 -5
  6. package/docs/components/data-grid/content.md +125 -1
  7. package/docs/components/insight/content.md +644 -0
  8. package/docs/components/insight-stack/content.md +231 -0
  9. package/docs/foundations/styles/tokens/all-tokens/content.md +23 -3
  10. package/docs/llms.txt +4 -0
  11. package/docs/patterns/forms/content.md +255 -0
  12. package/docs/resources/release-notes/0.3.1/content.md +13 -0
  13. package/docs/resources/release-notes/0.3.2/content.md +17 -0
  14. package/docs/resources/release-notes/content.md +2 -0
  15. package/lib/commonjs/components/BottomSheet/index.js +63 -16
  16. package/lib/commonjs/components/BottomSheet/index.js.map +1 -1
  17. package/lib/commonjs/components/CurrencyInput/index.js +35 -10
  18. package/lib/commonjs/components/CurrencyInput/index.js.map +1 -1
  19. package/lib/commonjs/components/CurrencyInput/styles.js +40 -3
  20. package/lib/commonjs/components/CurrencyInput/styles.js.map +1 -1
  21. package/lib/commonjs/components/Insight/index.js +438 -0
  22. package/lib/commonjs/components/Insight/index.js.map +1 -0
  23. package/lib/commonjs/components/Insight/insetRadius.js +35 -0
  24. package/lib/commonjs/components/Insight/insetRadius.js.map +1 -0
  25. package/lib/commonjs/components/Insight/styles.js +186 -0
  26. package/lib/commonjs/components/Insight/styles.js.map +1 -0
  27. package/lib/commonjs/components/InsightStack/index.js +93 -0
  28. package/lib/commonjs/components/InsightStack/index.js.map +1 -0
  29. package/lib/commonjs/components/InsightStack/styles.js +61 -0
  30. package/lib/commonjs/components/InsightStack/styles.js.map +1 -0
  31. package/lib/commonjs/components/index.js +24 -0
  32. package/lib/commonjs/components/index.js.map +1 -1
  33. package/lib/commonjs/figma/CurrencyInput.figma.js +8 -8
  34. package/lib/commonjs/figma/CurrencyInput.figma.js.map +1 -1
  35. package/lib/commonjs/figma/Insight.figma.js +73 -0
  36. package/lib/commonjs/figma/Insight.figma.js.map +1 -0
  37. package/lib/commonjs/figma/InsightStack.figma.js +39 -0
  38. package/lib/commonjs/figma/InsightStack.figma.js.map +1 -0
  39. package/lib/module/components/BottomSheet/index.js +64 -17
  40. package/lib/module/components/BottomSheet/index.js.map +1 -1
  41. package/lib/module/components/CurrencyInput/index.js +37 -12
  42. package/lib/module/components/CurrencyInput/index.js.map +1 -1
  43. package/lib/module/components/CurrencyInput/styles.js +38 -3
  44. package/lib/module/components/CurrencyInput/styles.js.map +1 -1
  45. package/lib/module/components/Insight/index.js +433 -0
  46. package/lib/module/components/Insight/index.js.map +1 -0
  47. package/lib/module/components/Insight/insetRadius.js +30 -0
  48. package/lib/module/components/Insight/insetRadius.js.map +1 -0
  49. package/lib/module/components/Insight/styles.js +183 -0
  50. package/lib/module/components/Insight/styles.js.map +1 -0
  51. package/lib/module/components/InsightStack/index.js +89 -0
  52. package/lib/module/components/InsightStack/index.js.map +1 -0
  53. package/lib/module/components/InsightStack/styles.js +58 -0
  54. package/lib/module/components/InsightStack/styles.js.map +1 -0
  55. package/lib/module/components/index.js +2 -0
  56. package/lib/module/components/index.js.map +1 -1
  57. package/lib/module/figma/CurrencyInput.figma.js +8 -8
  58. package/lib/module/figma/CurrencyInput.figma.js.map +1 -1
  59. package/lib/module/figma/Insight.figma.js +67 -0
  60. package/lib/module/figma/Insight.figma.js.map +1 -0
  61. package/lib/module/figma/InsightStack.figma.js +33 -0
  62. package/lib/module/figma/InsightStack.figma.js.map +1 -0
  63. package/lib/typescript/components/BottomSheet/index.d.ts.map +1 -1
  64. package/lib/typescript/components/CurrencyInput/index.d.ts +4 -1
  65. package/lib/typescript/components/CurrencyInput/index.d.ts.map +1 -1
  66. package/lib/typescript/components/CurrencyInput/styles.d.ts +17 -6
  67. package/lib/typescript/components/CurrencyInput/styles.d.ts.map +1 -1
  68. package/lib/typescript/components/Insight/index.d.ts +87 -0
  69. package/lib/typescript/components/Insight/index.d.ts.map +1 -0
  70. package/lib/typescript/components/Insight/insetRadius.d.ts +8 -0
  71. package/lib/typescript/components/Insight/insetRadius.d.ts.map +1 -0
  72. package/lib/typescript/components/Insight/styles.d.ts +33 -0
  73. package/lib/typescript/components/Insight/styles.d.ts.map +1 -0
  74. package/lib/typescript/components/InsightStack/index.d.ts +21 -0
  75. package/lib/typescript/components/InsightStack/index.d.ts.map +1 -0
  76. package/lib/typescript/components/InsightStack/styles.d.ts +11 -0
  77. package/lib/typescript/components/InsightStack/styles.d.ts.map +1 -0
  78. package/lib/typescript/components/index.d.ts +2 -0
  79. package/lib/typescript/components/index.d.ts.map +1 -1
  80. package/lib/typescript/figma/CurrencyInput.figma.d.ts.map +1 -1
  81. package/lib/typescript/figma/Insight.figma.d.ts +8 -0
  82. package/lib/typescript/figma/Insight.figma.d.ts.map +1 -0
  83. package/lib/typescript/figma/InsightStack.figma.d.ts +8 -0
  84. package/lib/typescript/figma/InsightStack.figma.d.ts.map +1 -0
  85. package/package.json +5 -5
  86. package/src/components/BottomSheet/index.tsx +88 -13
  87. package/src/components/CurrencyInput/index.tsx +36 -12
  88. package/src/components/CurrencyInput/styles.ts +51 -4
  89. package/src/components/Insight/index.tsx +531 -0
  90. package/src/components/Insight/insetRadius.ts +30 -0
  91. package/src/components/Insight/styles.ts +193 -0
  92. package/src/components/InsightStack/index.tsx +86 -0
  93. package/src/components/InsightStack/styles.ts +61 -0
  94. package/src/components/index.ts +2 -0
package/README.md CHANGED
@@ -48,7 +48,7 @@ The [component reference](https://design.candescent.com/docs/components) is the
48
48
 
49
49
  The package also ships that reference as markdown in `docs/`, matched to the installed version; coding agents should start at `AGENTS.md` in the package root.
50
50
 
51
- > `Box` is exported but deprecated — use React Native's `View` directly.
51
+ > Use [`Box`](https://design.candescent.com/docs/components/box) for layout containers. The `View` re-export below is deprecated.
52
52
 
53
53
  ### Providers
54
54
 
@@ -91,7 +91,7 @@ For convenience, the package re-exports several core RN components and hooks wit
91
91
 
92
92
  | Export | Source |
93
93
  | ---------------------- | -------------------------------- |
94
- | `View` | `react-native` |
94
+ | `View` (deprecated) | `react-native` |
95
95
  | `ScrollView` | `react-native` |
96
96
  | `KeyboardAvoidingView` | `react-native` |
97
97
  | `SafeAreaView` | `react-native-safe-area-context` |
package/coverage.svg CHANGED
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 92.7%"><title>coverage: 92.7%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">92.7%</text><text x="835" y="150" fill-opacity=".3" textLength="370">92.7%</text></g><text x="835" y="140" textLength="370">92.7%</text></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 92.9%"><title>coverage: 92.9%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">92.9%</text><text x="835" y="150" fill-opacity=".3" textLength="370">92.9%</text></g><text x="835" y="140" textLength="370">92.9%</text></g></g></svg>
@@ -310,11 +310,11 @@ Required once at the app root. Wraps the tree to enable modal presentation.
310
310
 
311
311
  ### BottomSheet.ScrollView
312
312
 
313
- Scrollable content wrapper for sheets with long content. Enables footer margin adjustment by default.
313
+ Scrollable content wrapper for sheets with long content. Enables footer margin adjustment by default when the sheet has a footer.
314
314
 
315
315
  ### BottomSheet.View
316
316
 
317
- Non-scrollable content wrapper for fit-to-content sheets. Enables footer margin adjustment by default.
317
+ Non-scrollable content wrapper for fit-to-content sheets. Enables footer margin adjustment by default when the sheet has a footer.
318
318
 
319
319
  ## Hook
320
320
 
@@ -82,6 +82,8 @@
82
82
  - [Box](https://design.candescent.com/docs/components/box/content.md) — The layout container for building screens, with semantic web elements through role and an asChild escape hatch.
83
83
  - [Card](https://design.candescent.com/docs/components/card/content.md) — A composable surface for grouping related content, with optional brand stripe, header, content, and footer regions.
84
84
  - [Drawer](https://design.candescent.com/docs/components/drawer/content.md) — A panel attached to an edge of the viewport or container, with modal and permanent presentations.
85
+ - [Insight](https://design.candescent.com/docs/components/insight/content.md) — A bordered card for a personalised, often AI-generated, insight, shown as a pressable summary or a detail card.
86
+ - [Insight Stack](https://design.candescent.com/docs/components/insight-stack/content.md) — Draws decorative cards behind a summary Insight to show that more insights are waiting.
85
87
  - [Scoped Theme](https://design.candescent.com/docs/components/scoped-theme/content.md) — Forces a specific theme on a component subtree, regardless of the application's global theme.
86
88
  - [Scroll Shadow](https://design.candescent.com/docs/components/scroll-shadow/content.md) — Fades the edges of a scroll view so overflowing content reads as scrollable on iOS, Android, and web.
87
89
  - [Stack](https://design.candescent.com/docs/components/stack/content.md) — Horizontal and vertical layout primitives with token-based spacing and optional reversed order.
@@ -23,9 +23,50 @@ export default function CurrencyInputBasicDemo() {
23
23
  }
24
24
  ```
25
25
 
26
+ ## Variants
27
+
28
+ Use the default `ghost` variant for a large, centered amount that is the focus of the screen. Use `outline` when the amount sits in a form next to other fields: it renders the same bordered shell as `Input`, with the currency symbol as a separate prefix.
29
+
30
+ ```tsx
31
+ import { useState } from 'react';
32
+ import { Box, CurrencyInput, Text, type CurrencyInputProps } from '@cdx-ui/components';
33
+
34
+ const variants = ['ghost', 'outline'] as const satisfies readonly NonNullable<
35
+ CurrencyInputProps['variant']
36
+ >[];
37
+
38
+ function VariantRow({ variant }: { variant: NonNullable<CurrencyInputProps['variant']> }) {
39
+ const [amount, setAmount] = useState(4890);
40
+
41
+ return (
42
+ <Box className="gap-2">
43
+ <Text size="sm" color="secondary">
44
+ {variant}
45
+ </Text>
46
+ <CurrencyInput
47
+ accessibilityLabel={`${variant} transfer amount`}
48
+ variant={variant}
49
+ value={amount}
50
+ onChangeValue={setAmount}
51
+ />
52
+ </Box>
53
+ );
54
+ }
55
+
56
+ export default function CurrencyInputVariantsDemo() {
57
+ return (
58
+ <Box className="w-full gap-4">
59
+ {variants.map((variant) => (
60
+ <VariantRow key={variant} variant={variant} />
61
+ ))}
62
+ </Box>
63
+ );
64
+ }
65
+ ```
66
+
26
67
  ## Sizes
27
68
 
28
- Use `size="small"` when the amount appears alongside other form fields.
69
+ Use `size="small"` for a more compact amount. When the amount appears alongside other form fields, use `variant="outline"` instead so it matches them.
29
70
 
30
71
  ```tsx
31
72
  import { useState } from 'react';
@@ -121,7 +162,26 @@ export default function CurrencyInputStatesDemo() {
121
162
 
122
163
  ## With field
123
164
 
124
- Compose with `Field` to provide a visible label, helper text, and required state.
165
+ Compose with `Field` to provide a visible label, helper text, and required state. In a form, use `variant="outline"` with the label and helper aligned to the start, as with `Input`.
166
+
167
+ ```tsx
168
+ import { useState } from 'react';
169
+ import { CurrencyInput, Field } from '@cdx-ui/components';
170
+
171
+ export default function CurrencyInputOutlineWithFieldDemo() {
172
+ const [amount, setAmount] = useState(0);
173
+
174
+ return (
175
+ <Field className="w-full" isRequired>
176
+ <Field.Label>Amount</Field.Label>
177
+ <CurrencyInput isRequired variant="outline" value={amount} onChangeValue={setAmount} />
178
+ <Field.Helper>Daily limit remaining: $1,200</Field.Helper>
179
+ </Field>
180
+ );
181
+ }
182
+ ```
183
+
184
+ For the `ghost` pattern, center the label and helper over the amount.
125
185
 
126
186
  ```tsx
127
187
  import { useState } from 'react';
@@ -150,6 +210,34 @@ import { CurrencyInput, Field } from '@cdx-ui/components';
150
210
 
151
211
  const DAILY_LIMIT = 1200;
152
212
 
213
+ export default function CurrencyInputOutlineWithFieldInvalidDemo() {
214
+ const [amount, setAmount] = useState(1500);
215
+ const isInvalid = amount > DAILY_LIMIT;
216
+
217
+ return (
218
+ <Field className="w-full" isRequired isInvalid={isInvalid}>
219
+ <Field.Label>Amount</Field.Label>
220
+ <CurrencyInput
221
+ isInvalid={isInvalid}
222
+ isRequired
223
+ variant="outline"
224
+ value={amount}
225
+ onChangeValue={setAmount}
226
+ />
227
+ <Field.Error>Amount exceeds your daily limit.</Field.Error>
228
+ </Field>
229
+ );
230
+ }
231
+ ```
232
+
233
+ The same validation in the `ghost` pattern, with a centered label and error.
234
+
235
+ ```tsx
236
+ import { useState } from 'react';
237
+ import { CurrencyInput, Field } from '@cdx-ui/components';
238
+
239
+ const DAILY_LIMIT = 1200;
240
+
153
241
  export default function CurrencyInputWithFieldInvalidDemo() {
154
242
  const [amount, setAmount] = useState(1500);
155
243
  const isInvalid = amount > DAILY_LIMIT;
@@ -181,14 +269,15 @@ Controlled calculator-style monetary entry. The numeric `value` is expressed in
181
269
  | `className?` | `string` | — | — |
182
270
  | `value?` | `number` | — | Current amount in major currency units, not cents. Renders a formatted zero when omitted or null. |
183
271
  | `style?` | `StyleProp<ViewStyle>` | — | — |
184
- | `size?` | `'default' \| 'small' \| null` | — | — |
185
272
  | `onChangeValue?` | `((value: number) => void)` | — | Called with the parsed amount in major currency units, not cents, when the user edits the field. |
186
- | `currencySymbol?` | `string` | — | Currency symbol rendered as an inline prefix. Defaults to `"$"`. |
273
+ | `currencySymbol?` | `string` | — | Currency symbol shown before the amount. Defaults to `"$"`. Part of the field text in `ghost`; a separate, non-editable prefix in `outline`. |
187
274
  | `isInvalid?` | `boolean` | — | If true, the input will indicate an error. |
188
275
  | `isDisabled?` | `boolean` | — | If true, the input will be disabled. |
189
276
  | `isRequired?` | `boolean` | — | Sets `aria-required="true"` on web when passed in form control. |
190
277
  | `isReadOnly?` | `boolean` | — | If true, prevents the value of the input from being edited. |
191
278
  | `isFullWidth?` | `boolean` | — | If true, the input element will span the full width of its parent. |
279
+ | `variant?` | `'ghost' \| 'outline'` | `'ghost'` | Visual style. `ghost` is the large, centered, borderless amount entry; `outline` renders the bordered `Input` shell with a leading currency-symbol prefix, for amounts that sit alongside other form fields. |
280
+ | `size?` | `'default' \| 'small'` | `'default'` | Height, padding, and text size. |
192
281
 
193
282
  Supports `accessibilityLabel`, `accessibilityHint`, `testID`, `onFocus`, `onBlur`, and `returnKeyType` from [`TextInputProps`](https://reactnative.dev/docs/textinput#props). `style` applies to the root container, and the forwarded ref targets the underlying `TextInput`.
194
283
 
@@ -201,5 +290,5 @@ Only digits contribute to the amount. Each new digit appends to the cents value,
201
290
  | Item | Behavior |
202
291
  | --------------- | ------------------------------------------------------------------------------------ |
203
292
  | Accessible name | Compose with `Field.Label` or pass `accessibilityLabel` directly. |
204
- | Value | `accessibilityValue.text` contains the fully formatted amount. |
293
+ | Value | `accessibilityValue.text` contains the fully formatted amount, including the symbol. |
205
294
  | Validation | Pair `isInvalid` with `Field.Error` so the error is not communicated by color alone. |
@@ -5,7 +5,16 @@ Resources: [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?
5
5
  `DataGrid` is a **web-only** data-driven grid. Install `@cdx-ui/data-grid` in addition to `@cdx-ui/components`. For fully custom markup use `Table`. For a standalone pager use `TablePagination`.
6
6
 
7
7
  ```tsx
8
- import { Avatar, Chip, IconButton, Menu, ProgressBar, Text, VStack } from '@cdx-ui/components';
8
+ import {
9
+ Avatar,
10
+ Checkbox,
11
+ Chip,
12
+ IconButton,
13
+ Menu,
14
+ ProgressBar,
15
+ Text,
16
+ VStack,
17
+ } from '@cdx-ui/components';
9
18
  import { DataGrid } from '@cdx-ui/data-grid';
10
19
  ```
11
20
 
@@ -332,6 +341,121 @@ export default function DataGridSelectionDemo() {
332
341
  }
333
342
  ```
334
343
 
344
+ ## Checkbox cells
345
+
346
+ To edit boolean fields in place, render a `Checkbox` from `renderCell`. This is separate from `checkboxSelection`: the checkbox reflects a row field, not row selection. Keep `rows` in state and update the matching row in `onChange` so the grid re-renders with the new value.
347
+
348
+ Give each checkbox an accessible label that names both the column and the row, such as "View — Accounts", so every control keeps a distinct name when reached outside the table context. Set `isDisabled` where an action does not apply, and turn off `sortable` and `filterable` on checkbox columns. Build columns that close over the change handler with `useMemo` so they stay stable between renders.
349
+
350
+ ```tsx
351
+ import { useCallback, useMemo, useState } from 'react';
352
+ import { Checkbox, Text, VStack } from '@cdx-ui/components';
353
+ import { DataGrid, type GridColDef } from '@cdx-ui/data-grid';
354
+ import { permissions, type PermissionAction, type PermissionRow } from './_data';
355
+
356
+ type ToggleField = PermissionAction | 'additional';
357
+
358
+ const actionColumns: { field: PermissionAction; headerName: string }[] = [
359
+ { field: 'view', headerName: 'View' },
360
+ { field: 'create', headerName: 'Create' },
361
+ { field: 'modify', headerName: 'Modify' },
362
+ { field: 'delete', headerName: 'Delete' },
363
+ ];
364
+
365
+ function createColumns(
366
+ onToggle: (id: string, field: ToggleField, checked: boolean) => void,
367
+ ): GridColDef<PermissionRow>[] {
368
+ return [
369
+ {
370
+ field: 'name',
371
+ headerName: 'Permission',
372
+ minWidth: 240,
373
+ flex: 1,
374
+ renderCell: ({ row }) => (
375
+ <VStack>
376
+ <Text numberOfLines={1} weight="medium">
377
+ {row.name}
378
+ </Text>
379
+ <Text numberOfLines={1} color="secondary" size="sm">
380
+ {row.description}
381
+ </Text>
382
+ </VStack>
383
+ ),
384
+ },
385
+ ...actionColumns.map(
386
+ ({ field, headerName }): GridColDef<PermissionRow> => ({
387
+ field,
388
+ headerName,
389
+ width: 96,
390
+ align: 'center',
391
+ headerAlign: 'center',
392
+ sortable: false,
393
+ filterable: false,
394
+ renderCell: ({ row }) => (
395
+ <Checkbox
396
+ value={`${row.id}-${field}`}
397
+ isChecked={row[field] === true}
398
+ isDisabled={row[field] === null}
399
+ onChange={(checked) => onToggle(row.id, field, checked)}
400
+ accessibilityLabel={`${headerName} — ${row.name}`}
401
+ aria-label={`${headerName} — ${row.name}`}
402
+ />
403
+ ),
404
+ }),
405
+ ),
406
+ {
407
+ field: 'additional',
408
+ headerName: 'Additional',
409
+ width: 140,
410
+ sortable: false,
411
+ filterable: false,
412
+ renderCell: ({ row }) =>
413
+ row.additional ? (
414
+ <Checkbox
415
+ value={`${row.id}-additional`}
416
+ isChecked={row.additional.checked}
417
+ onChange={(checked) => onToggle(row.id, 'additional', checked)}
418
+ accessibilityLabel={`${row.additional.label} — ${row.name}`}
419
+ aria-label={`${row.additional.label} — ${row.name}`}
420
+ >
421
+ <Checkbox.Label>{row.additional.label}</Checkbox.Label>
422
+ </Checkbox>
423
+ ) : null,
424
+ },
425
+ ];
426
+ }
427
+
428
+ export default function DataGridCheckboxCellsDemo() {
429
+ const [rows, setRows] = useState(permissions);
430
+
431
+ const handleToggle = useCallback((id: string, field: ToggleField, checked: boolean) => {
432
+ setRows((current) =>
433
+ current.map((row) => {
434
+ if (row.id !== id) {
435
+ return row;
436
+ }
437
+ if (field === 'additional') {
438
+ return row.additional ? { ...row, additional: { ...row.additional, checked } } : row;
439
+ }
440
+ return { ...row, [field]: checked };
441
+ }),
442
+ );
443
+ }, []);
444
+
445
+ const columns = useMemo(() => createColumns(handleToggle), [handleToggle]);
446
+
447
+ return (
448
+ <DataGrid
449
+ rows={rows}
450
+ columns={columns}
451
+ hideFooter
452
+ aria-label="Set permissions"
453
+ className="h-85"
454
+ />
455
+ );
456
+ }
457
+ ```
458
+
335
459
  ## Server data
336
460
 
337
461
  Set `paginationMode="server"` and `sortingMode="server"` to let your data source own those operations. `filterMode` follows `paginationMode` unless you set it, so server pagination also means server filtering. Keep the models controlled, pass the current page in `rows`, and provide the full `rowCount` of matching rows.