@cdx-ui/components 0.3.12 → 0.3.14

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 (56) hide show
  1. package/coverage.svg +1 -1
  2. package/docs/components/alert-dialog/content.md +359 -0
  3. package/docs/components/bar-chart/content.md +108 -2
  4. package/docs/components/data-grid/content.md +551 -5
  5. package/docs/components/dialog/content.md +468 -13
  6. package/docs/components/line-chart/content.md +1 -0
  7. package/docs/components/list-item/content.md +1 -1
  8. package/docs/components/table/content.md +84 -0
  9. package/docs/foundations/expressions/icons/content.md +1 -1
  10. package/docs/llms.txt +2 -0
  11. package/docs/resources/release-notes/0.3.12/content.md +15 -0
  12. package/docs/resources/release-notes/0.3.13/content.md +21 -0
  13. package/docs/resources/release-notes/content.md +2 -0
  14. package/lib/commonjs/components/BarChart/geometry.js +95 -10
  15. package/lib/commonjs/components/BarChart/geometry.js.map +1 -1
  16. package/lib/commonjs/components/BarChart/index.js +8 -6
  17. package/lib/commonjs/components/BarChart/index.js.map +1 -1
  18. package/lib/commonjs/components/CurrencyInput/styles.js +1 -1
  19. package/lib/commonjs/components/ListItem/styles.js +1 -1
  20. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  21. package/lib/commonjs/figma/CurrencyInput.figma.js +4 -4
  22. package/lib/commonjs/figma/CurrencyInput.figma.js.map +1 -1
  23. package/lib/commonjs/figma/ProgressBar.figma.js +8 -5
  24. package/lib/commonjs/figma/ProgressBar.figma.js.map +1 -1
  25. package/lib/commonjs/figma/ProgressCircle.figma.js +8 -2
  26. package/lib/commonjs/figma/ProgressCircle.figma.js.map +1 -1
  27. package/lib/module/components/BarChart/geometry.js +93 -9
  28. package/lib/module/components/BarChart/geometry.js.map +1 -1
  29. package/lib/module/components/BarChart/index.js +9 -7
  30. package/lib/module/components/BarChart/index.js.map +1 -1
  31. package/lib/module/components/CurrencyInput/styles.js +1 -1
  32. package/lib/module/components/ListItem/styles.js +1 -1
  33. package/lib/module/components/ListItem/styles.js.map +1 -1
  34. package/lib/module/figma/CurrencyInput.figma.js +4 -4
  35. package/lib/module/figma/CurrencyInput.figma.js.map +1 -1
  36. package/lib/module/figma/ProgressBar.figma.js +8 -5
  37. package/lib/module/figma/ProgressBar.figma.js.map +1 -1
  38. package/lib/module/figma/ProgressCircle.figma.js +8 -2
  39. package/lib/module/figma/ProgressCircle.figma.js.map +1 -1
  40. package/lib/typescript/components/BarChart/geometry.d.ts +19 -2
  41. package/lib/typescript/components/BarChart/geometry.d.ts.map +1 -1
  42. package/lib/typescript/components/BarChart/index.d.ts +4 -2
  43. package/lib/typescript/components/BarChart/index.d.ts.map +1 -1
  44. package/lib/typescript/components/ListItem/index.d.ts +2 -2
  45. package/lib/typescript/components/chart/types.d.ts +7 -0
  46. package/lib/typescript/components/chart/types.d.ts.map +1 -1
  47. package/lib/typescript/figma/CurrencyInput.figma.d.ts.map +1 -1
  48. package/lib/typescript/figma/ProgressBar.figma.d.ts.map +1 -1
  49. package/lib/typescript/figma/ProgressCircle.figma.d.ts.map +1 -1
  50. package/package.json +5 -5
  51. package/src/components/BarChart/geometry.ts +126 -10
  52. package/src/components/BarChart/index.tsx +24 -16
  53. package/src/components/CurrencyInput/styles.ts +1 -1
  54. package/src/components/ListItem/index.tsx +2 -2
  55. package/src/components/ListItem/styles.ts +1 -1
  56. package/src/components/chart/types.ts +7 -0
@@ -61,6 +61,141 @@ export default function DialogBasicDemo() {
61
61
  }
62
62
  ```
63
63
 
64
+ ## Sizes
65
+
66
+ Set `size` on `Dialog` to cap the popup width: `xs`, `sm`, `md` (default), `lg`, `xl`, or `full`. Every size except `full` takes 90% of the viewport on narrow screens. Match the size to the content: short confirmations fit `xs` or `sm`, while statements and comparisons need `lg` or wider.
67
+
68
+ ```tsx
69
+ import { useState } from 'react';
70
+ import { Button, Dialog, HStack, Table, Text, type DialogRootProps } from '@cdx-ui/components';
71
+
72
+ type DialogSize = NonNullable<DialogRootProps['size']>;
73
+
74
+ interface SizeScenario {
75
+ size: DialogSize;
76
+ title: string;
77
+ description: string;
78
+ rows: { label: string; value: string }[];
79
+ action: string;
80
+ }
81
+
82
+ const scenarios: SizeScenario[] = [
83
+ {
84
+ size: 'xs',
85
+ title: 'Card locked',
86
+ description: 'Debit card ending in 4821',
87
+ rows: [{ label: 'Status', value: 'Locked' }],
88
+ action: 'Done',
89
+ },
90
+ {
91
+ size: 'sm',
92
+ title: 'Turn on Face ID?',
93
+ description: 'Sign in faster without typing your password.',
94
+ rows: [{ label: 'Device', value: 'iPhone 16 Pro' }],
95
+ action: 'Turn on',
96
+ },
97
+ {
98
+ size: 'md',
99
+ title: 'Confirm transfer',
100
+ description: 'From Everyday Checking to High-Yield Savings',
101
+ rows: [
102
+ { label: 'Amount', value: '$500.00' },
103
+ { label: 'Delivery', value: 'Today' },
104
+ ],
105
+ action: 'Transfer $500',
106
+ },
107
+ {
108
+ size: 'lg',
109
+ title: 'October statement',
110
+ description: 'Everyday Checking •••• 6643',
111
+ rows: [
112
+ { label: 'Opening balance', value: '$3,104.22' },
113
+ { label: 'Deposits', value: '+$4,860.00' },
114
+ { label: 'Withdrawals', value: '−$5,546.15' },
115
+ { label: 'Closing balance', value: '$2,418.07' },
116
+ ],
117
+ action: 'Download PDF',
118
+ },
119
+ {
120
+ size: 'xl',
121
+ title: 'Compare savings accounts',
122
+ description: 'Rates are variable and may change after the account is opened.',
123
+ rows: [
124
+ { label: 'High-Yield Savings', value: '4.10% APY' },
125
+ { label: 'Money Market', value: '3.75% APY' },
126
+ { label: '12-month CD', value: '4.50% APY' },
127
+ { label: 'Kids Savings', value: '2.00% APY' },
128
+ ],
129
+ action: 'Open an account',
130
+ },
131
+ {
132
+ size: 'full',
133
+ title: 'Monthly spending',
134
+ description: 'October 1 – October 31',
135
+ rows: [
136
+ { label: 'Housing', value: '$1,850.00' },
137
+ { label: 'Groceries', value: '$642.18' },
138
+ { label: 'Dining', value: '$311.40' },
139
+ { label: 'Transportation', value: '$228.75' },
140
+ { label: 'Subscriptions', value: '$64.97' },
141
+ ],
142
+ action: 'View insights',
143
+ },
144
+ ];
145
+
146
+ function SizeDialog({ scenario }: { scenario: SizeScenario }) {
147
+ const [open, setOpen] = useState(false);
148
+
149
+ return (
150
+ <Dialog isOpen={open} onOpenChange={setOpen} size={scenario.size}>
151
+ <Dialog.Trigger asChild>
152
+ <Button variant="outline">
153
+ <Button.Label>{scenario.size}</Button.Label>
154
+ </Button>
155
+ </Dialog.Trigger>
156
+ <Dialog.Popup>
157
+ <Dialog.Header>
158
+ <Dialog.Title>{scenario.title}</Dialog.Title>
159
+ <Dialog.Description>{scenario.description}</Dialog.Description>
160
+ <Dialog.Close />
161
+ </Dialog.Header>
162
+ <Dialog.Content>
163
+ <Table fullBleed accessibilityLabel={`${scenario.title} details`}>
164
+ <Table.Body>
165
+ {scenario.rows.map((row) => (
166
+ <Table.Row key={row.label}>
167
+ <Table.Cell>
168
+ <Text color="secondary">{row.label}</Text>
169
+ </Table.Cell>
170
+ <Table.Cell align="end">
171
+ <Text weight="semibold">{row.value}</Text>
172
+ </Table.Cell>
173
+ </Table.Row>
174
+ ))}
175
+ </Table.Body>
176
+ </Table>
177
+ </Dialog.Content>
178
+ <Dialog.Footer>
179
+ <Button onPress={() => setOpen(false)}>
180
+ <Button.Label>{scenario.action}</Button.Label>
181
+ </Button>
182
+ </Dialog.Footer>
183
+ </Dialog.Popup>
184
+ </Dialog>
185
+ );
186
+ }
187
+
188
+ export default function DialogSizesDemo() {
189
+ return (
190
+ <HStack space="sm" className="flex-wrap justify-center">
191
+ {scenarios.map((scenario) => (
192
+ <SizeDialog key={scenario.size} scenario={scenario} />
193
+ ))}
194
+ </HStack>
195
+ );
196
+ }
197
+ ```
198
+
64
199
  ## Title severities
65
200
 
66
201
  Set `severity` on `Dialog.Title` to add a status icon and semantic color treatment.
@@ -158,13 +293,335 @@ export default function DialogTitleSeveritiesDemo() {
158
293
  }
159
294
  ```
160
295
 
296
+ ## Custom triggers
297
+
298
+ `Dialog.Trigger` with `asChild` turns any pressable element into a trigger, such as an `IconButton` or a `ListItem`, and one dialog can have several. To open the dialog from somewhere a trigger can't wrap, such as a `Menu.Item`, control it with `isOpen` and `onOpenChange`.
299
+
300
+ ```tsx
301
+ import { useState } from 'react';
302
+ import {
303
+ Avatar,
304
+ Box,
305
+ Button,
306
+ Card,
307
+ Dialog,
308
+ Heading,
309
+ Icon,
310
+ IconButton,
311
+ ListItem,
312
+ Menu,
313
+ Table,
314
+ Text,
315
+ } from '@cdx-ui/components';
316
+ import { ChevronRight, CreditCard, Info, MoreHoriz } from '@cdx-ui/icons';
317
+
318
+ const rewards = [
319
+ { label: 'Dining · 3x points', value: '6,210 pts' },
320
+ { label: 'Travel · 2x points', value: '4,180 pts' },
321
+ { label: 'Everything else · 1x', value: '2,090 pts' },
322
+ ];
323
+
324
+ export default function DialogCustomTriggersDemo() {
325
+ const [open, setOpen] = useState(false);
326
+
327
+ return (
328
+ <Dialog isOpen={open} onOpenChange={setOpen}>
329
+ <Card fullBleed className="w-full max-w-sm">
330
+ <Card.Header>
331
+ <Box className="flex-row items-center gap-2">
332
+ <Heading size="sm">Visa Signature</Heading>
333
+ <Dialog.Trigger asChild>
334
+ <IconButton
335
+ as={Info}
336
+ variant="ghost"
337
+ size="small"
338
+ accessibilityLabel="How rewards are earned"
339
+ />
340
+ </Dialog.Trigger>
341
+ </Box>
342
+ <Menu placement="bottom-end">
343
+ <Menu.Trigger asChild>
344
+ <IconButton
345
+ as={MoreHoriz}
346
+ variant="ghost"
347
+ size="small"
348
+ accessibilityLabel="Visa Signature actions"
349
+ />
350
+ </Menu.Trigger>
351
+ <Menu.Popup>
352
+ <Menu.Content accessibilityLabel="Visa Signature actions">
353
+ <Menu.Item onPress={() => setOpen(true)}>
354
+ <Menu.ItemLabel>Rewards summary</Menu.ItemLabel>
355
+ </Menu.Item>
356
+ <Menu.Item onPress={() => undefined}>
357
+ <Menu.ItemLabel>Lock card</Menu.ItemLabel>
358
+ </Menu.Item>
359
+ </Menu.Content>
360
+ </Menu.Popup>
361
+ </Menu>
362
+ </Card.Header>
363
+ <Card.Content>
364
+ <Dialog.Trigger asChild>
365
+ <ListItem showSeparator={false} accessibilityHint="Opens rewards summary">
366
+ <ListItem.LeadingSlot>
367
+ <Avatar color="brand">
368
+ <Avatar.Icon as={CreditCard} />
369
+ </Avatar>
370
+ </ListItem.LeadingSlot>
371
+ <ListItem.Content>
372
+ <ListItem.Description>Rewards balance</ListItem.Description>
373
+ <ListItem.Title>12,480 points</ListItem.Title>
374
+ </ListItem.Content>
375
+ <ListItem.TrailingSlot>
376
+ <Icon as={ChevronRight} />
377
+ </ListItem.TrailingSlot>
378
+ </ListItem>
379
+ </Dialog.Trigger>
380
+ </Card.Content>
381
+ </Card>
382
+
383
+ <Dialog.Popup>
384
+ <Dialog.Header>
385
+ <Dialog.Title>Rewards summary</Dialog.Title>
386
+ <Dialog.Description>Worth $124.80 as a statement credit.</Dialog.Description>
387
+ <Dialog.Close />
388
+ </Dialog.Header>
389
+ <Dialog.Content>
390
+ <Table fullBleed accessibilityLabel="Points by category">
391
+ <Table.Body>
392
+ {rewards.map((reward) => (
393
+ <Table.Row key={reward.label}>
394
+ <Table.Cell>
395
+ <Text color="secondary">{reward.label}</Text>
396
+ </Table.Cell>
397
+ <Table.Cell align="end">
398
+ <Text weight="semibold">{reward.value}</Text>
399
+ </Table.Cell>
400
+ </Table.Row>
401
+ ))}
402
+ </Table.Body>
403
+ </Table>
404
+ </Dialog.Content>
405
+ <Dialog.Footer>
406
+ <Button variant="outline" onPress={() => setOpen(false)}>
407
+ <Button.Label>Close</Button.Label>
408
+ </Button>
409
+ <Button onPress={() => setOpen(false)}>
410
+ <Button.Label>Redeem points</Button.Label>
411
+ </Button>
412
+ </Dialog.Footer>
413
+ </Dialog.Popup>
414
+ </Dialog>
415
+ );
416
+ }
417
+ ```
418
+
419
+ ## Stacked actions
420
+
421
+ When a dialog offers more than two choices, or the labels are too long to sit side by side, stack the actions in a `Button.Group` with `flexDirection="column"` inside `Dialog.Footer`, and pass `fullWidth` to each `Button`. Give the group `className="flex-1"` so it spans the footer. Order them from most to least recommended.
422
+
423
+ ```tsx
424
+ import { useState } from 'react';
425
+ import { Button, Dialog, Table, Text } from '@cdx-ui/components';
426
+ import { Event, Savings } from '@cdx-ui/icons';
427
+
428
+ export default function DialogStackedActionsDemo() {
429
+ const [open, setOpen] = useState(false);
430
+
431
+ return (
432
+ <Dialog isOpen={open} onOpenChange={setOpen} size="sm">
433
+ <Dialog.Trigger asChild>
434
+ <Button variant="outline" color="warning">
435
+ <Button.Label>Review upcoming payment</Button.Label>
436
+ </Button>
437
+ </Dialog.Trigger>
438
+ <Dialog.Popup>
439
+ <Dialog.Header>
440
+ <Dialog.Title severity="warning">Payment may overdraw checking</Dialog.Title>
441
+ <Dialog.Description>Mortgage payment scheduled for tomorrow</Dialog.Description>
442
+ <Dialog.Close />
443
+ </Dialog.Header>
444
+ <Dialog.Content>
445
+ <Table fullBleed accessibilityLabel="Payment details">
446
+ <Table.Body>
447
+ <Table.Row>
448
+ <Table.Cell>
449
+ <Text color="secondary">Payment amount</Text>
450
+ </Table.Cell>
451
+ <Table.Cell align="end">
452
+ <Text weight="semibold">$1,250.00</Text>
453
+ </Table.Cell>
454
+ </Table.Row>
455
+ <Table.Row>
456
+ <Table.Cell>
457
+ <Text color="secondary">Available balance</Text>
458
+ </Table.Cell>
459
+ <Table.Cell align="end">
460
+ <Text weight="semibold" color="danger">
461
+ $1,084.62
462
+ </Text>
463
+ </Table.Cell>
464
+ </Table.Row>
465
+ </Table.Body>
466
+ </Table>
467
+ </Dialog.Content>
468
+ <Dialog.Footer>
469
+ <Button.Group flexDirection="column" className="flex-1">
470
+ <Button fullWidth onPress={() => setOpen(false)}>
471
+ <Button.Icon as={Savings} position="leading" />
472
+ <Button.Label>Transfer $200 from savings</Button.Label>
473
+ </Button>
474
+ <Button fullWidth variant="outline" onPress={() => setOpen(false)}>
475
+ <Button.Icon as={Event} position="leading" />
476
+ <Button.Label>Reschedule payment</Button.Label>
477
+ </Button>
478
+ <Button fullWidth variant="ghost" onPress={() => setOpen(false)}>
479
+ <Button.Label>Pay anyway</Button.Label>
480
+ </Button>
481
+ </Button.Group>
482
+ </Dialog.Footer>
483
+ </Dialog.Popup>
484
+ </Dialog>
485
+ );
486
+ }
487
+ ```
488
+
489
+ ## Form
490
+
491
+ Wrap fields in `Form` inside `Dialog.Content` for short, focused tasks. Keep the dialog open until validation passes, and show errors with `Field.Error`. Use a `FullScreenDialog` instead when the form needs more than a few fields.
492
+
493
+ ```tsx
494
+ import { useState } from 'react';
495
+ import {
496
+ Button,
497
+ CurrencyInput,
498
+ Dialog,
499
+ Field,
500
+ Form,
501
+ Input,
502
+ Radio,
503
+ Select,
504
+ } from '@cdx-ui/components';
505
+ import { Repeat } from '@cdx-ui/icons';
506
+
507
+ const AVAILABLE_BALANCE = 2418.07;
508
+
509
+ const accounts = [
510
+ { value: 'savings', label: 'High-Yield Savings •••• 2290' },
511
+ { value: 'vacation', label: 'Vacation Fund •••• 7713' },
512
+ { value: 'emergency', label: 'Emergency Fund •••• 0458' },
513
+ ];
514
+
515
+ export default function DialogFormDemo() {
516
+ const [open, setOpen] = useState(false);
517
+ const [destination, setDestination] = useState<string | undefined>();
518
+ const [amount, setAmount] = useState(0);
519
+ const [frequency, setFrequency] = useState('monthly');
520
+ const [submitted, setSubmitted] = useState(false);
521
+
522
+ const destinationInvalid = submitted && !destination;
523
+ const amountInvalid = submitted && (amount <= 0 || amount > AVAILABLE_BALANCE);
524
+
525
+ const handleOpenChange = (next: boolean) => {
526
+ setOpen(next);
527
+ if (!next) {
528
+ setSubmitted(false);
529
+ }
530
+ };
531
+
532
+ const handleSubmit = () => {
533
+ setSubmitted(true);
534
+ if (destination && amount > 0 && amount <= AVAILABLE_BALANCE) {
535
+ handleOpenChange(false);
536
+ }
537
+ };
538
+
539
+ return (
540
+ <Dialog isOpen={open} onOpenChange={handleOpenChange}>
541
+ <Dialog.Trigger asChild>
542
+ <Button>
543
+ <Button.Icon as={Repeat} position="leading" />
544
+ <Button.Label>Set up recurring transfer</Button.Label>
545
+ </Button>
546
+ </Dialog.Trigger>
547
+ <Dialog.Popup>
548
+ <Dialog.Header>
549
+ <Dialog.Title>Recurring transfer</Dialog.Title>
550
+ <Dialog.Description>From Everyday Checking · $2,418.07 available</Dialog.Description>
551
+ <Dialog.Close />
552
+ </Dialog.Header>
553
+ <Dialog.Content>
554
+ <Form>
555
+ <Field isRequired isInvalid={destinationInvalid}>
556
+ <Field.Label>To account</Field.Label>
557
+ <Select items={accounts} value={destination} onValueChange={setDestination}>
558
+ <Select.Trigger>
559
+ <Select.Value placeholder="Select an account" />
560
+ </Select.Trigger>
561
+ <Select.Content>
562
+ {accounts.map((account) => (
563
+ <Select.Item key={account.value} value={account.value}>
564
+ <Select.ItemLabel>{account.label}</Select.ItemLabel>
565
+ </Select.Item>
566
+ ))}
567
+ </Select.Content>
568
+ </Select>
569
+ <Field.Error>Choose where the money goes.</Field.Error>
570
+ </Field>
571
+ <Field isRequired isInvalid={amountInvalid}>
572
+ <Field.Label>Amount</Field.Label>
573
+ <CurrencyInput
574
+ variant="outline"
575
+ value={amount}
576
+ onChangeValue={setAmount}
577
+ isInvalid={amountInvalid}
578
+ />
579
+ <Field.Helper>Each transfer runs on the 1st of the period.</Field.Helper>
580
+ <Field.Error>Enter an amount up to $2,418.07.</Field.Error>
581
+ </Field>
582
+ <Field>
583
+ <Field.Label>Frequency</Field.Label>
584
+ <Radio.Group value={frequency} onChange={setFrequency} direction="row">
585
+ <Radio value="weekly">
586
+ <Radio.Label>Weekly</Radio.Label>
587
+ </Radio>
588
+ <Radio value="monthly">
589
+ <Radio.Label>Monthly</Radio.Label>
590
+ </Radio>
591
+ <Radio value="quarterly">
592
+ <Radio.Label>Quarterly</Radio.Label>
593
+ </Radio>
594
+ </Radio.Group>
595
+ </Field>
596
+ <Field>
597
+ <Field.Label>Memo (optional)</Field.Label>
598
+ <Input>
599
+ <Input.Field placeholder="e.g. Summer trip" />
600
+ </Input>
601
+ </Field>
602
+ </Form>
603
+ </Dialog.Content>
604
+ <Dialog.Footer>
605
+ <Button variant="outline" onPress={() => handleOpenChange(false)}>
606
+ <Button.Label>Cancel</Button.Label>
607
+ </Button>
608
+ <Button onPress={handleSubmit}>
609
+ <Button.Label>Schedule transfer</Button.Label>
610
+ </Button>
611
+ </Dialog.Footer>
612
+ </Dialog.Popup>
613
+ </Dialog>
614
+ );
615
+ }
616
+ ```
617
+
161
618
  ## Scrollable content
162
619
 
163
- `Dialog.Content` is not scrollable by default. Wrap long content in a `ScrollView` so the header and footer remain visible. On native, set `keyboardShouldPersistTaps="handled"` so a focused field keeps the keyboard open while the body scrolls.
620
+ `Dialog.Content` is not scrollable by default. Wrap long content in a `ScrollView` so the header and footer remain visible, and wrap that in a `ScrollShadow` with `surface="primary"` to fade the edges that still have content. On native, set `keyboardShouldPersistTaps="handled"` so a focused field keeps the keyboard open while the body scrolls.
164
621
 
165
622
  ```tsx
166
623
  import { useState } from 'react';
167
- import { Button, Dialog, ScrollView } from '@cdx-ui/components';
624
+ import { Button, Dialog, ScrollShadow, ScrollView } from '@cdx-ui/components';
168
625
 
169
626
  const terms = [
170
627
  'Authorization. You authorize us to debit the selected funding account and credit the destination account for the amount shown in the transfer review.',
@@ -198,17 +655,15 @@ export default function DialogScrollableContentDemo() {
198
655
  <Dialog.Close />
199
656
  </Dialog.Header>
200
657
  <Dialog.Content>
201
- <ScrollView
202
- className="h-60"
203
- contentContainerClassName="gap-3"
204
- keyboardShouldPersistTaps="handled"
205
- >
206
- {terms.map((term, index) => (
207
- <Dialog.Text key={term}>
208
- {index + 1}. {term}
209
- </Dialog.Text>
210
- ))}
211
- </ScrollView>
658
+ <ScrollShadow className="h-60" surface="primary">
659
+ <ScrollView contentContainerClassName="gap-3" keyboardShouldPersistTaps="handled">
660
+ {terms.map((term, index) => (
661
+ <Dialog.Text key={term}>
662
+ {index + 1}. {term}
663
+ </Dialog.Text>
664
+ ))}
665
+ </ScrollView>
666
+ </ScrollShadow>
212
667
  </Dialog.Content>
213
668
  <Dialog.Footer>
214
669
  <Button variant="outline" onPress={() => setOpen(false)}>
@@ -494,6 +494,7 @@ One entry in `series`: a line that reads one `dataset` column.
494
494
  | `valueFormatter?` | `ChartValueFormatter` | — | Formats tick text on this axis (`location: 'tick'`). On a value axis it also formats series values when the series has no `valueFormatter`. On a line chart's x-axis it formats numeric categories, both the tick (`location: 'tick'`) and the tooltip title (`location: 'tooltip'`). |
495
495
  | `position?` | `'top' \| 'bottom' \| 'left' \| 'right' \| 'none'` | — | Side the axis labels draw on. `'none'` hides them. Each chart accepts the sides it can draw and falls back to its default side for the others. |
496
496
  | `tickLabelInterval?` | `"auto" \| ((value: unknown, index: number) => boolean)` | `'auto'` | Which category labels to draw. A function keeps the label at `index` when it returns `true`. `'auto'` currently draws every label; it does not thin them yet. |
497
+ | `tickLabelMaxLines?` | `number` | `2` | Most lines a category name may wrap onto before it ends in `…`. `1` truncates without wrapping. Read by `BarChart` on its category axis; a horizontal chart also stops at the lines its rows are tall enough to hold. Forge only; MUI X has no equivalent. |
497
498
 
498
499
  ### LineChartMarker
499
500
 
@@ -658,7 +658,7 @@ Group heading row with an optional accent top-border divider and an optional `su
658
658
  | `children` | `ReactNode` | — | Primary section label. String or number children are styled as an uppercase label. |
659
659
  | `asChild?` | `boolean` | — | Renders the consumer's child element in place of the default `View` host. |
660
660
  | `trailing?` | `ReactNode` | — | Optional right-aligned adornment (e.g. section Chip). |
661
- | `variant?` | `'ghost' \| 'subtle' \| 'gradient'` | `'ghost'` | Background treatment. `ghost` has no fill and a bottom border; `subtle` adds a neutral fill (matching `subtle` on Avatar, Chip, and IconButton) and drops the bottom border. `gradient` fades from the primary surface into a brand tint, left to right, and keeps the bottom border. Label typography is the same for all three. |
661
+ | `variant?` | `'ghost' \| 'subtle' \| 'gradient'` | `'ghost'` | Background treatment. `ghost` has no fill and a bottom border; `subtle` adds a light neutral fill (`surface-neutral-tint`) and drops the bottom border. `gradient` fades from the primary surface into a brand tint, left to right, and keeps the bottom border. Label typography is the same for all three. |
662
662
  | `showDivider?` | `boolean` | `true` | Accent top border above the header. |
663
663
 
664
664
  Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
@@ -229,6 +229,90 @@ export default function TableRowActionsDemo() {
229
229
  }
230
230
  ```
231
231
 
232
+ ## Section rows
233
+
234
+ Group rows under section titles, as in a permissions matrix, with one `Table.Body` per section. Start each body with a row holding a single `Table.Head` that has `scope="rowgroup"` and a `colSpan` covering every column, so screen readers announce the section for the rows beneath it. Use `scope="row"` on the first cell of each row and name the section in each checkbox label, such as "Edit Transfers, Payments", so every control stays distinct out of context. On native, `colSpan` merges the cells but `scope` is dropped.
235
+
236
+ For sortable or filterable data with a hierarchy, use [`DataGrid` tree data](https://design.candescent.com/docs/components/data-grid/content.md#tree-data) instead.
237
+
238
+ ```tsx
239
+ import { useState } from 'react';
240
+ import { Checkbox, Table, Text } from '@cdx-ui/components';
241
+
242
+ const actions = ['View', 'Edit', 'Delete'] as const;
243
+ type Action = (typeof actions)[number];
244
+
245
+ const sections = [
246
+ { title: 'Accounts', resources: ['Balances', 'Statements'] },
247
+ { title: 'Payments', resources: ['Transfers', 'Payees'] },
248
+ ] as const;
249
+
250
+ export default function TableSectionRowsDemo() {
251
+ const [granted, setGranted] = useState<ReadonlySet<string>>(
252
+ new Set(['Balances:View', 'Transfers:View', 'Transfers:Edit']),
253
+ );
254
+
255
+ const toggle = (key: string) => {
256
+ setGranted((previous) => {
257
+ const next = new Set(previous);
258
+ if (next.has(key)) {
259
+ next.delete(key);
260
+ } else {
261
+ next.add(key);
262
+ }
263
+ return next;
264
+ });
265
+ };
266
+
267
+ return (
268
+ <Table>
269
+ <Table.Caption>Role permissions</Table.Caption>
270
+ <Table.Header>
271
+ <Table.Row>
272
+ <Table.Head>Resource</Table.Head>
273
+ {actions.map((action) => (
274
+ <Table.Head key={action} align="center">
275
+ {action}
276
+ </Table.Head>
277
+ ))}
278
+ </Table.Row>
279
+ </Table.Header>
280
+ {sections.map((section) => (
281
+ <Table.Body key={section.title}>
282
+ <Table.Row>
283
+ <Table.Head scope="rowgroup" colSpan={actions.length + 1}>
284
+ <Text size="sm" weight="semibold">
285
+ {section.title}
286
+ </Text>
287
+ </Table.Head>
288
+ </Table.Row>
289
+ {section.resources.map((resource) => (
290
+ <Table.Row key={resource}>
291
+ <Table.Head scope="row">{resource}</Table.Head>
292
+ {actions.map((action: Action) => {
293
+ const key = `${resource}:${action}`;
294
+ const label = `${action} ${resource}, ${section.title}`;
295
+ return (
296
+ <Table.Cell key={action} align="center">
297
+ <Checkbox
298
+ value={key}
299
+ isChecked={granted.has(key)}
300
+ onChange={() => toggle(key)}
301
+ accessibilityLabel={label}
302
+ aria-label={label}
303
+ />
304
+ </Table.Cell>
305
+ );
306
+ })}
307
+ </Table.Row>
308
+ ))}
309
+ </Table.Body>
310
+ ))}
311
+ </Table>
312
+ );
313
+ }
314
+ ```
315
+
232
316
  ## In card
233
317
 
234
318
  On mobile, a headerless table can present key-value summaries inside a card. Pair it with a compact visual summary built from layout primitives while chart components are unavailable, and keep the values in the table as the accessible source of detail.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Cross-platform icon library for Forge UI, built on [Material Symbols](https://fonts.google.com/icons) (outlined style, weight 400, optical size 24). Renders via `react-native-svg` and targets iOS, Android, and Web.
4
4
 
5
- 3,994 icons are available, each with outlined and filled variants, plus support for custom
5
+ 3,999 icons are available, each with outlined and filled variants, plus support for custom
6
6
  SVGs. Country flags are a separate set and do not appear in the browser below. Render one with
7
7
  [`CountryFlag`](https://design.candescent.com/docs/components/country-flag/content.md).
8
8
 
package/docs/llms.txt CHANGED
@@ -151,6 +151,8 @@ Links point at each page's `content.md`, which carries the page's prose, demo so
151
151
 
152
152
  ## Release notes
153
153
 
154
+ - [0.3.13](resources/release-notes/0.3.13/content.md): DataGrid adds tree data, detail panels, and column-targeted filters; ListItem and Menu receive visual and focus fixes.
155
+ - [0.3.12](resources/release-notes/0.3.12/content.md): DataGrid adds built-in column types, pinning, type-aware filters, and polarity or delta number cells.
154
156
  - [0.3.11](resources/release-notes/0.3.11/content.md): Read-only Input and CurrencyInput stay non-editable on Android instead of accepting edits again.
155
157
  - [0.3.10](resources/release-notes/0.3.10/content.md): CurrencyInput no longer clips the top of default-size amounts on iOS.
156
158
  - [0.3.9](resources/release-notes/0.3.9/content.md): DateRangePicker bottom sheets accept custom snap points, stack behavior, and fill; DataGrid keeps column headers within their bounds.
@@ -0,0 +1,15 @@
1
+ # 0.3.12 (https://design.candescent.com/docs/resources/release-notes/0.3.12)
2
+
3
+ DataGrid adds built-in column types, pinning, type-aware filters, and polarity or delta number cells.
4
+
5
+ This release expands **DataGrid** with typed columns, pinning, and richer filters, bumps the Uniwind styling layer used across Forge UI, and fixes clamped **[SystemAlert](https://design.candescent.com/docs/components/system-alert/content.md)** descriptions on web.
6
+
7
+ ## What's new
8
+
9
+ **DataGrid** — Set a column `type` to get defaults for display, sorting, and editing instead of wiring everything by hand. Pin columns to the left or right edge of the viewport, use filter operators that match each column’s type, and render financial-style **polarity** and **delta** number cells for signed amounts and period-over-period change. The filter panel closes on outside presses (including Escape) while nested **[DatePicker](https://design.candescent.com/docs/components/date-picker/content.md)** and Select popovers stay usable, pinned-column edge lines extend through the footer on short grids, and overlay focus handling keeps the panel open when a nested control autofocuses on mount ([#958](https://github.com/dfh-candescent/forge-design-system/pull/958)).
10
+
11
+ **Uniwind** — Forge UI packages move to a newer Uniwind release; **[Icon](https://design.candescent.com/docs/components/icon/content.md)** class handling is updated to match. If your app depends on Uniwind directly alongside `@cdx-ui/web` or `@cdx-ui/native`, align your Uniwind version with the one Forge UI pulls in to avoid duplicate or mismatched styling ([#891](https://github.com/dfh-candescent/forge-design-system/pull/891)).
12
+
13
+ ## Bug fixes
14
+
15
+ **[SystemAlert](https://design.candescent.com/docs/components/system-alert/content.md)** — Multi-line descriptions with `numberOfLines` no longer show clipped text in the bottom padding on web ([#939](https://github.com/dfh-candescent/forge-design-system/pull/939)).