division-ds 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/AGENTS.md +1 -1
  2. package/package.json +1 -1
  3. package/public/docs/components/accordion.md +1 -1
  4. package/public/docs/components/ai-label.md +1 -1
  5. package/public/docs/components/alert-dialog.md +1 -1
  6. package/public/docs/components/alert.md +1 -1
  7. package/public/docs/components/approval-request.md +1 -1
  8. package/public/docs/components/auth-form.md +1 -1
  9. package/public/docs/components/avatar-group.md +1 -1
  10. package/public/docs/components/avatar.md +1 -1
  11. package/public/docs/components/badge.md +1 -1
  12. package/public/docs/components/breadcrumbs.md +1 -1
  13. package/public/docs/components/button.md +1 -1
  14. package/public/docs/components/card.md +1 -1
  15. package/public/docs/components/carousel.md +1 -1
  16. package/public/docs/components/cart.md +1 -1
  17. package/public/docs/components/chart-panel.md +1 -1
  18. package/public/docs/components/chat-message.md +1 -1
  19. package/public/docs/components/chat-thread.md +1 -1
  20. package/public/docs/components/checkbox.md +1 -1
  21. package/public/docs/components/checkout-form.md +1 -1
  22. package/public/docs/components/command-palette.md +1 -1
  23. package/public/docs/components/data-table.md +1 -1
  24. package/public/docs/components/date-picker.md +3 -3
  25. package/public/docs/components/date-presets.md +1 -1
  26. package/public/docs/components/date-range-picker.md +3 -3
  27. package/public/docs/components/description-list.md +1 -1
  28. package/public/docs/components/dialog.md +1 -1
  29. package/public/docs/components/divider.md +1 -1
  30. package/public/docs/components/dropdown-menu.md +1 -1
  31. package/public/docs/components/empty-state.md +1 -1
  32. package/public/docs/components/footer.md +1 -1
  33. package/public/docs/components/form-field.md +2 -2
  34. package/public/docs/components/icon-button.md +1 -1
  35. package/public/docs/components/input.md +1 -1
  36. package/public/docs/components/kanban-board.md +1 -1
  37. package/public/docs/components/kbd.md +1 -1
  38. package/public/docs/components/link.md +1 -1
  39. package/public/docs/components/list-item.md +1 -1
  40. package/public/docs/components/loading-animation.md +1 -1
  41. package/public/docs/components/multi-select.md +5 -5
  42. package/public/docs/components/navbar.md +1 -1
  43. package/public/docs/components/newsletter-form.md +1 -1
  44. package/public/docs/components/page-header.md +1 -1
  45. package/public/docs/components/pagination.md +1 -1
  46. package/public/docs/components/product-card.md +1 -1
  47. package/public/docs/components/progress.md +1 -1
  48. package/public/docs/components/prompt-input.md +1 -1
  49. package/public/docs/components/quantity-stepper.md +2 -2
  50. package/public/docs/components/radio.md +1 -1
  51. package/public/docs/components/rating.md +1 -1
  52. package/public/docs/components/score-ring.md +1 -1
  53. package/public/docs/components/search.md +1 -1
  54. package/public/docs/components/section-heading.md +1 -1
  55. package/public/docs/components/segmented-control.md +1 -1
  56. package/public/docs/components/select.md +2 -2
  57. package/public/docs/components/sheet.md +1 -1
  58. package/public/docs/components/sidebar.md +1 -1
  59. package/public/docs/components/skeleton.md +1 -1
  60. package/public/docs/components/slider.md +1 -1
  61. package/public/docs/components/sparkline.md +1 -1
  62. package/public/docs/components/spinner.md +1 -1
  63. package/public/docs/components/stat.md +1 -1
  64. package/public/docs/components/status-tracker.md +1 -1
  65. package/public/docs/components/stepper.md +1 -1
  66. package/public/docs/components/switch.md +1 -1
  67. package/public/docs/components/tabs.md +1 -1
  68. package/public/docs/components/tag.md +1 -1
  69. package/public/docs/components/textarea.md +1 -1
  70. package/public/docs/components/time-field.md +5 -5
  71. package/public/docs/components/timeline.md +1 -1
  72. package/public/docs/components/toast.md +1 -1
  73. package/public/docs/components/tool-call.md +1 -1
  74. package/public/docs/components/tooltip.md +1 -1
  75. package/public/manifest/components.json +171 -80
  76. package/scripts/lib/agent-docs.mjs +1 -1
  77. package/scripts/lib/props-validator.mjs +36 -2
  78. package/division-ds-0.3.0.tgz +0 -0
@@ -118,7 +118,8 @@
118
118
  "caption": "With an explanation and sources",
119
119
  "source": "<div className=\"flex flex-col gap-2 max-w-md\">\n <AiLabel\n label=\"AI summary\"\n explanation=\"Summarised from the three most recent reviews of this replica. It can miss details or get them wrong: verify before you rely on it.\"\n sources={[\n { label: 'Review by Ilse Varga', href: '#review-1' },\n { label: 'Review by Tomas Brandt', href: '#review-2' },\n ]}\n />\n <p className=\"text-sm text-[var(--color-text-primary)]\">\n Reviewers find the gearbox quiet and the hop-up easy to tune; two mention a loose stock.\n </p>\n</div>"
120
120
  }
121
- ]
121
+ ],
122
+ "closed": false
122
123
  },
123
124
  {
124
125
  "name": "Avatar",
@@ -256,7 +257,8 @@
256
257
  "caption": "A row",
257
258
  "source": "const FIELD_TEAM = [\n { name: 'Dana Iverson', role: 'Route Lead' },\n { name: 'Ravi Okonkwo', role: 'Comms' },\n { name: 'Mei Tanaka', role: 'Medic' },\n { name: 'Sana Idrissi', role: 'Logistics' },\n]\n\n<div className=\"flex flex-col gap-3\">\n {FIELD_TEAM.map(member => (\n <div key={member.name} className=\"flex items-center gap-3\">\n <Avatar name={member.name} size=\"md\" />\n <div>\n <p className=\"text-sm\">{member.name}</p>\n <p className=\"text-xs text-[var(--color-text-muted)]\">{member.role}</p>\n </div>\n </div>\n ))}\n</div>"
258
259
  }
259
- ]
260
+ ],
261
+ "closed": false
260
262
  },
261
263
  {
262
264
  "name": "Badge",
@@ -395,7 +397,8 @@
395
397
  "caption": "In a row",
396
398
  "source": "<div className=\"flex flex-col gap-2 w-full max-w-xs\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">Depot Relay</span>\n <Badge tone=\"ok\" emphasis=\"subtle\">\n Nominal\n </Badge>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">North Sensor Array</span>\n <Badge tone=\"warning\" emphasis=\"subtle\">\n Degraded\n </Badge>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">Northgate Beacon</span>\n <Badge tone=\"hostile\" emphasis=\"solid\">\n Offline\n </Badge>\n </div>\n</div>"
397
399
  }
398
- ]
400
+ ],
401
+ "closed": false
399
402
  },
400
403
  {
401
404
  "name": "Button",
@@ -604,7 +607,8 @@
604
607
  "caption": "Focus ring is never clipped",
605
608
  "source": "<div className=\"flex flex-col gap-4\">\n <p className=\"text-sm text-[var(--color-text-secondary)] max-w-md\">\n Press Tab to focus the button below, in either theme. The focus ring (the outline\n that shows which element the keyboard is on) draws a clean rectangle around the whole\n bevelled shape. You'll never see it cut off, because the component draws it on the\n outer wrapper, which isn't clipped, and not on the bevelled button itself.\n </p>\n <Button variant=\"primary\">Dispatch team</Button>\n</div>"
606
609
  }
607
- ]
610
+ ],
611
+ "closed": false
608
612
  },
609
613
  {
610
614
  "name": "Checkbox",
@@ -744,7 +748,8 @@
744
748
  "caption": "Invalid",
745
749
  "source": "<div className=\"flex items-center gap-2\">\n <Checkbox id=\"state-invalid\" invalid />\n <label htmlFor=\"state-invalid\">Confirm waiver on file</label>\n</div>"
746
750
  }
747
- ]
751
+ ],
752
+ "closed": false
748
753
  },
749
754
  {
750
755
  "name": "Divider",
@@ -835,7 +840,8 @@
835
840
  "caption": "Drawing on",
836
841
  "source": "<div className=\"flex flex-col gap-4 w-full max-w-sm\">\n {/* The Button atom, not a bare <button>: a hand-rolled one here had no system focus\n ring at all (the UA default, rgb(16, 16, 16) auto 1px) and no type, so it defaulted\n to submit. */}\n <Button variant=\"secondary\" size=\"sm\" wrapperClassName=\"w-fit\" onClick={() => setReplayKey(k => k + 1)}>\n Replay\n </Button>\n <Divider key={replayKey} className=\"anim-draw-on\" />\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n This animation is switched off completely when someone has asked their device for\n less motion (the prefers-reduced-motion: reduce setting).\n </p>\n</div>"
837
842
  }
838
- ]
843
+ ],
844
+ "closed": false
839
845
  },
840
846
  {
841
847
  "name": "IconButton",
@@ -1000,7 +1006,8 @@
1000
1006
  "caption": "Disabled",
1001
1007
  "source": "<IconButton label=\"Next step\" disabled>\n <ChevronRight aria-hidden=\"true\" />\n</IconButton>"
1002
1008
  }
1003
- ]
1009
+ ],
1010
+ "closed": false
1004
1011
  },
1005
1012
  {
1006
1013
  "name": "Input",
@@ -1197,7 +1204,8 @@
1197
1204
  "caption": "Labelled the manual way",
1198
1205
  "source": "<div className=\"flex flex-col gap-1 text-sm max-w-xs\">\n <label htmlFor=\"input-docs-callsign\">Call sign</label>\n <Input id=\"input-docs-callsign\" aria-describedby=\"input-docs-callsign-help\" />\n <p id=\"input-docs-callsign-help\" className=\"text-xs text-[var(--color-text-secondary)]\">\n Letters, numbers and a hyphen. FormField will set this pattern up for you.\n </p>\n</div>"
1199
1206
  }
1200
- ]
1207
+ ],
1208
+ "closed": false
1201
1209
  },
1202
1210
  {
1203
1211
  "name": "Kbd",
@@ -1273,7 +1281,8 @@
1273
1281
  "caption": "A chord: one cap per key",
1274
1282
  "source": "<span className=\"flex items-center gap-1\">\n <Kbd>Ctrl</Kbd>\n <span className=\"text-[var(--color-text-muted)] text-sm\">+</span>\n <Kbd>K</Kbd>\n</span>\n<span className=\"text-sm text-[var(--color-text-secondary)]\">Open the command palette</span>"
1275
1283
  }
1276
- ]
1284
+ ],
1285
+ "closed": false
1277
1286
  },
1278
1287
  {
1279
1288
  "name": "Link",
@@ -1393,7 +1402,8 @@
1393
1402
  "caption": "External",
1394
1403
  "source": "<Link href=\"https://example.com\" external>\n Supplier\n</Link>"
1395
1404
  }
1396
- ]
1405
+ ],
1406
+ "closed": false
1397
1407
  },
1398
1408
  {
1399
1409
  "name": "LoadingAnimation",
@@ -1490,7 +1500,8 @@
1490
1500
  "caption": "Aperture, three sizes",
1491
1501
  "source": "<LoadingAnimation variant=\"aperture\" size=\"sm\" label=\"Loading field kits\" />\n<LoadingAnimation variant=\"aperture\" size=\"md\" label=\"Loading field kits\" />\n<LoadingAnimation variant=\"aperture\" size=\"lg\" label=\"Loading field kits\" />"
1492
1502
  }
1493
- ]
1503
+ ],
1504
+ "closed": false
1494
1505
  },
1495
1506
  {
1496
1507
  "name": "Progress",
@@ -1610,7 +1621,8 @@
1610
1621
  "caption": "Tones",
1611
1622
  "source": "<div className=\"flex flex-col gap-4 w-full max-w-sm\">\n <Progress label=\"Signal supply run\" value={40} tone=\"signal\" showValue />\n <Progress label=\"BB reserves\" value={82} tone=\"ok\" showValue />\n <Progress label=\"Gas reserves\" value={22} tone=\"warning\" showValue />\n <Progress label=\"Battery reserves\" value={6} tone=\"hostile\" showValue />\n</div>"
1612
1623
  }
1613
- ]
1624
+ ],
1625
+ "closed": false
1614
1626
  },
1615
1627
  {
1616
1628
  "name": "Radio",
@@ -1785,7 +1797,8 @@
1785
1797
  "caption": "Disabled option",
1786
1798
  "source": "const SPEEDS = ['standard', 'priority', 'overnight'] as const\n\n<RadioGroup aria-label=\"Dispatch speed, one option disabled\" defaultValue=\"standard\">\n {SPEEDS.map(speed => (\n <div key={speed} className=\"flex items-center gap-2\">\n <Radio value={speed} id={`disabled-${speed}`} disabled={speed === 'overnight'} />\n <label htmlFor={`disabled-${speed}`} className=\"capitalize\">\n {speed}\n </label>\n </div>\n ))}\n</RadioGroup>"
1787
1799
  }
1788
- ]
1800
+ ],
1801
+ "closed": false
1789
1802
  },
1790
1803
  {
1791
1804
  "name": "ScoreRing",
@@ -1903,7 +1916,8 @@
1903
1916
  "caption": "The band boundaries: 49, 50, 89 and 90",
1904
1917
  "source": "<ScoreRing label=\"Depot A\" value={49} size=\"sm\" />\n<ScoreRing label=\"Depot B\" value={50} size=\"sm\" />\n<ScoreRing label=\"Depot C\" value={89} size=\"sm\" />\n<ScoreRing label=\"Depot D\" value={90} size=\"sm\" />"
1905
1918
  }
1906
- ]
1919
+ ],
1920
+ "closed": false
1907
1921
  },
1908
1922
  {
1909
1923
  "name": "Select",
@@ -1930,7 +1944,7 @@
1930
1944
  {
1931
1945
  "name": "label",
1932
1946
  "type": "string",
1933
- "description": "Required. The trigger’s accessible name, the name a screen reader announces for it."
1947
+ "description": "The trigger’s accessible name, the name a screen reader announces for it. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
1934
1948
  },
1935
1949
  {
1936
1950
  "name": "options",
@@ -2165,6 +2179,10 @@
2165
2179
  "caption": "Beside an Input",
2166
2180
  "source": "const TERRAIN_OPTIONS = [\n { value: 'woodland', label: 'Woodland' },\n { value: 'urban', label: 'Urban' },\n { value: 'desert', label: 'Desert' },\n { value: 'indoor', label: 'Indoor' },\n]\n\n<label className=\"flex flex-col gap-1 text-sm\">\n Call sign\n <Input placeholder=\"Anvil-3\" />\n</label>\n{/* Deliberately NOT a <label>. A <label> associates with a labelable element — input,\n select, textarea, button among them — but Radix's trigger is a <button>, and a\n <label> wrapping a button is inert: it neither names it nor forwards a click. The\n Select's accessible name comes from its required `label` prop, and the visible text\n here matches it exactly (SC 2.5.3, label in name). Wrapping it in a <label> would\n have looked correct and done nothing. */}\n<div className=\"flex flex-col gap-1 text-sm\">\n <span>Terrain</span>\n <Select label=\"Terrain\" options={TERRAIN_OPTIONS} placeholder=\"Choose terrain\" />\n</div>"
2167
2181
  }
2182
+ ],
2183
+ "closed": false,
2184
+ "namedByFormField": [
2185
+ "Select"
2168
2186
  ]
2169
2187
  },
2170
2188
  {
@@ -2301,7 +2319,8 @@
2301
2319
  "caption": "A profile row loading",
2302
2320
  "source": "<SkeletonGroup label=\"Loading the squad roster\" className=\"flex w-full max-w-sm items-center gap-3\">\n <Skeleton shape=\"avatar\" size=\"md\" />\n <div className=\"flex flex-1 flex-col gap-2\">\n <Skeleton shape=\"text\" className=\"w-1/2\" />\n <Skeleton shape=\"text\" className=\"w-1/3\" />\n </div>\n</SkeletonGroup>"
2303
2321
  }
2304
- ]
2322
+ ],
2323
+ "closed": false
2305
2324
  },
2306
2325
  {
2307
2326
  "name": "Slider",
@@ -2477,7 +2496,8 @@
2477
2496
  "caption": "Disabled",
2478
2497
  "source": "<div className=\"w-full max-w-sm\">\n <Slider label=\"Locked ration\" defaultValue={[8]} max={20} disabled />\n</div>"
2479
2498
  }
2480
- ]
2499
+ ],
2500
+ "closed": false
2481
2501
  },
2482
2502
  {
2483
2503
  "name": "Sparkline",
@@ -2617,7 +2637,8 @@
2617
2637
  "caption": "No values, one value, a flat run",
2618
2638
  "source": "<Sparkline values={[]} label=\"No readings yet\" />\n<Sparkline values={[12]} label=\"One reading: 12\" showLast />\n<Sparkline values={[7, 7, 7, 7]} label=\"Flat at 7\" showLast />"
2619
2639
  }
2620
- ]
2640
+ ],
2641
+ "closed": true
2621
2642
  },
2622
2643
  {
2623
2644
  "name": "Spinner",
@@ -2723,7 +2744,8 @@
2723
2744
  "caption": "Decorative, inside a control",
2724
2745
  "source": "<span className=\"inline-flex items-center gap-2 border border-[var(--color-border-hairline-strong)] px-3 py-2\">\n <Spinner aria-hidden=\"true\" />\n <span className=\"text-sm\">Dispatching…</span>\n</span>"
2725
2746
  }
2726
- ]
2747
+ ],
2748
+ "closed": false
2727
2749
  },
2728
2750
  {
2729
2751
  "name": "Switch",
@@ -2874,7 +2896,8 @@
2874
2896
  "caption": "A settings row",
2875
2897
  "source": "<div className=\"flex w-full items-start justify-between gap-4\">\n <div>\n <label htmlFor=\"settings-offline-maps\" className=\"font-medium\">\n Offline maps\n </label>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n Download the current route area before you lose signal.\n </p>\n </div>\n <Switch id=\"settings-offline-maps\" defaultChecked />\n</div>"
2876
2898
  }
2877
- ]
2899
+ ],
2900
+ "closed": false
2878
2901
  },
2879
2902
  {
2880
2903
  "name": "Tag",
@@ -2985,7 +3008,8 @@
2985
3008
  "caption": "Long text",
2986
3009
  "source": "<Tag onDismiss={() => {}}>Stick battery 11.1 V, 1300 mAh, lithium polymer</Tag>"
2987
3010
  }
2988
- ]
3011
+ ],
3012
+ "closed": false
2989
3013
  },
2990
3014
  {
2991
3015
  "name": "Textarea",
@@ -3111,7 +3135,8 @@
3111
3135
  "caption": "With a counter",
3112
3136
  "source": "function CounterExample() {\n const max = 140\n const [value, setValue] = useState('')\n return (\n <label className=\"flex flex-col gap-1 text-sm w-full max-w-xs\">\n Field day summary\n <Textarea\n value={value}\n onChange={e => setValue(e.target.value)}\n maxLength={max}\n aria-describedby=\"textarea-docs-counter\"\n />\n <span id=\"textarea-docs-counter\" className=\"text-xs text-[var(--color-text-secondary)]\">\n {value.length} / {max}\n </span>\n </label>\n )\n}\n\n<CounterExample />"
3113
3137
  }
3114
- ]
3138
+ ],
3139
+ "closed": false
3115
3140
  },
3116
3141
  {
3117
3142
  "name": "Tooltip",
@@ -3215,7 +3240,8 @@
3215
3240
  "caption": "Keyboard",
3216
3241
  "source": "<p className=\"text-sm text-[var(--color-text-secondary)] max-w-md\">\n Press Tab to move to the trigger and the tooltip appears. Press Escape and it goes away,\n without you moving the pointer. That's a rule in WCAG 2.2 (the Web Content Accessibility\n Guidelines, the public rulebook for making websites usable by people with disabilities).\n Its success criterion (SC) 1.4.13 says this kind of content has to stay dismissible (you\n can close it), hoverable (you can move the pointer onto it) and persistent (it stays\n until you do).\n</p>"
3217
3242
  }
3218
- ]
3243
+ ],
3244
+ "closed": true
3219
3245
  },
3220
3246
  {
3221
3247
  "name": "Accordion",
@@ -3396,7 +3422,8 @@
3396
3422
  "caption": "Controlled: the page keeps track of which sections are open",
3397
3423
  "source": "function ControlledExample() {\n const [open, setOpen] = useState<string[]>(['battery'])\n return (\n <div className=\"w-full max-w-lg\">\n <p className=\"mb-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-text-secondary)]\">\n Open: {open.length ? open.join(', ') : 'none'}\n </p>\n <Accordion type=\"multiple\" value={open} onValueChange={setOpen}>\n <AccordionItem value=\"battery\">\n <AccordionTrigger>Battery</AccordionTrigger>\n <AccordionContent>11.1V LiPo, 1450mAh, with a deans connector.</AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"hop-up\">\n <AccordionTrigger>Hop-up</AccordionTrigger>\n <AccordionContent>Rotary adjustment, 60 degree bucking.</AccordionContent>\n </AccordionItem>\n </Accordion>\n </div>\n )\n}\n\n<ControlledExample />"
3398
3424
  }
3399
- ]
3425
+ ],
3426
+ "closed": false
3400
3427
  },
3401
3428
  {
3402
3429
  "name": "Alert",
@@ -3553,7 +3580,8 @@
3553
3580
  "caption": "Live: it appears after a click, and a screen reader announces it straight away",
3554
3581
  "source": "function LiveExample() {\n const [shown, setShown] = useState(false)\n\n return (\n <div className=\"flex flex-col gap-3 w-full\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n wrapperClassName=\"self-start\"\n onClick={() => setShown(true)}\n >\n Trigger a live alert\n </Button>\n {shown ? (\n <Alert live tone=\"hostile\" title=\"Sync failed\" onDismiss={() => setShown(false)}>\n The last manifest sync could not reach the server.\n </Alert>\n ) : null}\n </div>\n )\n}\n\n<LiveExample />"
3555
3582
  }
3556
- ]
3583
+ ],
3584
+ "closed": false
3557
3585
  },
3558
3586
  {
3559
3587
  "name": "ApprovalRequest",
@@ -3707,7 +3735,8 @@
3707
3735
  "caption": "Resolved",
3708
3736
  "source": "<div className=\"flex w-full max-w-xl flex-col gap-3\">\n <ApprovalRequest title=\"Move the Friday pickup to Thursday?\" state=\"approved\" onApprove={() => {}} onReject={() => {}}>\n Pickup at Depot 4, Thursday 08:00 instead of Friday 08:00.\n </ApprovalRequest>\n <ApprovalRequest title=\"Cancel order ORD-1182?\" state=\"rejected\" onApprove={() => {}} onReject={() => {}}>\n Two replicas, already packed.\n </ApprovalRequest>\n</div>"
3709
3737
  }
3710
- ]
3738
+ ],
3739
+ "closed": true
3711
3740
  },
3712
3741
  {
3713
3742
  "name": "AvatarGroup",
@@ -3858,7 +3887,8 @@
3858
3887
  "caption": "In a row, beside the words it summarises",
3859
3888
  "source": "const ALPHA: AvatarGroupPerson[] = [\n { name: 'Ilse Varga', status: 'online' },\n { name: 'Ravi Okonkwo', status: 'away' },\n { name: 'Mei Tanaka', status: 'busy' },\n { name: 'Sana Idrissi', status: 'online' },\n { name: 'Dana Iverson', status: 'offline' },\n { name: 'Lin Moreau', status: 'online' },\n { name: 'Tomas Reyes', status: 'offline' },\n]\n\n<div className=\"flex items-center gap-3\">\n <AvatarGroup people={ALPHA} max={3} size=\"sm\" label=\"Watching this replica\" />\n <p className=\"text-sm text-[var(--color-text-secondary)]\">7 people are watching this replica</p>\n</div>"
3860
3889
  }
3861
- ]
3890
+ ],
3891
+ "closed": false
3862
3892
  },
3863
3893
  {
3864
3894
  "name": "Breadcrumbs",
@@ -3984,7 +4014,8 @@
3984
4014
  "caption": "A level that is not a page",
3985
4015
  "source": "const MID_LEVEL_TRAIL = [\n { label: 'Catalogue', href: '/catalogue' },\n { label: 'Optics' },\n { label: 'Holo sights', href: '/catalogue/optics/holo-sights' },\n]\n\n<Breadcrumbs items={MID_LEVEL_TRAIL} label=\"Breadcrumb, mid-level example\" />"
3986
4016
  }
3987
- ]
4017
+ ],
4018
+ "closed": true
3988
4019
  },
3989
4020
  {
3990
4021
  "name": "Card",
@@ -4172,7 +4203,8 @@
4172
4203
  "caption": "As a section",
4173
4204
  "source": "<div className=\"max-w-sm w-full\">\n <Card as=\"section\">\n <CardHeader>\n <CardTitle level={2}>Totals</CardTitle>\n <CardDescription>This week, all depots.</CardDescription>\n </CardHeader>\n <CardBody>128 kits collected, 6 outstanding.</CardBody>\n </Card>\n</div>"
4174
4205
  }
4175
- ]
4206
+ ],
4207
+ "closed": false
4176
4208
  },
4177
4209
  {
4178
4210
  "name": "ChatMessage",
@@ -4338,7 +4370,8 @@
4338
4370
  "caption": "Feedback on a reply: thumbs up or down, one vote, and an optional detail step after Not helpful",
4339
4371
  "source": "const pretendSend = () => new Promise<void>(resolve => setTimeout(resolve, 800))\n\n<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage\n role=\"assistant\"\n author=\"Assistant\"\n actions={<ResponseFeedback onSubmit={pretendSend} onDetail={() => {}} />}\n >\n Depot 4 holds 1,240 replicas. Eleven are below their reorder point.\n </ChatMessage>\n</div>"
4340
4372
  }
4341
- ]
4373
+ ],
4374
+ "closed": true
4342
4375
  },
4343
4376
  {
4344
4377
  "name": "DatePicker",
@@ -4374,7 +4407,7 @@
4374
4407
  {
4375
4408
  "name": "label",
4376
4409
  "type": "string",
4377
- "description": "Required. Names the field. Inside a FormField, FormField sets it from its own label."
4410
+ "description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
4378
4411
  },
4379
4412
  {
4380
4413
  "name": "value",
@@ -4621,7 +4654,7 @@
4621
4654
  },
4622
4655
  {
4623
4656
  "caption": "In a FormField, with help text",
4624
- "source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Trip date\" help=\"Type it, or open the calendar.\">\n <DatePicker label=\"Trip date\" />\n </FormField>\n</div>"
4657
+ "source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Trip date\" help=\"Type it, or open the calendar.\">\n <DatePicker />\n </FormField>\n</div>"
4625
4658
  },
4626
4659
  {
4627
4660
  "caption": "Controlled: the page holds the value",
@@ -4639,6 +4672,11 @@
4639
4672
  "caption": "The calendar on its own",
4640
4673
  "source": "function InlineCalendar() {\n const [date, setDate] = useState<string | null>(null)\n return (\n <div className=\"flex flex-col gap-2\">\n <Calendar value={date} onSelect={setDate} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">{date ? formatLong(date) : 'No day picked'}</span>\n </div>\n )\n}\n\n<InlineCalendar />"
4641
4674
  }
4675
+ ],
4676
+ "closed": true,
4677
+ "namedByFormField": [
4678
+ "DateField",
4679
+ "DatePicker"
4642
4680
  ]
4643
4681
  },
4644
4682
  {
@@ -4754,7 +4792,8 @@
4754
4792
  "caption": "Your own periods",
4755
4793
  "source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker\n label=\"Season\"\n aside={\n <DatePresets\n label=\"Seasons\"\n presets={[\n { label: 'Winter 2026', range: { start: '2026-01-01', end: '2026-03-31' } },\n { label: 'Spring 2026', range: { start: '2026-04-01', end: '2026-06-30' } },\n { label: 'Summer 2026', range: { start: '2026-07-01', end: '2026-09-30' } },\n ]}\n />\n }\n />\n</div>"
4756
4794
  }
4757
- ]
4795
+ ],
4796
+ "closed": true
4758
4797
  },
4759
4798
  {
4760
4799
  "name": "DateRangePicker",
@@ -4780,7 +4819,7 @@
4780
4819
  {
4781
4820
  "name": "label",
4782
4821
  "type": "string",
4783
- "description": "Required. Names the field. Inside a FormField, FormField sets it."
4822
+ "description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
4784
4823
  },
4785
4824
  {
4786
4825
  "name": "value",
@@ -4971,7 +5010,7 @@
4971
5010
  },
4972
5011
  {
4973
5012
  "caption": "With DatePresets beside the months",
4974
- "source": "<div className=\"w-full max-w-sm\">\n <FormField label=\"Report period\" help=\"Pick a preset, or two days.\">\n <DateRangePicker label=\"Report period\" aside={<DatePresets />} />\n </FormField>\n</div>"
5013
+ "source": "<div className=\"w-full max-w-sm\">\n <FormField label=\"Report period\" help=\"Pick a preset, or two days.\">\n <DateRangePicker aside={<DatePresets />} />\n </FormField>\n</div>"
4975
5014
  },
4976
5015
  {
4977
5016
  "caption": "Controlled: the page holds the range",
@@ -4981,6 +5020,10 @@
4981
5020
  "caption": "Limits: November and December only",
4982
5021
  "source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker label=\"Rental\" min=\"2026-11-01\" max=\"2026-12-30\" defaultValue={{ start: '2026-11-20', end: '2026-11-23' }} />\n</div>"
4983
5022
  }
5023
+ ],
5024
+ "closed": true,
5025
+ "namedByFormField": [
5026
+ "DateRangePicker"
4984
5027
  ]
4985
5028
  },
4986
5029
  {
@@ -5091,7 +5134,8 @@
5091
5134
  "caption": "Three columns, fewer as the container narrows",
5092
5135
  "source": "<div className=\"w-full\">\n <DescriptionList\n columns={3}\n items={[\n { term: 'Order', description: 'ORD-2291' },\n { term: 'Placed', description: '12 Oct 2026' },\n { term: 'Ships', description: '16 Oct 2026' },\n { term: 'Depot', description: 'Cluj, bay 4' },\n { term: 'Carrier', description: 'Regular courier' },\n { term: 'Weight', description: '22.1 kg' },\n ]}\n />\n</div>"
5093
5136
  }
5094
- ]
5137
+ ],
5138
+ "closed": true
5095
5139
  },
5096
5140
  {
5097
5141
  "name": "DropdownMenu",
@@ -5371,7 +5415,8 @@
5371
5415
  "caption": "From an icon button",
5372
5416
  "source": "function IconButtonTriggerExample() {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton label=\"Depot actions\" variant=\"secondary\">\n <MoreHorizontal aria-hidden=\"true\" />\n </IconButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem>View manifest</DropdownMenuItem>\n <DropdownMenuItem>Reassign</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem tone=\"hostile\">Decommission</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n<IconButtonTriggerExample />"
5373
5417
  }
5374
- ]
5418
+ ],
5419
+ "closed": false
5375
5420
  },
5376
5421
  {
5377
5422
  "name": "EmptyState",
@@ -5516,7 +5561,8 @@
5516
5561
  "caption": "Title only",
5517
5562
  "source": "<EmptyState level={4} size=\"sm\" title=\"No returns yet\" />"
5518
5563
  }
5519
- ]
5564
+ ],
5565
+ "closed": false
5520
5566
  },
5521
5567
  {
5522
5568
  "name": "FormField",
@@ -5567,7 +5613,7 @@
5567
5613
  {
5568
5614
  "name": "children",
5569
5615
  "type": "ReactElement",
5570
- "description": "Exactly one control: Input, Textarea, Select or QuantityStepper. A label set on a Select or QuantityStepper child is replaced by the FormField label. An id you give the control is kept (point an ErrorSummary entry at it); without one, FormField makes one up."
5616
+ "description": "Exactly one control: Input, Textarea, Select, MultiSelect, QuantityStepper, DatePicker, DateRangePicker or TimeField. The controls that name themselves (all but Input and Textarea) take the FormField label, so leave their own label out. An id you give the control is kept (point an ErrorSummary entry at it); without one, FormField makes one up."
5571
5617
  },
5572
5618
  {
5573
5619
  "name": "className",
@@ -5679,7 +5725,8 @@
5679
5725
  "caption": "A form with an error summary",
5680
5726
  "source": "const RESERVE_COUNTRIES = [\n { value: 'ro', label: 'Romania' },\n { value: 'ie', label: 'Ireland' },\n { value: 'de', label: 'Germany' },\n]\n\nfunction ReserveExample() {\n const [country, setCountry] = useState('')\n const [magazines, setMagazines] = useState(0)\n const [errors, setErrors] = useState<FormError[]>([])\n const [attempt, setAttempt] = useState(0)\n const summary = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (attempt > 0) summary.current?.focus()\n }, [attempt])\n\n const countryError = errors.find(current => current.fieldId === 'reserve-country')?.message\n const magazinesError = errors.find(current => current.fieldId === 'reserve-magazines')?.message\n\n return (\n <form\n noValidate\n onSubmit={event => {\n event.preventDefault()\n const next: FormError[] = []\n if (!country) next.push({ fieldId: 'reserve-country', message: 'Choose a country.' })\n if (magazines < 4) next.push({ fieldId: 'reserve-magazines', message: 'Order at least 4 spare magazines.' })\n setErrors(next)\n if (next.length > 0) setAttempt(value => value + 1)\n }}\n className=\"flex flex-col gap-4 w-full max-w-sm p-5 bg-[var(--color-surface-base)]\"\n >\n <ErrorSummary ref={summary} errors={errors} headingLevel={4} />\n <FormField label=\"Country\" required error={countryError}>\n <Select\n id=\"reserve-country\"\n label=\"Country\"\n options={RESERVE_COUNTRIES}\n value={country}\n onValueChange={value => {\n setCountry(value)\n if (value) setErrors(current => current.filter(item => item.fieldId !== 'reserve-country'))\n }}\n />\n </FormField>\n <FormField label=\"Spare magazines\" help=\"Four per replica.\" error={magazinesError}>\n <QuantityStepper\n id=\"reserve-magazines\"\n label=\"Spare magazines\"\n max={40}\n value={magazines}\n onValueChange={value => {\n setMagazines(value)\n if (value >= 4) setErrors(current => current.filter(item => item.fieldId !== 'reserve-magazines'))\n }}\n />\n </FormField>\n <Button type=\"submit\">Reserve</Button>\n </form>\n )\n}\n\n<ReserveExample />"
5681
5727
  }
5682
- ]
5728
+ ],
5729
+ "closed": true
5683
5730
  },
5684
5731
  {
5685
5732
  "name": "ListItem",
@@ -5851,7 +5898,8 @@
5851
5898
  "caption": "A standalone row, outside a List",
5852
5899
  "source": "<div className=\"w-full max-w-xl\">\n <ListItem as=\"div\" title=\"Spare magazine, 120 rounds\" description=\"Mid-cap, steel\" meta=\"€14.90\" />\n</div>"
5853
5900
  }
5854
- ]
5901
+ ],
5902
+ "closed": false
5855
5903
  },
5856
5904
  {
5857
5905
  "name": "MultiSelect",
@@ -5878,7 +5926,7 @@
5878
5926
  {
5879
5927
  "name": "label",
5880
5928
  "type": "string",
5881
- "description": "Required. The name screen readers give the text field and the list. FormField replaces it with its own label."
5929
+ "description": "The name screen readers give the text field and the list. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
5882
5930
  },
5883
5931
  {
5884
5932
  "name": "options",
@@ -6034,15 +6082,15 @@
6034
6082
  "examples": [
6035
6083
  {
6036
6084
  "caption": "Default",
6037
- "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\">\n <MultiSelect label=\"Loadout\" options={LOADOUT} placeholder=\"Choose your kit…\" />\n </FormField>\n</div>"
6085
+ "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\">\n <MultiSelect options={LOADOUT} placeholder=\"Choose your kit…\" />\n </FormField>\n</div>"
6038
6086
  },
6039
6087
  {
6040
6088
  "caption": "Controlled: the page holds the chosen options",
6041
- "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\nfunction ControlledExample() {\n const [kit, setKit] = useState<string[]>(['mags', 'goggles', 'gloves'])\n return (\n <div className=\"flex flex-col gap-2 w-full max-w-md\">\n <FormField label=\"Loadout\" help=\"Remove a tag and watch the others close the gap.\">\n <MultiSelect label=\"Loadout\" options={LOADOUT} value={kit} onValueChange={setKit} />\n </FormField>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">value: [{kit.join(', ')}]</span>\n </div>\n )\n}\n\n<ControlledExample />"
6089
+ "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\nfunction ControlledExample() {\n const [kit, setKit] = useState<string[]>(['mags', 'goggles', 'gloves'])\n return (\n <div className=\"flex flex-col gap-2 w-full max-w-md\">\n <FormField label=\"Loadout\" help=\"Remove a tag and watch the others close the gap.\">\n <MultiSelect options={LOADOUT} value={kit} onValueChange={setKit} />\n </FormField>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">value: [{kit.join(', ')}]</span>\n </div>\n )\n}\n\n<ControlledExample />"
6042
6090
  },
6043
6091
  {
6044
6092
  "caption": "Invalid",
6045
- "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\" error=\"Pick eye protection.\">\n <MultiSelect label=\"Loadout\" options={LOADOUT} defaultValue={['mags']} />\n </FormField>\n</div>"
6093
+ "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\" error=\"Pick eye protection.\">\n <MultiSelect options={LOADOUT} defaultValue={['mags']} />\n </FormField>\n</div>"
6046
6094
  },
6047
6095
  {
6048
6096
  "caption": "Sizes",
@@ -6052,6 +6100,10 @@
6052
6100
  "caption": "Disabled",
6053
6101
  "source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <MultiSelect label=\"Locked loadout\" options={LOADOUT} defaultValue={['mags', 'gloves']} disabled />\n</div>"
6054
6102
  }
6103
+ ],
6104
+ "closed": true,
6105
+ "namedByFormField": [
6106
+ "MultiSelect"
6055
6107
  ]
6056
6108
  },
6057
6109
  {
@@ -6192,7 +6244,8 @@
6192
6244
  "caption": "Under a breadcrumb trail",
6193
6245
  "source": "<PageHeader\n level={2}\n breadcrumbs={\n <Breadcrumbs\n items={[\n { label: 'Account', href: '#account' },\n { label: 'Settings', href: '#settings' },\n { label: 'Notifications' },\n ]}\n />\n }\n title=\"Notifications\"\n description=\"What we send, and where.\"\n actions={<Button variant=\"secondary\">Reset to defaults</Button>}\n/>"
6194
6246
  }
6195
- ]
6247
+ ],
6248
+ "closed": true
6196
6249
  },
6197
6250
  {
6198
6251
  "name": "Pagination",
@@ -6415,7 +6468,8 @@
6415
6468
  "caption": "Table footer",
6416
6469
  "source": "function TableFooterExample() {\n const total = 397\n const [page, setPage] = useState(2)\n const [pageSize, setPageSize] = useState(20)\n return (\n <div className=\"w-full\">\n <Pagination\n page={page}\n pageCount={Math.ceil(total / pageSize)}\n onPageChange={setPage}\n totalItems={total}\n pageSize={pageSize}\n onPageSizeChange={size => {\n setPageSize(size)\n setPage(1)\n }}\n showFirstLast\n label=\"Pagination, table footer\"\n />\n </div>\n )\n}\n\n<TableFooterExample />"
6417
6470
  }
6418
- ]
6471
+ ],
6472
+ "closed": true
6419
6473
  },
6420
6474
  {
6421
6475
  "name": "PromptInput",
@@ -6577,7 +6631,8 @@
6577
6631
  "caption": "Disabled",
6578
6632
  "source": "<div className=\"flex w-full max-w-xl flex-col gap-2\">\n <PromptInput label=\"Message the assistant (disabled)\" value=\"\" onValueChange={() => {}} onSubmit={() => {}} disabled />\n <p className=\"font-body text-sm text-[var(--color-text-secondary)]\">The assistant is offline until 14:00.</p>\n</div>"
6579
6633
  }
6580
- ]
6634
+ ],
6635
+ "closed": true
6581
6636
  },
6582
6637
  {
6583
6638
  "name": "QuantityStepper",
@@ -6603,7 +6658,7 @@
6603
6658
  {
6604
6659
  "name": "label",
6605
6660
  "type": "string",
6606
- "description": "Required. Names the field and both buttons (\"Decrease Speed loader quantity\")."
6661
+ "description": "Names the field and both buttons (\"Decrease Speed loader quantity\"). Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
6607
6662
  },
6608
6663
  {
6609
6664
  "name": "value",
@@ -6768,6 +6823,10 @@
6768
6823
  "caption": "In a cart line",
6769
6824
  "source": "function CartLine() {\n const price = 38\n const [qty, setQty] = useState(2)\n return (\n <div className=\"flex items-center gap-4 w-full\">\n <span className=\"flex-1 font-body text-sm text-[var(--color-text-primary)]\">Speed loader</span>\n <QuantityStepper\n label=\"Speed loader quantity\"\n defaultValue={2}\n min={1}\n max={9}\n onValueChange={setQty}\n />\n <span className=\"w-16 text-right font-display font-semibold tabular-nums text-[var(--color-text-primary)]\">\n €{price * qty}\n </span>\n </div>\n )\n}\n\n<CartLine />"
6770
6825
  }
6826
+ ],
6827
+ "closed": true,
6828
+ "namedByFormField": [
6829
+ "QuantityStepper"
6771
6830
  ]
6772
6831
  },
6773
6832
  {
@@ -6938,7 +6997,8 @@
6938
6997
  "caption": "Input",
6939
6998
  "source": "function LiveInput() {\n const [value, setValue] = useState(4)\n return (\n <div className=\"flex items-center gap-3\">\n <RatingInput label=\"Rate the Prism holo sight\" value={value} onValueChange={setValue} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n You rated it {value} {value === 1 ? 'star' : 'stars'}\n </span>\n </div>\n )\n}\n\n<LiveInput />"
6940
6999
  }
6941
- ]
7000
+ ],
7001
+ "closed": false
6942
7002
  },
6943
7003
  {
6944
7004
  "name": "Search",
@@ -7088,7 +7148,8 @@
7088
7148
  "caption": "Sizes",
7089
7149
  "source": "<div className=\"max-w-xs w-full\">\n <Search label=\"Search, small\" placeholder=\"Small\" inputSize=\"sm\" />\n</div>\n<div className=\"max-w-xs w-full\">\n <Search label=\"Search, medium\" placeholder=\"Medium\" inputSize=\"md\" />\n</div>\n<div className=\"max-w-xs w-full\">\n <Search label=\"Search, large\" placeholder=\"Large\" inputSize=\"lg\" />\n</div>"
7090
7150
  }
7091
- ]
7151
+ ],
7152
+ "closed": true
7092
7153
  },
7093
7154
  {
7094
7155
  "name": "SectionHeading",
@@ -7209,7 +7270,8 @@
7209
7270
  "caption": "Naming its section",
7210
7271
  "source": "<section aria-labelledby=\"section-heading-docs-care\">\n <SectionHeading\n level={4}\n id=\"section-heading-docs-care\"\n title=\"Care plans\"\n description=\"The section below is a region named “Care plans”. It takes that name from this heading, which carries the id.\"\n />\n <p className=\"mt-4 font-body text-sm text-[var(--color-text-primary)]\">\n A chrono check, a tune and upgrade fitting, billed monthly.\n </p>\n</section>"
7211
7272
  }
7212
- ]
7273
+ ],
7274
+ "closed": true
7213
7275
  },
7214
7276
  {
7215
7277
  "name": "SegmentedControl",
@@ -7351,7 +7413,8 @@
7351
7413
  "caption": "Small, with icons, lined up with a small Button",
7352
7414
  "source": "function ToolbarExample() {\n const [view, setView] = useState('board')\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <SegmentedControl\n label=\"View\"\n size=\"sm\"\n options={[\n { value: 'board', label: 'Board', icon: <Columns3 /> },\n { value: 'table', label: 'Table', icon: <Table /> },\n ]}\n value={view}\n onValueChange={setView}\n />\n <Button size=\"sm\" variant=\"secondary\">\n Export\n </Button>\n </div>\n )\n}\n\n<ToolbarExample />"
7353
7415
  }
7354
- ]
7416
+ ],
7417
+ "closed": true
7355
7418
  },
7356
7419
  {
7357
7420
  "name": "Stat",
@@ -7464,7 +7527,8 @@
7464
7527
  "caption": "Without a delta",
7465
7528
  "source": "<div className=\"max-w-[10rem] w-full\">\n <Stat label=\"Kits dispatched\" value={18} padTo={3} />\n</div>"
7466
7529
  }
7467
- ]
7530
+ ],
7531
+ "closed": false
7468
7532
  },
7469
7533
  {
7470
7534
  "name": "StatusTracker",
@@ -7629,7 +7693,8 @@
7629
7693
  "caption": "Other status words",
7630
7694
  "source": "<div className=\"w-full max-w-xs\">\n <StatusTracker\n label=\"Door sensor, last 8 hours\"\n statusLabels={{ ok: 'Closed', warning: 'Left open', hostile: 'Forced', neutral: 'Offline' }}\n items={[\n { id: 'h1', status: 'ok', label: '02:00 to 03:00' },\n { id: 'h2', status: 'ok', label: '03:00 to 04:00' },\n { id: 'h3', status: 'warning', label: '04:00 to 05:00, open 9 min' },\n { id: 'h4', status: 'ok', label: '05:00 to 06:00' },\n { id: 'h5', status: 'neutral', label: '06:00 to 07:00' },\n { id: 'h6', status: 'ok', label: '07:00 to 08:00' },\n { id: 'h7', status: 'ok', label: '08:00 to 09:00' },\n { id: 'h8', status: 'ok', label: '09:00 to 10:00' },\n ]}\n />\n</div>"
7631
7695
  }
7632
- ]
7696
+ ],
7697
+ "closed": true
7633
7698
  },
7634
7699
  {
7635
7700
  "name": "Stepper",
@@ -7747,7 +7812,8 @@
7747
7812
  "caption": "Live: move through the steps with Back and Continue",
7748
7813
  "source": "const CHECKOUT_STEPS: Step[] = [\n { label: 'Shipping' },\n { label: 'Payment' },\n { label: 'Review', description: 'Check the manifest' },\n]\n\nfunction LiveStepper() {\n const [current, setCurrent] = useState(0)\n return (\n <div className=\"flex flex-col gap-6 w-full\">\n <Stepper steps={CHECKOUT_STEPS} current={current} label=\"Checkout progress\" />\n <div className=\"flex gap-3\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={current === 0}\n onClick={() => setCurrent(c => Math.max(0, c - 1))}\n >\n Back\n </Button>\n <Button\n size=\"sm\"\n disabled={current >= CHECKOUT_STEPS.length}\n onClick={() => setCurrent(c => Math.min(CHECKOUT_STEPS.length, c + 1))}\n >\n Continue\n </Button>\n </div>\n </div>\n )\n}\n\n<LiveStepper />"
7749
7814
  }
7750
- ]
7815
+ ],
7816
+ "closed": true
7751
7817
  },
7752
7818
  {
7753
7819
  "name": "Tabs",
@@ -7926,7 +7992,8 @@
7926
7992
  "caption": "Controlled: the page keeps track of which tab is open",
7927
7993
  "source": "function ControlledExample() {\n const [value, setValue] = useState('replica')\n\n return (\n <div className=\"max-w-sm w-full\">\n <div className=\"flex gap-2 mb-4\">\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setValue('replica')}>\n Show replica\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setValue('optics')}>\n Show optics\n </Button>\n </div>\n <Tabs value={value} onValueChange={setValue}>\n <TabsList aria-label=\"Kit sections\">\n <TabsTrigger value=\"replica\">Replica</TabsTrigger>\n <TabsTrigger value=\"optics\">Optics</TabsTrigger>\n </TabsList>\n <TabsContent value=\"replica\">Electric carbine replica.</TabsContent>\n <TabsContent value=\"optics\">Tube sight and holo sight.</TabsContent>\n </Tabs>\n </div>\n )\n}\n\n<ControlledExample />"
7928
7994
  }
7929
- ]
7995
+ ],
7996
+ "closed": false
7930
7997
  },
7931
7998
  {
7932
7999
  "name": "TimeField",
@@ -7951,7 +8018,7 @@
7951
8018
  {
7952
8019
  "name": "label",
7953
8020
  "type": "string",
7954
- "description": "Required. Names the field. Inside a FormField, FormField sets it."
8021
+ "description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
7955
8022
  },
7956
8023
  {
7957
8024
  "name": "value",
@@ -7978,7 +8045,7 @@
7978
8045
  "name": "step",
7979
8046
  "type": "number",
7980
8047
  "default": "30",
7981
- "description": "Minutes between the times in the list."
8048
+ "description": "Minutes between the times in the list. It only spaces the list: a typed time between steps (09:07) is kept as typed. Check it yourself if it must fall on a step."
7982
8049
  },
7983
8050
  {
7984
8051
  "name": "min",
@@ -8143,8 +8210,12 @@
8143
8210
  },
8144
8211
  {
8145
8212
  "caption": "With a DatePicker: a date and a time",
8146
- "source": "function DateAndTime() {\n const [date, setDate] = useState('2026-11-14')\n const [time, setTime] = useState('09:30')\n return (\n <div className=\"flex w-full max-w-md flex-col gap-3\">\n <div className=\"grid gap-3 sm:grid-cols-[1fr_9rem]\">\n <FormField label=\"Pickup date\">\n <DatePicker label=\"Pickup date\" value={date} onValueChange={setDate} />\n </FormField>\n <FormField label=\"Pickup time\">\n <TimeField label=\"Pickup time\" value={time} onValueChange={setTime} step={15} min=\"08:00\" max=\"18:00\" />\n </FormField>\n </div>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n {date && time ? `${date}T${time}` : 'Pick a date and a time'}\n </span>\n </div>\n )\n}\n\n<DateAndTime />"
8213
+ "source": "function DateAndTime() {\n const [date, setDate] = useState('2026-11-14')\n const [time, setTime] = useState('09:30')\n return (\n <div className=\"flex w-full max-w-md flex-col gap-3\">\n <div className=\"grid gap-3 sm:grid-cols-[1fr_9rem]\">\n <FormField label=\"Pickup date\">\n <DatePicker value={date} onValueChange={setDate} />\n </FormField>\n <FormField label=\"Pickup time\">\n <TimeField value={time} onValueChange={setTime} step={15} min=\"08:00\" max=\"18:00\" />\n </FormField>\n </div>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n {date && time ? `${date}T${time}` : 'Pick a date and a time'}\n </span>\n </div>\n )\n}\n\n<DateAndTime />"
8147
8214
  }
8215
+ ],
8216
+ "closed": true,
8217
+ "namedByFormField": [
8218
+ "TimeField"
8148
8219
  ]
8149
8220
  },
8150
8221
  {
@@ -8299,7 +8370,8 @@
8299
8370
  "caption": "Without groups",
8300
8371
  "source": "<div className=\"w-full max-w-xl\">\n <Timeline label=\"Work order WO-29\">\n <TimelineItem icon={<Wrench />} title=\"Opened\" time=\"2026-10-09T08:00:00+03:00\" timeLabel=\"Fri 9 Oct\" />\n <TimelineItem\n icon={<Wrench />}\n tone=\"signal\"\n title=\"Assigned to Mara Lind\"\n time=\"2026-10-12T11:20:00+03:00\"\n timeLabel=\"Mon 12 Oct\"\n />\n <TimelineItem\n icon={<CircleCheck />}\n tone=\"ok\"\n title=\"Closed: pallet jack serviced\"\n time=\"2026-10-13T15:45:00+03:00\"\n timeLabel=\"Tue 13 Oct\"\n />\n </Timeline>\n</div>"
8301
8372
  }
8302
- ]
8373
+ ],
8374
+ "closed": true
8303
8375
  },
8304
8376
  {
8305
8377
  "name": "Toast",
@@ -8446,7 +8518,8 @@
8446
8518
  "caption": "With an action",
8447
8519
  "source": "function WithActionExample() {\n const { toast } = useToast()\n\n return (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() =>\n toast({\n title: 'Item removed',\n action: {\n label: 'Undo',\n altText: 'Undo from the manifest history',\n onAction: () => toast({ title: 'Item restored', tone: 'ok' }),\n },\n })\n }\n >\n Remove item\n </Button>\n )\n}\n\n<WithActionExample />"
8448
8520
  }
8449
- ]
8521
+ ],
8522
+ "closed": false
8450
8523
  },
8451
8524
  {
8452
8525
  "name": "ToolCall",
@@ -8577,7 +8650,8 @@
8577
8650
  "caption": "Without a title, details open",
8578
8651
  "source": "<div className=\"flex w-full max-w-xl flex-col gap-3\">\n <ToolCall\n name=\"count_pallets\"\n status=\"done\"\n duration=\"0.3 s\"\n input={'{ \"depot\": 4, \"bay\": 2 }'}\n output=\"14 pallets\"\n defaultOpen\n />\n</div>"
8579
8652
  }
8580
- ]
8653
+ ],
8654
+ "closed": true
8581
8655
  },
8582
8656
  {
8583
8657
  "name": "AlertDialog",
@@ -8798,7 +8872,8 @@
8798
8872
  "caption": "Type the name to confirm, for the costliest actions",
8799
8873
  "source": "const request = (fail = false) =>\n new Promise<void>((resolve, reject) =>\n window.setTimeout(() => (fail ? reject(new Error('The server did not answer.')) : resolve()), 1000)\n )\n\nconst ACCOUNT = 'northfield-supply'\n\nfunction TypeToConfirmExample() {\n const [typed, setTyped] = useState('')\n const matches = typed.trim() === ACCOUNT\n return (\n <AlertDialog onOpenChange={() => setTyped('')}>\n <AlertDialogTrigger asChild>\n <Button variant=\"danger\">Delete account</Button>\n </AlertDialogTrigger>\n <AlertDialogContent\n title=\"Delete the account?\"\n description=\"Its 212 orders, saved kit lists and team access are removed for good.\"\n >\n <FormField label=\"Account name\" help={`Type ${ACCOUNT} to enable Delete account.`}>\n <Input\n value={typed}\n onChange={event => setTyped(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"font-mono\"\n />\n </FormField>\n <AlertDialogFooter>\n <AlertDialogCancel />\n <AlertDialogAction disabled={!matches} loadingLabel=\"Deleting account\" onClick={() => request()}>\n Delete account\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )\n}\n\n<TypeToConfirmExample />"
8800
8874
  }
8801
- ]
8875
+ ],
8876
+ "closed": true
8802
8877
  },
8803
8878
  {
8804
8879
  "name": "AuthForm",
@@ -8952,7 +9027,8 @@
8952
9027
  "caption": "Two-step sign-in with a code",
8953
9028
  "source": "const wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\n<AuthForm mode=\"verify-code\" onSubmit={() => wait(600).then(() => undefined)} headingLevel={4} />"
8954
9029
  }
8955
- ]
9030
+ ],
9031
+ "closed": true
8956
9032
  },
8957
9033
  {
8958
9034
  "name": "Carousel",
@@ -9079,7 +9155,8 @@
9079
9155
  "caption": "Three short items: both buttons do nothing while everything fits",
9080
9156
  "source": "<Carousel label=\"Kestrel lengths\" itemWidth=\"8rem\">\n {['Kestrel K', 'Kestrel C', 'Kestrel L'].map(name => (\n <div\n key={name}\n className=\"flex h-20 items-center justify-center border border-[var(--color-border-hairline-strong)] font-display font-semibold text-[var(--color-text-primary)]\"\n >\n {name}\n </div>\n ))}\n</Carousel>"
9081
9157
  }
9082
- ]
9158
+ ],
9159
+ "closed": true
9083
9160
  },
9084
9161
  {
9085
9162
  "name": "Cart",
@@ -9257,7 +9334,8 @@
9257
9334
  "caption": "Empty",
9258
9335
  "source": "<Cart lines={[]} onQuantityChange={() => {}} onRemove={() => {}} headingLevel={4} title=\"Saved for later\" />"
9259
9336
  }
9260
- ]
9337
+ ],
9338
+ "closed": true
9261
9339
  },
9262
9340
  {
9263
9341
  "name": "ChartPanel",
@@ -9499,7 +9577,8 @@
9499
9577
  "caption": "Radar, two series",
9500
9578
  "source": "type Profile = { measure: string; m4: number; ak: number }\n\nconst PROFILES: Profile[] = [\n { measure: 'Range', m4: 78, ak: 64 },\n { measure: 'Accuracy', m4: 82, ak: 70 },\n { measure: 'Rate of fire', m4: 74, ak: 86 },\n { measure: 'Weight', m4: 58, ak: 44 },\n { measure: 'Reliability', m4: 70, ak: 90 },\n { measure: 'Upgrades', m4: 92, ak: 62 },\n]\n\n<ChartPanel<Profile>\n title=\"Replica profiles\"\n summary=\"The M4 replica leads on range, accuracy and upgrades; the AK replica on rate of fire and reliability.\"\n kind=\"radar\"\n data={PROFILES}\n xKey=\"measure\"\n xLabel=\"Measure\"\n series={[\n { key: 'm4', label: 'M4 replica' },\n { key: 'ak', label: 'AK replica' },\n ]}\n headingLevel={4}\n/>"
9501
9579
  }
9502
- ]
9580
+ ],
9581
+ "closed": true
9503
9582
  },
9504
9583
  {
9505
9584
  "name": "ChatThread",
@@ -9625,7 +9704,8 @@
9625
9704
  "caption": "A live conversation: send anything, watch the reply stream in, press Stop to end it, or scroll up while it's still arriving",
9626
9705
  "source": "type Turn =\n | { id: number; role: 'user'; text: string }\n | { id: number; role: 'assistant'; text: string; tool?: ToolCallStatus; approval?: boolean }\n | { id: number; role: 'system'; text: string }\n\nconst REPLIES = [\n 'Depot 4 holds 1,240 replicas across 38 lines. Two lines are under their reorder point: the K-41 field radio has 6 left against a reorder point of 10, and the trail pack has 3 left against 8. Everything else has at least two weeks of cover at the current rate. The reorder above covers both lines; once it is approved, the supplier confirms within a working day.',\n 'Inbound this week: one delivery from Arad on Thursday morning, 22 pallets, booked into bay 2. Outbound: nine shipments, the largest to Sibiu on Friday. Nothing is waiting on a person except the reorder above.',\n]\n\nconst HISTORY: Turn[] = [\n { id: 1, role: 'user', text: 'Summarise stock at Depot 4.' },\n {\n id: 2,\n role: 'assistant',\n text: 'Depot 4 is in good shape: 1,240 replicas on hand and nothing overdue. Two lines are low, so I have drafted a reorder for you to check.',\n tool: 'done',\n approval: true,\n },\n]\n\nfunction LiveConversation() {\n const [turns, setTurns] = useState<Turn[]>(HISTORY)\n const [draft, setDraft] = useState('')\n const [phase, setPhase] = useState<'idle' | 'tool' | 'streaming'>('idle')\n const [approval, setApproval] = useState<ApprovalState>('pending')\n const [replyIndex, setReplyIndex] = useState(0)\n const nextId = useRef(HISTORY.length + 1)\n const activeId = useRef<number | null>(null)\n const reply = useStreamedText(REPLIES[replyIndex % REPLIES.length], { active: phase === 'streaming' })\n\n const updateActive = (change: (turn: Turn & { role: 'assistant' }) => Turn) =>\n setTurns(list => list.map(turn => (turn.id === activeId.current && turn.role === 'assistant' ? change(turn) : turn)))\n\n const addStopped = () => {\n const id = nextId.current++\n setTurns(list => [...list, { id, role: 'system', text: 'Assistant stopped' }])\n }\n\n // The tool \"runs\" for a moment, then the reply starts streaming.\n useEffect(() => {\n if (phase !== 'tool') return\n const timer = window.setTimeout(() => {\n updateActive(turn => ({ ...turn, tool: 'done' }))\n setPhase('streaming')\n }, 1200)\n return () => window.clearTimeout(timer)\n }, [phase])\n\n // Finished or stopped: keep what arrived, and hand the thread a message that is no longer streaming.\n useEffect(() => {\n if (phase !== 'streaming' || !reply.done) return\n updateActive(turn => ({ ...turn, text: reply.text }))\n if (reply.stopped) addStopped()\n setPhase('idle')\n setReplyIndex(index => index + 1)\n }, [phase, reply.done, reply.text, reply.stopped])\n\n const send = (text: string) => {\n const question = nextId.current++\n const answer = nextId.current++\n activeId.current = answer\n setTurns(list => [...list, { id: question, role: 'user', text }, { id: answer, role: 'assistant', text: '', tool: 'running' }])\n setDraft('')\n setPhase('tool')\n }\n\n const stop = () => {\n if (phase === 'streaming') {\n reply.stop()\n return\n }\n // Stopped before any text: the empty reply goes, the system line says what happened.\n setTurns(list => list.filter(turn => turn.id !== activeId.current))\n addStopped()\n setPhase('idle')\n }\n\n const busy = phase !== 'idle'\n\n return (\n <div className=\"flex w-full max-w-2xl flex-col border border-[var(--color-border-hairline-strong)]\">\n <ChatThread label=\"Conversation with the assistant\" busy={phase === 'tool'} className=\"h-[28rem]\">\n {turns.map(turn => {\n if (turn.role === 'user') {\n return (\n <ChatMessage key={turn.id} role=\"user\" author=\"You\">\n {turn.text}\n </ChatMessage>\n )\n }\n if (turn.role === 'system') {\n return (\n <ChatMessage key={turn.id} role=\"system\" author=\"Status\">\n {turn.text}\n </ChatMessage>\n )\n }\n const streaming = phase === 'streaming' && turn.id === activeId.current\n const text = streaming ? reply.text : turn.text\n return (\n <ChatMessage key={turn.id} role=\"assistant\" author=\"Assistant\" streaming={streaming}>\n {turn.tool === undefined ? null : (\n <ToolCall\n name=\"query_stock_levels\"\n title=\"Read stock levels at Depot 4\"\n status={turn.tool}\n duration={turn.tool === 'done' ? '1.2 s' : undefined}\n input={'{ \"depot\": 4 }'}\n output={turn.tool === 'done' ? '{ \"on_hand\": 1240, \"lines\": 38, \"below_reorder_point\": 2 }' : undefined}\n />\n )}\n {text === '' ? null : <p>{text}</p>}\n {turn.approval ? (\n <ApprovalRequest\n title=\"Send this reorder to the supplier?\"\n state={approval}\n onApprove={() => setApproval('approved')}\n onReject={() => setApproval('rejected')}\n approveLabel=\"Send reorder\"\n rejectLabel=\"Don't send\"\n >\n <p>To: Arad replica supply</p>\n <p>10 × K-41 field radio, 8 × trail pack, to Depot 4 by Thursday 22 October.</p>\n </ApprovalRequest>\n ) : null}\n </ChatMessage>\n )\n })}\n </ChatThread>\n <PromptInput\n label=\"Message the assistant\"\n placeholder=\"Ask about stock, orders or depots\"\n value={draft}\n onValueChange={setDraft}\n onSubmit={send}\n busy={busy}\n onStop={stop}\n className=\"p-4 border-t border-[var(--color-border-hairline-strong)]\"\n />\n </div>\n )\n}\n\n<LiveConversation />"
9627
9706
  }
9628
- ]
9707
+ ],
9708
+ "closed": true
9629
9709
  },
9630
9710
  {
9631
9711
  "name": "CheckoutForm",
@@ -9803,7 +9883,8 @@
9803
9883
  "caption": "Three steps, a demo order",
9804
9884
  "source": "const LINES: CartLine[] = [\n { id: 'kestrel-c', name: 'Kestrel C carbine replica', unitPrice: 13900, quantity: 1 },\n { id: 'prism-holo-sight', name: 'Prism holo sight', unitPrice: 4900, quantity: 2 },\n { id: 'beam-600', name: 'Beam 600 rail light', unitPrice: 2400, quantity: 1 },\n]\n\nconst COUNTRIES = [\n { value: 'ie', label: 'Ireland' },\n { value: 'ro', label: 'Romania' },\n { value: 'de', label: 'Germany' },\n { value: 'fr', label: 'France' },\n { value: 'es', label: 'Spain' },\n]\n\nconst wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\n<CheckoutForm lines={LINES} countries={COUNTRIES} onPlaceOrder={() => wait(800).then(() => undefined)} headingLevel={3} />"
9805
9885
  }
9806
- ]
9886
+ ],
9887
+ "closed": true
9807
9888
  },
9808
9889
  {
9809
9890
  "name": "CommandPalette",
@@ -10005,7 +10086,8 @@
10005
10086
  "caption": "Store commands",
10006
10087
  "source": "function StoreCommands() {\n const [open, setOpen] = useState(false)\n const [last, setLast] = useState('none')\n const make = (id: string, label: string, group: string, icon: ReactNode, extra: Partial<Command> = {}): Command => ({\n id,\n label,\n group,\n icon,\n onSelect: () => setLast(label),\n ...extra,\n })\n const commands = [\n make('orders', 'Go to orders', 'Navigate', <Package />, { keywords: ['sales'] }),\n make('shipments', 'Go to shipments', 'Navigate', <Truck />),\n make('returns', 'Go to returns', 'Navigate', <Inbox />),\n make('new-shipment', 'New shipment', 'Create', <Plus />, { shortcut: 'N' }),\n make('new-product', 'New product', 'Create', <Box />),\n make('invite', 'Invite a teammate', 'Account', <Users />, { keywords: ['team', 'people'] }),\n make('settings', 'Open settings', 'Account', <Settings />),\n ]\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button variant=\"secondary\" aria-keyshortcuts=\"Control+K Meta+K\" onClick={() => setOpen(true)}>\n Open command palette\n </Button>\n <span aria-hidden=\"true\" className=\"inline-flex gap-1\">\n <Kbd>Ctrl</Kbd>\n <Kbd>K</Kbd>\n </span>\n <p role=\"status\" className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n Last command: {last}\n </p>\n <CommandPalette commands={commands} open={open} onOpenChange={setOpen} />\n </div>\n )\n}\n\n<StoreCommands />"
10007
10088
  }
10008
- ]
10089
+ ],
10090
+ "closed": true
10009
10091
  },
10010
10092
  {
10011
10093
  "name": "DataTable",
@@ -10227,7 +10309,8 @@
10227
10309
  "caption": "Empty",
10228
10310
  "source": "type Status = 'In transit' | 'Delivered' | 'Held'\n\ntype Shipment = { ref: string; destination: string; weight: number; items: number; status: Status }\n\nconst STATUS_TONE: Record<Status, 'info' | 'ok' | 'warning'> = {\n 'In transit': 'info',\n Delivered: 'ok',\n Held: 'warning',\n}\n\nconst COLUMNS: DataTableColumn<Shipment>[] = [\n { key: 'ref', header: 'Reference', cell: s => s.ref, sortValue: s => s.ref },\n { key: 'destination', header: 'Destination', cell: s => s.destination },\n { key: 'weight', header: 'Weight (kg)', cell: s => s.weight.toFixed(1), sortValue: s => s.weight, numeric: true },\n { key: 'items', header: 'Items', cell: s => String(s.items), sortValue: s => s.items, numeric: true },\n {\n key: 'status',\n header: 'Status',\n cell: s => (\n <Badge emphasis=\"solid\" tone={STATUS_TONE[s.status]}>\n {s.status}\n </Badge>\n ),\n },\n]\n\n<DataTable caption=\"Returns\" columns={COLUMNS} rows={[]} getRowId={s => s.ref} rowHeader=\"ref\" emptyMessage=\"No returns yet.\" />"
10229
10311
  }
10230
- ]
10312
+ ],
10313
+ "closed": true
10231
10314
  },
10232
10315
  {
10233
10316
  "name": "Dialog",
@@ -10399,7 +10482,8 @@
10399
10482
  "caption": "Long content",
10400
10483
  "source": "function LongContentExample() {\n const contentRef = useRef<HTMLDivElement>(null)\n return (\n <Dialog>\n <DialogTrigger asChild>\n <Button variant=\"secondary\">Read shipping terms</Button>\n </DialogTrigger>\n <DialogContent\n ref={contentRef}\n title=\"Shipping terms\"\n size=\"lg\"\n tabIndex={-1}\n onOpenAutoFocus={event => {\n event.preventDefault()\n contentRef.current?.focus()\n }}\n >\n <div className=\"flex flex-col gap-4\">\n <p>Orders placed before 14:00 local time ship the same day; anything after ships the next business day.</p>\n <p>Every parcel leaves in double-walled board with corner protection; ask for crate packaging on anything over 30kg.</p>\n <p>Photograph the box before opening and report damage within 48 hours; we replace or refund, never both.</p>\n <p>Unopened stock returns within 30 days for a full refund; opened stock returns for store credit only.</p>\n <p>Depots outside the primary freight lanes add one to three business days and a remote-area surcharge.</p>\n <p>A single parcel tops out at 40kg; heavier loads split automatically at checkout.</p>\n <p>Cross-border shipments carry the recipient's own duties and taxes, billed separately by the carrier.</p>\n <p>Reach the shipping desk at shipping@example.com for anything this page does not answer.</p>\n </div>\n <DialogFooter>\n <DialogClose asChild>\n <Button>Done</Button>\n </DialogClose>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n<LongContentExample />"
10401
10484
  }
10402
- ]
10485
+ ],
10486
+ "closed": true
10403
10487
  },
10404
10488
  {
10405
10489
  "name": "Footer",
@@ -10613,7 +10697,8 @@
10613
10697
  "caption": "With a wordmark and a backdrop: a layer behind the letters, with headroom over them",
10614
10698
  "source": "const STORE_COLUMNS: FooterColumn[] = [\n {\n title: 'Shop',\n links: [\n { label: 'Replicas', href: '#replicas' },\n { label: 'Optics', href: '#optics' },\n { label: 'Parts', href: '#parts' },\n { label: 'Care', href: '#care' },\n ],\n },\n {\n title: 'Support',\n links: [\n { label: 'Shipping', href: '#shipping' },\n { label: 'Returns', href: '#returns' },\n { label: 'Warranty', href: '#warranty' },\n { label: 'Contact', href: '#contact' },\n ],\n },\n {\n title: 'Company',\n links: [\n { label: 'About', href: '#about' },\n { label: 'Field notes', href: '#field-notes' },\n { label: 'Careers', href: '#careers' },\n ],\n },\n {\n title: 'Elsewhere',\n links: [\n { label: 'Site finder', href: 'https://example.com/sites', external: true },\n { label: 'Field guides', href: 'https://example.com/field-guides', external: true },\n ],\n },\n]\n\nconst LEGAL = '© 2026 Hexline. A demo brand for this design system.'\n\nconst BACKDROP = (\n <div\n className=\"absolute inset-0\"\n style={{\n backgroundImage: 'radial-gradient(var(--color-text-muted) 1px, transparent 1.5px)',\n backgroundSize: '14px 14px',\n maskImage: 'linear-gradient(to bottom, transparent, black 70%)',\n }}\n />\n)\n\n<Footer label=\"Backdrop footer\" headingLevel={4} columns={STORE_COLUMNS.slice(0, 2)} legal={LEGAL} wordmark=\"Hexline\" backdrop={BACKDROP} />"
10615
10699
  }
10616
- ]
10700
+ ],
10701
+ "closed": true
10617
10702
  },
10618
10703
  {
10619
10704
  "name": "KanbanBoard",
@@ -10864,7 +10949,8 @@
10864
10949
  "caption": "Work orders, with an empty column",
10865
10950
  "source": "type Order = { id: string; title: string; bay: string; status: 'open' | 'parts' | 'done' }\n\nconst ORDER_COLUMNS: KanbanColumn[] = [\n { id: 'open', title: 'Open' },\n { id: 'parts', title: 'Waiting for parts' },\n { id: 'done', title: 'Done' },\n]\n\nconst ORDERS: Order[] = [\n { id: 'wo-31', title: 'Replace dock door seal', bay: 'Bay 2', status: 'open' },\n { id: 'wo-32', title: 'Recalibrate floor scale', bay: 'Bay 5', status: 'open' },\n { id: 'wo-29', title: 'Service pallet jack', bay: 'Bay 1', status: 'done' },\n]\n\nconst orderAccessors = {\n getItemId: (order: Order) => order.id,\n getColumnId: (order: Order) => order.status,\n setColumnId: (order: Order, status: string) => ({ ...order, status: status as Order['status'] }),\n}\n\nfunction WorkOrders() {\n const [orders, setOrders] = useState(ORDERS)\n return (\n <div className=\"w-full min-w-0\">\n <KanbanBoard\n label=\"Depot work orders\"\n columns={ORDER_COLUMNS}\n items={orders}\n getItemId={orderAccessors.getItemId}\n getColumnId={orderAccessors.getColumnId}\n getItemLabel={order => order.title}\n renderCard={order => (\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-semibold\">{order.title}</p>\n <p className=\"font-mono text-xs uppercase tracking-[0.12em] text-[var(--color-text-secondary)]\">{order.bay}</p>\n </div>\n )}\n onMove={(id, status, index) => setOrders(current => applyKanbanMove(current, id, status, index, orderAccessors))}\n emptyColumnMessage=\"No orders waiting\"\n columnHeadingLevel={4}\n />\n </div>\n )\n}\n\n<WorkOrders />"
10866
10951
  }
10867
- ]
10952
+ ],
10953
+ "closed": true
10868
10954
  },
10869
10955
  {
10870
10956
  "name": "Navbar",
@@ -11002,7 +11088,8 @@
11002
11088
  "caption": "A narrow container",
11003
11089
  "source": "const ITEMS = [\n { label: 'Replicas', href: '#replicas' },\n { label: 'Optics', href: '#optics', current: true },\n { label: 'Parts', href: '#parts' },\n { label: 'Care', href: '#care' },\n { label: 'Field notes', href: '#field-notes' },\n]\n\nconst BRAND = (\n <a\n href=\"#home\"\n className=\"inline-flex items-center min-h-10 font-display font-bold uppercase tracking-[0.14em] text-[var(--color-text-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-focus-ring)]\"\n >\n Hexline\n </a>\n)\n\nconst ACTIONS = (\n <>\n <IconButton label=\"Search\" variant=\"ghost\">\n <Search aria-hidden=\"true\" className=\"w-5 h-5\" />\n </IconButton>\n <IconButton label=\"Cart, 2 items\" variant=\"ghost\" asChild>\n <a href=\"#cart\">\n <ShoppingBag aria-hidden=\"true\" className=\"w-5 h-5\" />\n </a>\n </IconButton>\n </>\n)\n\n<div className=\"max-w-sm\">\n <Navbar label=\"Store navigation, narrow\" brand={BRAND} items={ITEMS} actions={ACTIONS} />\n</div>"
11004
11090
  }
11005
- ]
11091
+ ],
11092
+ "closed": true
11006
11093
  },
11007
11094
  {
11008
11095
  "name": "NewsletterForm",
@@ -11154,7 +11241,8 @@
11154
11241
  "caption": "A sign-up that fails",
11155
11242
  "source": "const wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\nasync function refuse() {\n await wait(700)\n throw new Error('The demo server is switched off.')\n}\n\n<div className=\"flex w-full flex-col gap-3\">\n <p className=\"font-body text-sm text-[var(--color-text-secondary)]\">\n Demo: this one rejects every address, so you can see the alert, and the address you typed stays in the field.\n </p>\n <NewsletterForm\n label=\"Restock alerts by email\"\n consent=\"An email when a sold-out replica is back. Nothing else.\"\n submitLabel=\"Notify me\"\n busyLabel=\"Signing you up\"\n onSubmit={refuse}\n headingLevel={4}\n />\n</div>"
11156
11243
  }
11157
- ]
11244
+ ],
11245
+ "closed": true
11158
11246
  },
11159
11247
  {
11160
11248
  "name": "ProductCard",
@@ -11328,7 +11416,8 @@
11328
11416
  "caption": "Nothing matches",
11329
11417
  "source": "<ProductGrid label=\"Hexline range, filtered\" products={[]} emptyMessage=\"No products match these filters.\" />"
11330
11418
  }
11331
- ]
11419
+ ],
11420
+ "closed": true
11332
11421
  },
11333
11422
  {
11334
11423
  "name": "Sheet",
@@ -11483,7 +11572,8 @@
11483
11572
  "caption": "From the left",
11484
11573
  "source": "const DEPOTS = ['Brasov north', 'Brasov south', 'Sibiu', 'Cluj', 'Timisoara', 'Oradea']\n\nfunction FromTheLeftExample() {\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"secondary\">Open depot list</Button>\n </SheetTrigger>\n <SheetContent side=\"left\" title=\"Depots\">\n <ul role=\"list\" className=\"flex flex-col gap-2\">\n {DEPOTS.map(depot => (\n <li key={depot}>{depot}</li>\n ))}\n </ul>\n </SheetContent>\n </Sheet>\n )\n}\n\n<FromTheLeftExample />"
11485
11574
  }
11486
- ]
11575
+ ],
11576
+ "closed": true
11487
11577
  },
11488
11578
  {
11489
11579
  "name": "Sidebar",
@@ -11616,7 +11706,8 @@
11616
11706
  "caption": "In a sheet on a narrow screen: picking a link closes the sheet, through renderLink",
11617
11707
  "source": "const GROUPS: SidebarGroup[] = [\n {\n title: 'Operations',\n icon: <Warehouse />,\n items: [\n { label: 'Overview', href: '#overview', icon: <LayoutDashboard /> },\n { label: 'Orders', href: '#orders', icon: <Package />, current: true, count: 12 },\n { label: 'Shipments', href: '#shipments', icon: <Truck /> },\n { label: 'Returns', href: '#returns', icon: <Inbox />, count: 3 },\n ],\n },\n {\n title: 'Catalogue',\n icon: <Boxes />,\n defaultOpen: false,\n items: [\n { label: 'Products', href: '#products', icon: <Box /> },\n { label: 'Inventory', href: '#inventory', icon: <Map /> },\n ],\n },\n {\n title: 'Account',\n icon: <UserCog />,\n collapsible: false,\n items: [\n { label: 'Settings', href: '#settings', icon: <Settings /> },\n { label: 'Team', href: '#team', icon: <Users /> },\n ],\n },\n]\n\nconst HEADER = <p className=\"font-display font-semibold\">Hexline ops</p>\n\nconst FOOTER = <p className=\"text-xs text-[var(--color-text-secondary)]\">Signed in as Ana Pop</p>\n\nconst closeOnChoose = ({ onClick, ...props }: LinkRenderProps) => (\n <SheetClose asChild type={undefined}>\n <a {...props} onClick={onClick} />\n </SheetClose>\n)\n\n<Sheet>\n <SheetTrigger asChild>\n <Button variant=\"secondary\">Open navigation</Button>\n </SheetTrigger>\n <SheetContent side=\"left\" title=\"Navigation\">\n <Sidebar\n label=\"Dashboard navigation, in a sheet\"\n groups={GROUPS}\n header={HEADER}\n footer={FOOTER}\n renderLink={closeOnChoose}\n />\n </SheetContent>\n</Sheet>"
11618
11708
  }
11619
- ]
11709
+ ],
11710
+ "closed": true
11620
11711
  }
11621
11712
  ]
11622
11713
  }