division-ds 0.2.1 → 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.
- package/AGENTS.md +4 -4
- package/package.json +1 -1
- package/public/docs/components/accordion.md +1 -1
- package/public/docs/components/ai-label.md +1 -1
- package/public/docs/components/alert-dialog.md +1 -1
- package/public/docs/components/alert.md +1 -1
- package/public/docs/components/approval-request.md +1 -1
- package/public/docs/components/auth-form.md +1 -1
- package/public/docs/components/avatar-group.md +1 -1
- package/public/docs/components/avatar.md +1 -1
- package/public/docs/components/badge.md +1 -1
- package/public/docs/components/breadcrumbs.md +1 -1
- package/public/docs/components/button.md +1 -1
- package/public/docs/components/card.md +1 -1
- package/public/docs/components/carousel.md +1 -1
- package/public/docs/components/cart.md +1 -1
- package/public/docs/components/chart-panel.md +1 -1
- package/public/docs/components/chat-message.md +1 -1
- package/public/docs/components/chat-thread.md +1 -1
- package/public/docs/components/checkbox.md +1 -1
- package/public/docs/components/checkout-form.md +1 -1
- package/public/docs/components/command-palette.md +5 -2
- package/public/docs/components/data-table.md +1 -1
- package/public/docs/components/date-picker.md +166 -0
- package/public/docs/components/date-presets.md +111 -0
- package/public/docs/components/date-range-picker.md +135 -0
- package/public/docs/components/description-list.md +1 -1
- package/public/docs/components/dialog.md +1 -1
- package/public/docs/components/divider.md +1 -1
- package/public/docs/components/dropdown-menu.md +1 -1
- package/public/docs/components/empty-state.md +1 -1
- package/public/docs/components/footer.md +1 -1
- package/public/docs/components/form-field.md +2 -2
- package/public/docs/components/icon-button.md +1 -1
- package/public/docs/components/input.md +1 -1
- package/public/docs/components/kanban-board.md +1 -1
- package/public/docs/components/kbd.md +1 -1
- package/public/docs/components/link.md +1 -1
- package/public/docs/components/list-item.md +1 -1
- package/public/docs/components/loading-animation.md +1 -1
- package/public/docs/components/multi-select.md +5 -5
- package/public/docs/components/navbar.md +1 -1
- package/public/docs/components/newsletter-form.md +1 -1
- package/public/docs/components/page-header.md +1 -1
- package/public/docs/components/pagination.md +1 -1
- package/public/docs/components/product-card.md +1 -1
- package/public/docs/components/progress.md +1 -1
- package/public/docs/components/prompt-input.md +1 -1
- package/public/docs/components/quantity-stepper.md +2 -2
- package/public/docs/components/radio.md +1 -1
- package/public/docs/components/rating.md +1 -1
- package/public/docs/components/score-ring.md +1 -1
- package/public/docs/components/search.md +1 -1
- package/public/docs/components/section-heading.md +1 -1
- package/public/docs/components/segmented-control.md +1 -1
- package/public/docs/components/select.md +2 -2
- package/public/docs/components/sheet.md +1 -1
- package/public/docs/components/sidebar.md +1 -1
- package/public/docs/components/skeleton.md +1 -1
- package/public/docs/components/slider.md +1 -1
- package/public/docs/components/sparkline.md +1 -1
- package/public/docs/components/spinner.md +1 -1
- package/public/docs/components/stat.md +1 -1
- package/public/docs/components/status-tracker.md +1 -1
- package/public/docs/components/stepper.md +1 -1
- package/public/docs/components/switch.md +1 -1
- package/public/docs/components/tabs.md +1 -1
- package/public/docs/components/tag.md +1 -1
- package/public/docs/components/textarea.md +1 -1
- package/public/docs/components/time-field.md +127 -0
- package/public/docs/components/timeline.md +1 -1
- package/public/docs/components/toast.md +1 -1
- package/public/docs/components/tool-call.md +1 -1
- package/public/docs/components/tooltip.md +1 -1
- package/public/manifest/components.json +1045 -86
- package/scripts/lib/agent-docs.mjs +22 -1
- package/scripts/lib/mcp-tools.mjs +2 -14
- package/scripts/lib/props-validator.mjs +36 -2
- package/scripts/lib/search-score.mjs +24 -0
- package/skills/division-ds/SKILL.md +2 -2
- package/skills/division-ds/topics/forms.md +1 -1
- package/division-ds-0.2.1.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": "
|
|
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",
|
|
@@ -4309,35 +4341,689 @@
|
|
|
4309
4341
|
"description": "The person's words on surface.sunken, aligned right, at most 85% of the width."
|
|
4310
4342
|
},
|
|
4311
4343
|
{
|
|
4312
|
-
"part": "Body (assistant)",
|
|
4313
|
-
"description": "Full width, with no bubble: text, paragraphs, and ToolCall and ApprovalRequest blocks stacked in a column."
|
|
4344
|
+
"part": "Body (assistant)",
|
|
4345
|
+
"description": "Full width, with no bubble: text, paragraphs, and ToolCall and ApprovalRequest blocks stacked in a column."
|
|
4346
|
+
},
|
|
4347
|
+
{
|
|
4348
|
+
"part": "Caret",
|
|
4349
|
+
"description": "A solid block in the signal colour after the last paragraph while streaming, drawn with the CSS ::after rule. It never blinks."
|
|
4350
|
+
},
|
|
4351
|
+
{
|
|
4352
|
+
"part": "Actions",
|
|
4353
|
+
"description": "Small buttons under an assistant message: Copy, Retry, and ResponseFeedback (thumbs up and down)."
|
|
4354
|
+
},
|
|
4355
|
+
{
|
|
4356
|
+
"part": "System line",
|
|
4357
|
+
"description": "A centred label in monospaced type between two thin lines: \"Assistant stopped\". Its author is there for screen readers only."
|
|
4358
|
+
}
|
|
4359
|
+
],
|
|
4360
|
+
"examples": [
|
|
4361
|
+
{
|
|
4362
|
+
"caption": "The three roles",
|
|
4363
|
+
"source": "<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage role=\"user\" author=\"You\" time=\"2026-10-15T09:40:00+03:00\" timeLabel=\"09:40\">\n Which replicas are running low at Depot 4?\n </ChatMessage>\n <ChatMessage\n role=\"assistant\"\n author=\"Assistant\"\n time=\"2026-10-15T09:40:00+03:00\"\n timeLabel=\"09:40\"\n actions={\n <>\n <Button size=\"xs\" variant=\"ghost\" iconStart={<Copy />}>\n Copy\n </Button>\n <Button size=\"xs\" variant=\"ghost\" iconStart={<RotateCcw />}>\n Retry\n </Button>\n </>\n }\n >\n Two are under their reorder point: the K-41 field radio (6 left, reorder at 10) and the trail\n pack (3 left, reorder at 8). Everything else has at least two weeks of cover.\n </ChatMessage>\n <ChatMessage role=\"system\" author=\"Status\">\n Assistant stopped\n </ChatMessage>\n</div>"
|
|
4364
|
+
},
|
|
4365
|
+
{
|
|
4366
|
+
"caption": "While the reply is still streaming in: aria-busy, an ARIA (Accessible Rich Internet Applications) attribute that tells screen readers the message isn't finished, and a cursor that doesn't blink",
|
|
4367
|
+
"source": "<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage role=\"assistant\" author=\"Assistant\" streaming>\n Depot 4 holds 1,240 replicas across 38 lines. The busiest\n </ChatMessage>\n</div>"
|
|
4368
|
+
},
|
|
4369
|
+
{
|
|
4370
|
+
"caption": "Feedback on a reply: thumbs up or down, one vote, and an optional detail step after Not helpful",
|
|
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>"
|
|
4372
|
+
}
|
|
4373
|
+
],
|
|
4374
|
+
"closed": true
|
|
4375
|
+
},
|
|
4376
|
+
{
|
|
4377
|
+
"name": "DatePicker",
|
|
4378
|
+
"title": "Date picker",
|
|
4379
|
+
"tier": "molecules",
|
|
4380
|
+
"slug": "date-picker",
|
|
4381
|
+
"summary": "One date, typed or picked. The field has a segment for the day, the month and the year, in the order your language writes them: type the digits and it moves on by itself, or use the arrow keys. The button inside it opens a calendar. The value is a plain 'YYYY-MM-DD' string with no time zone, so it means the same day for everyone. Calendar and DateField are exported too, for building your own.",
|
|
4382
|
+
"useWhen": "One date: a delivery day, a deadline, a birthday.",
|
|
4383
|
+
"notFor": "A range: DateRangePicker. A time: TimeField.",
|
|
4384
|
+
"source": "src/components/molecules/date-picker/DatePicker.tsx",
|
|
4385
|
+
"docs": "/docs/components/date-picker.md",
|
|
4386
|
+
"imports": [
|
|
4387
|
+
{
|
|
4388
|
+
"names": [
|
|
4389
|
+
"Calendar"
|
|
4390
|
+
],
|
|
4391
|
+
"from": "src/components/molecules/date-picker/Calendar"
|
|
4392
|
+
},
|
|
4393
|
+
{
|
|
4394
|
+
"names": [
|
|
4395
|
+
"DateField"
|
|
4396
|
+
],
|
|
4397
|
+
"from": "src/components/molecules/date-picker/DateField"
|
|
4398
|
+
},
|
|
4399
|
+
{
|
|
4400
|
+
"names": [
|
|
4401
|
+
"DatePicker"
|
|
4402
|
+
],
|
|
4403
|
+
"from": "src/components/molecules/date-picker/DatePicker"
|
|
4404
|
+
}
|
|
4405
|
+
],
|
|
4406
|
+
"props": [
|
|
4407
|
+
{
|
|
4408
|
+
"name": "label",
|
|
4409
|
+
"type": "string",
|
|
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."
|
|
4411
|
+
},
|
|
4412
|
+
{
|
|
4413
|
+
"name": "value",
|
|
4414
|
+
"type": "string ('YYYY-MM-DD' | '')",
|
|
4415
|
+
"description": "Controlled value (your page holds it)."
|
|
4416
|
+
},
|
|
4417
|
+
{
|
|
4418
|
+
"name": "defaultValue",
|
|
4419
|
+
"type": "string",
|
|
4420
|
+
"default": "''",
|
|
4421
|
+
"description": "Starting value when the picker holds the value itself."
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
"name": "onValueChange",
|
|
4425
|
+
"type": "(value: string) => void",
|
|
4426
|
+
"description": "Fires with a whole, real date, or '' when the field is emptied. A half-typed date does not fire."
|
|
4427
|
+
},
|
|
4428
|
+
{
|
|
4429
|
+
"name": "min",
|
|
4430
|
+
"type": "string",
|
|
4431
|
+
"description": "Earliest date, 'YYYY-MM-DD'. Earlier days are struck through; typing one marks the field invalid."
|
|
4432
|
+
},
|
|
4433
|
+
{
|
|
4434
|
+
"name": "max",
|
|
4435
|
+
"type": "string",
|
|
4436
|
+
"description": "Latest date, 'YYYY-MM-DD'."
|
|
4437
|
+
},
|
|
4438
|
+
{
|
|
4439
|
+
"name": "isDateDisabled",
|
|
4440
|
+
"type": "(date: string) => boolean",
|
|
4441
|
+
"description": "Days that cannot be picked, such as weekends or holidays."
|
|
4442
|
+
},
|
|
4443
|
+
{
|
|
4444
|
+
"name": "locale",
|
|
4445
|
+
"type": "string",
|
|
4446
|
+
"description": "A language tag such as 'en-GB' or 'de'. Sets the segment order, the separator, the month and day names and the first day of the week. Defaults to the browser's."
|
|
4447
|
+
},
|
|
4448
|
+
{
|
|
4449
|
+
"name": "weekStartsOn",
|
|
4450
|
+
"type": "number",
|
|
4451
|
+
"description": "0 Sunday to 6 Saturday. Defaults to the locale's."
|
|
4452
|
+
},
|
|
4453
|
+
{
|
|
4454
|
+
"name": "showToday",
|
|
4455
|
+
"type": "boolean",
|
|
4456
|
+
"default": "true",
|
|
4457
|
+
"description": "Shows the Today button under the calendar when today can be picked."
|
|
4458
|
+
},
|
|
4459
|
+
{
|
|
4460
|
+
"name": "disabled",
|
|
4461
|
+
"type": "boolean",
|
|
4462
|
+
"default": "false",
|
|
4463
|
+
"description": "Disables the field and the calendar button."
|
|
4464
|
+
},
|
|
4465
|
+
{
|
|
4466
|
+
"name": "name",
|
|
4467
|
+
"type": "string",
|
|
4468
|
+
"description": "Submits the 'YYYY-MM-DD' value with a form, through a hidden input."
|
|
4469
|
+
},
|
|
4470
|
+
{
|
|
4471
|
+
"name": "invalid",
|
|
4472
|
+
"type": "boolean",
|
|
4473
|
+
"description": "Marks the field invalid. FormField sets it when there is an error."
|
|
4474
|
+
},
|
|
4475
|
+
{
|
|
4476
|
+
"name": "required",
|
|
4477
|
+
"type": "boolean",
|
|
4478
|
+
"description": "Marks the field required. FormField sets it."
|
|
4479
|
+
},
|
|
4480
|
+
{
|
|
4481
|
+
"name": "id",
|
|
4482
|
+
"type": "string",
|
|
4483
|
+
"description": "The field's id, so a FormField's error text can point at it. FormField sets it."
|
|
4484
|
+
},
|
|
4485
|
+
{
|
|
4486
|
+
"name": "aria-describedby",
|
|
4487
|
+
"type": "string",
|
|
4488
|
+
"description": "The ids of the help and error text. FormField sets it."
|
|
4489
|
+
},
|
|
4490
|
+
{
|
|
4491
|
+
"name": "className",
|
|
4492
|
+
"type": "string",
|
|
4493
|
+
"description": "Combined with the field's classes."
|
|
4494
|
+
},
|
|
4495
|
+
{
|
|
4496
|
+
"name": "ref",
|
|
4497
|
+
"type": "Ref<HTMLDivElement>",
|
|
4498
|
+
"description": "The field's group element."
|
|
4499
|
+
},
|
|
4500
|
+
{
|
|
4501
|
+
"name": "trailing (DateField)",
|
|
4502
|
+
"type": "ReactNode",
|
|
4503
|
+
"description": "Something inside the field after the segments. DatePicker puts its calendar button here."
|
|
4504
|
+
},
|
|
4505
|
+
{
|
|
4506
|
+
"name": "bare (DateField)",
|
|
4507
|
+
"type": "boolean",
|
|
4508
|
+
"default": "false",
|
|
4509
|
+
"description": "No border, fill or ring of its own, for two dates inside one border (DateRangePicker draws them)."
|
|
4510
|
+
},
|
|
4511
|
+
{
|
|
4512
|
+
"name": "onSelect (Calendar)",
|
|
4513
|
+
"type": "(date: string) => void",
|
|
4514
|
+
"description": "A day was picked with a click, Enter or Space."
|
|
4515
|
+
},
|
|
4516
|
+
{
|
|
4517
|
+
"name": "value (Calendar)",
|
|
4518
|
+
"type": "string | null",
|
|
4519
|
+
"description": "The picked day. Ignored when dayState is given."
|
|
4520
|
+
},
|
|
4521
|
+
{
|
|
4522
|
+
"name": "month (Calendar)",
|
|
4523
|
+
"type": "string",
|
|
4524
|
+
"description": "Any day in the first month shown. Controlled with onMonthChange."
|
|
4525
|
+
},
|
|
4526
|
+
{
|
|
4527
|
+
"name": "defaultMonth (Calendar)",
|
|
4528
|
+
"type": "string",
|
|
4529
|
+
"description": "The first month shown when the calendar holds it itself. Defaults to the value's month, or today's."
|
|
4530
|
+
},
|
|
4531
|
+
{
|
|
4532
|
+
"name": "onMonthChange (Calendar)",
|
|
4533
|
+
"type": "(month: string) => void",
|
|
4534
|
+
"description": "Fires with the new first month's 1st day."
|
|
4535
|
+
},
|
|
4536
|
+
{
|
|
4537
|
+
"name": "dayState (Calendar)",
|
|
4538
|
+
"type": "(date: string) => 'single' | 'start' | 'end' | 'start-end' | 'middle' | undefined",
|
|
4539
|
+
"description": "Draws each day yourself, for a range or any other selection."
|
|
4540
|
+
},
|
|
4541
|
+
{
|
|
4542
|
+
"name": "onDayEnter (Calendar)",
|
|
4543
|
+
"type": "(date: string) => void",
|
|
4544
|
+
"description": "The keyboard or the pointer moved onto a day. A range uses it to preview the band."
|
|
4545
|
+
},
|
|
4546
|
+
{
|
|
4547
|
+
"name": "numberOfMonths (Calendar)",
|
|
4548
|
+
"type": "1 | 2",
|
|
4549
|
+
"default": "1",
|
|
4550
|
+
"description": "Months side by side, stacked on narrow screens."
|
|
4551
|
+
},
|
|
4552
|
+
{
|
|
4553
|
+
"name": "autoFocus (Calendar)",
|
|
4554
|
+
"type": "boolean",
|
|
4555
|
+
"default": "false",
|
|
4556
|
+
"description": "Moves focus to the picked day, or today, when the calendar appears."
|
|
4557
|
+
}
|
|
4558
|
+
],
|
|
4559
|
+
"api": [
|
|
4560
|
+
{
|
|
4561
|
+
"name": "DayState",
|
|
4562
|
+
"kind": "type",
|
|
4563
|
+
"code": "/** How a day is drawn. `single` is one picked day; the rest belong to a range. */\nexport type DayState = 'single' | 'start' | 'end' | 'start-end' | 'middle'",
|
|
4564
|
+
"from": "src/components/molecules/date-picker/Calendar"
|
|
4565
|
+
}
|
|
4566
|
+
],
|
|
4567
|
+
"keyboard": [
|
|
4568
|
+
{
|
|
4569
|
+
"keys": [
|
|
4570
|
+
"0–9"
|
|
4571
|
+
],
|
|
4572
|
+
"does": "Types into the focused segment and moves on once it is full."
|
|
4573
|
+
},
|
|
4574
|
+
{
|
|
4575
|
+
"keys": [
|
|
4576
|
+
"↑",
|
|
4577
|
+
"↓"
|
|
4578
|
+
],
|
|
4579
|
+
"does": "In the field: one more or one less in the segment. In the calendar: a week earlier or later."
|
|
4580
|
+
},
|
|
4581
|
+
{
|
|
4582
|
+
"keys": [
|
|
4583
|
+
"←",
|
|
4584
|
+
"→"
|
|
4585
|
+
],
|
|
4586
|
+
"does": "In the field: the previous or next segment. In the calendar: the day before or after."
|
|
4587
|
+
},
|
|
4588
|
+
{
|
|
4589
|
+
"keys": [
|
|
4590
|
+
"Backspace"
|
|
4591
|
+
],
|
|
4592
|
+
"does": "Clears the segment, or moves back when it is already empty."
|
|
4593
|
+
},
|
|
4594
|
+
{
|
|
4595
|
+
"keys": [
|
|
4596
|
+
"Page Up",
|
|
4597
|
+
"Page Down"
|
|
4598
|
+
],
|
|
4599
|
+
"does": "The same day a month earlier or later. With Shift, a year."
|
|
4600
|
+
},
|
|
4601
|
+
{
|
|
4602
|
+
"keys": [
|
|
4603
|
+
"Home",
|
|
4604
|
+
"End"
|
|
4605
|
+
],
|
|
4606
|
+
"does": "The first or last day of the week."
|
|
4607
|
+
},
|
|
4608
|
+
{
|
|
4609
|
+
"keys": [
|
|
4610
|
+
"Enter",
|
|
4611
|
+
"Space"
|
|
4612
|
+
],
|
|
4613
|
+
"does": "Picks the focused day and closes the calendar."
|
|
4614
|
+
},
|
|
4615
|
+
{
|
|
4616
|
+
"keys": [
|
|
4617
|
+
"Esc"
|
|
4618
|
+
],
|
|
4619
|
+
"does": "Closes the calendar without a change."
|
|
4620
|
+
}
|
|
4621
|
+
],
|
|
4622
|
+
"dos": [
|
|
4623
|
+
"Store and send the 'YYYY-MM-DD' value as it is. It has no time zone, so it can't shift by a day.",
|
|
4624
|
+
"Set min and max to the real limits, so days nobody can book are struck through rather than refused after the fact.",
|
|
4625
|
+
"Wrap it in a FormField for a visible label, help text and errors."
|
|
4626
|
+
],
|
|
4627
|
+
"donts": [
|
|
4628
|
+
"Don't convert the value with new Date('2026-11-14'): that reads it as midnight UTC and can show the day before in the Americas.",
|
|
4629
|
+
"Don't hide unavailable days. Struck through and announced, they explain themselves.",
|
|
4630
|
+
"Don't use it for a date of birth without setting max to today."
|
|
4631
|
+
],
|
|
4632
|
+
"anatomy": [
|
|
4633
|
+
{
|
|
4634
|
+
"part": "Field (DateField)",
|
|
4635
|
+
"description": "A group named by the label, holding three spinbuttons (Day, Month, Year). Each says its value or \"Empty\". A date that does not exist (31 February) or falls outside min and max marks the field invalid."
|
|
4636
|
+
},
|
|
4637
|
+
{
|
|
4638
|
+
"part": "Calendar button",
|
|
4639
|
+
"description": "A ghost IconButton inside the field. Its name includes the picked date: \"Choose date, Saturday, 14 November 2026\"."
|
|
4640
|
+
},
|
|
4641
|
+
{
|
|
4642
|
+
"part": "Calendar",
|
|
4643
|
+
"description": "A grid with one tab stop. Each day is a button named with the full date. The picked day is a solid accent square with a cut corner; today has aria-current and a short bar under its number. Days that can't be picked stay visible, struck through and announced as unavailable."
|
|
4644
|
+
},
|
|
4645
|
+
{
|
|
4646
|
+
"part": "Today",
|
|
4647
|
+
"description": "A secondary Button under the calendar, shown only when today can be picked."
|
|
4648
|
+
}
|
|
4649
|
+
],
|
|
4650
|
+
"examples": [
|
|
4651
|
+
{
|
|
4652
|
+
"caption": "Default",
|
|
4653
|
+
"source": "<div className=\"w-full max-w-xs\">\n <DatePicker label=\"Delivery date\" />\n</div>"
|
|
4654
|
+
},
|
|
4655
|
+
{
|
|
4656
|
+
"caption": "In a FormField, with help text",
|
|
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>"
|
|
4658
|
+
},
|
|
4659
|
+
{
|
|
4660
|
+
"caption": "Controlled: the page holds the value",
|
|
4661
|
+
"source": "function Controlled() {\n const [date, setDate] = useState('2026-11-14')\n return (\n <div className=\"flex w-full max-w-xs flex-col gap-2\">\n <DatePicker label=\"Dispatch date\" value={date} onValueChange={setDate} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">value: {date ? `'${date}'` : \"''\"}</span>\n </div>\n )\n}\n\n<Controlled />"
|
|
4662
|
+
},
|
|
4663
|
+
{
|
|
4664
|
+
"caption": "Limits: weekdays only, within the next two months",
|
|
4665
|
+
"source": "const weekend = (date: string) => [0, 6].includes(new Date(`${date}T12:00:00Z`).getUTCDay())\n\n<div className=\"w-full max-w-xs\">\n <DatePicker label=\"Collection day\" min=\"2026-11-02\" max=\"2026-12-31\" defaultValue=\"2026-11-16\" isDateDisabled={weekend} />\n</div>"
|
|
4666
|
+
},
|
|
4667
|
+
{
|
|
4668
|
+
"caption": "US order and a Sunday start, from the locale",
|
|
4669
|
+
"source": "<div className=\"w-full max-w-xs\">\n <DatePicker label=\"Ship date\" locale=\"en-US\" defaultValue=\"2026-11-14\" />\n</div>"
|
|
4670
|
+
},
|
|
4671
|
+
{
|
|
4672
|
+
"caption": "The calendar on its own",
|
|
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 />"
|
|
4674
|
+
}
|
|
4675
|
+
],
|
|
4676
|
+
"closed": true,
|
|
4677
|
+
"namedByFormField": [
|
|
4678
|
+
"DateField",
|
|
4679
|
+
"DatePicker"
|
|
4680
|
+
]
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
"name": "DatePresets",
|
|
4684
|
+
"title": "Date presets",
|
|
4685
|
+
"tier": "molecules",
|
|
4686
|
+
"slug": "date-presets",
|
|
4687
|
+
"summary": "Named periods such as Last 7 days, one click each. The defaults: Today, Yesterday, Last 7 days, Last 30 days, This month, Last month, This year, or your own. An add-on: put it in a DateRangePicker's aside and it reads and sets the picker's range by itself, then closes it. On its own it is a list you give a value and an onSelect. Installs DateRangePicker and DatePicker with it.",
|
|
4688
|
+
"useWhen": "Reports and logs, where people want the usual periods.",
|
|
4689
|
+
"notFor": "Exact dates: DateRangePicker alone.",
|
|
4690
|
+
"source": "src/components/molecules/date-presets/DatePresets.tsx",
|
|
4691
|
+
"docs": "/docs/components/date-presets.md",
|
|
4692
|
+
"imports": [
|
|
4693
|
+
{
|
|
4694
|
+
"names": [
|
|
4695
|
+
"DatePresets",
|
|
4696
|
+
"defaultPresets"
|
|
4697
|
+
],
|
|
4698
|
+
"from": "src/components/molecules/date-presets/DatePresets"
|
|
4699
|
+
}
|
|
4700
|
+
],
|
|
4701
|
+
"props": [
|
|
4702
|
+
{
|
|
4703
|
+
"name": "presets",
|
|
4704
|
+
"type": "DatePreset[]",
|
|
4705
|
+
"default": "defaultPresets()",
|
|
4706
|
+
"description": "Each { label, range }. Make your own, or start from defaultPresets(today)."
|
|
4707
|
+
},
|
|
4708
|
+
{
|
|
4709
|
+
"name": "value",
|
|
4710
|
+
"type": "{ start: string; end: string }",
|
|
4711
|
+
"description": "The current range. Inside a DateRangePicker it comes from the picker."
|
|
4712
|
+
},
|
|
4713
|
+
{
|
|
4714
|
+
"name": "onSelect",
|
|
4715
|
+
"type": "(range: DateRange) => void",
|
|
4716
|
+
"description": "A preset was chosen. Inside a DateRangePicker the picker also takes it and closes."
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
"name": "label",
|
|
4720
|
+
"type": "string",
|
|
4721
|
+
"default": "'Presets'",
|
|
4722
|
+
"description": "The list's accessible name."
|
|
4723
|
+
},
|
|
4724
|
+
{
|
|
4725
|
+
"name": "className",
|
|
4726
|
+
"type": "string",
|
|
4727
|
+
"description": "Combined with the list's classes."
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
"name": "ref",
|
|
4731
|
+
"type": "Ref<HTMLDivElement>",
|
|
4732
|
+
"description": "The list element."
|
|
4733
|
+
},
|
|
4734
|
+
{
|
|
4735
|
+
"name": "defaultPresets()",
|
|
4736
|
+
"type": "(today?: string) => DatePreset[]",
|
|
4737
|
+
"description": "The seven usual periods counted back from today ('YYYY-MM-DD'; defaults to the reader's today). Every one includes today."
|
|
4738
|
+
}
|
|
4739
|
+
],
|
|
4740
|
+
"api": [
|
|
4741
|
+
{
|
|
4742
|
+
"name": "DatePreset",
|
|
4743
|
+
"kind": "type",
|
|
4744
|
+
"code": "export type DatePreset = { label: string; range: DateRange }",
|
|
4745
|
+
"from": "src/components/molecules/date-presets/DatePresets"
|
|
4746
|
+
},
|
|
4747
|
+
{
|
|
4748
|
+
"name": "defaultPresets",
|
|
4749
|
+
"kind": "function",
|
|
4750
|
+
"code": "/** The usual periods, counted back from `today` (default: the reader's today). Each includes today. */\nexport function defaultPresets(today: IsoDate = todayIso()): DatePreset[]",
|
|
4751
|
+
"from": "src/components/molecules/date-presets/DatePresets"
|
|
4752
|
+
}
|
|
4753
|
+
],
|
|
4754
|
+
"keyboard": [
|
|
4755
|
+
{
|
|
4756
|
+
"keys": [
|
|
4757
|
+
"Tab"
|
|
4758
|
+
],
|
|
4759
|
+
"does": "Moves through the presets."
|
|
4760
|
+
},
|
|
4761
|
+
{
|
|
4762
|
+
"keys": [
|
|
4763
|
+
"Enter",
|
|
4764
|
+
"Space"
|
|
4765
|
+
],
|
|
4766
|
+
"does": "Chooses the focused preset."
|
|
4767
|
+
}
|
|
4768
|
+
],
|
|
4769
|
+
"dos": [
|
|
4770
|
+
"Keep the list short, seven or fewer.",
|
|
4771
|
+
"Name periods the way your users say them: \"This quarter\" if they plan in quarters."
|
|
4772
|
+
],
|
|
4773
|
+
"donts": [
|
|
4774
|
+
"Don't offer a preset nobody can pick, such as a future period when max is today."
|
|
4775
|
+
],
|
|
4776
|
+
"anatomy": [
|
|
4777
|
+
{
|
|
4778
|
+
"part": "List",
|
|
4779
|
+
"description": "A group named \"Presets\". Each period is a button; the one matching the current range is pressed (aria-pressed) and filled."
|
|
4780
|
+
}
|
|
4781
|
+
],
|
|
4782
|
+
"examples": [
|
|
4783
|
+
{
|
|
4784
|
+
"caption": "Inside a DateRangePicker: no wiring",
|
|
4785
|
+
"source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker label=\"Activity\" aside={<DatePresets />} />\n</div>"
|
|
4786
|
+
},
|
|
4787
|
+
{
|
|
4788
|
+
"caption": "On its own",
|
|
4789
|
+
"source": "function OnItsOwn() {\n const [range, setRange] = useState<DateRange>(defaultPresets()[2].range)\n return (\n <div className=\"flex flex-col gap-3\">\n <DatePresets value={range} onSelect={setRange} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n {range.start} to {range.end}\n </span>\n </div>\n )\n}\n\n<OnItsOwn />"
|
|
4790
|
+
},
|
|
4791
|
+
{
|
|
4792
|
+
"caption": "Your own periods",
|
|
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>"
|
|
4794
|
+
}
|
|
4795
|
+
],
|
|
4796
|
+
"closed": true
|
|
4797
|
+
},
|
|
4798
|
+
{
|
|
4799
|
+
"name": "DateRangePicker",
|
|
4800
|
+
"title": "Date range picker",
|
|
4801
|
+
"tier": "molecules",
|
|
4802
|
+
"slug": "date-range-picker",
|
|
4803
|
+
"summary": "A start and an end date in one field, an add-on to DatePicker. Type both, or open two months side by side: the first day you pick is the start, the band follows you to the second, which sets the end and closes the calendar. Put DatePresets in `aside` for one-click periods like Last 7 days. Installs DatePicker with it.",
|
|
4804
|
+
"useWhen": "A booking, a report window, a leave request.",
|
|
4805
|
+
"notFor": "One day: DatePicker.",
|
|
4806
|
+
"source": "src/components/molecules/date-range-picker/DateRangePicker.tsx",
|
|
4807
|
+
"docs": "/docs/components/date-range-picker.md",
|
|
4808
|
+
"imports": [
|
|
4809
|
+
{
|
|
4810
|
+
"names": [
|
|
4811
|
+
"DateRangePicker",
|
|
4812
|
+
"EMPTY_RANGE",
|
|
4813
|
+
"useDateRangePicker"
|
|
4814
|
+
],
|
|
4815
|
+
"from": "src/components/molecules/date-range-picker/DateRangePicker"
|
|
4816
|
+
}
|
|
4817
|
+
],
|
|
4818
|
+
"props": [
|
|
4819
|
+
{
|
|
4820
|
+
"name": "label",
|
|
4821
|
+
"type": "string",
|
|
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."
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
"name": "value",
|
|
4826
|
+
"type": "{ start: string; end: string }",
|
|
4827
|
+
"description": "Controlled range. Each end is 'YYYY-MM-DD' or ''."
|
|
4828
|
+
},
|
|
4829
|
+
{
|
|
4830
|
+
"name": "defaultValue",
|
|
4831
|
+
"type": "{ start: string; end: string }",
|
|
4832
|
+
"default": "{ start: '', end: '' }",
|
|
4833
|
+
"description": "Starting range when the picker holds it itself."
|
|
4834
|
+
},
|
|
4835
|
+
{
|
|
4836
|
+
"name": "onValueChange",
|
|
4837
|
+
"type": "(range: DateRange) => void",
|
|
4838
|
+
"description": "Fires when either end changes. Days picked in the calendar always arrive in order."
|
|
4839
|
+
},
|
|
4840
|
+
{
|
|
4841
|
+
"name": "min",
|
|
4842
|
+
"type": "string",
|
|
4843
|
+
"description": "Earliest date, 'YYYY-MM-DD'."
|
|
4844
|
+
},
|
|
4845
|
+
{
|
|
4846
|
+
"name": "max",
|
|
4847
|
+
"type": "string",
|
|
4848
|
+
"description": "Latest date, 'YYYY-MM-DD'."
|
|
4849
|
+
},
|
|
4850
|
+
{
|
|
4851
|
+
"name": "isDateDisabled",
|
|
4852
|
+
"type": "(date: string) => boolean",
|
|
4853
|
+
"description": "Days that cannot be picked."
|
|
4854
|
+
},
|
|
4855
|
+
{
|
|
4856
|
+
"name": "locale",
|
|
4857
|
+
"type": "string",
|
|
4858
|
+
"description": "Segment order, names and first day of the week. Defaults to the browser's."
|
|
4859
|
+
},
|
|
4860
|
+
{
|
|
4861
|
+
"name": "weekStartsOn",
|
|
4862
|
+
"type": "number",
|
|
4863
|
+
"description": "0 Sunday to 6 Saturday. Defaults to the locale's."
|
|
4864
|
+
},
|
|
4865
|
+
{
|
|
4866
|
+
"name": "aside",
|
|
4867
|
+
"type": "ReactNode",
|
|
4868
|
+
"description": "Beside the months, in the popover. DatePresets reads and sets the range by itself here."
|
|
4869
|
+
},
|
|
4870
|
+
{
|
|
4871
|
+
"name": "partLabels",
|
|
4872
|
+
"type": "[string, string]",
|
|
4873
|
+
"default": "['Start date', 'End date']",
|
|
4874
|
+
"description": "The names of the two dates."
|
|
4875
|
+
},
|
|
4876
|
+
{
|
|
4877
|
+
"name": "disabled",
|
|
4878
|
+
"type": "boolean",
|
|
4879
|
+
"default": "false",
|
|
4880
|
+
"description": "Disables both dates and the button."
|
|
4881
|
+
},
|
|
4882
|
+
{
|
|
4883
|
+
"name": "invalid",
|
|
4884
|
+
"type": "boolean",
|
|
4885
|
+
"description": "Marks the field invalid. FormField sets it."
|
|
4886
|
+
},
|
|
4887
|
+
{
|
|
4888
|
+
"name": "required",
|
|
4889
|
+
"type": "boolean",
|
|
4890
|
+
"description": "Marks both dates required. FormField sets it."
|
|
4891
|
+
},
|
|
4892
|
+
{
|
|
4893
|
+
"name": "name",
|
|
4894
|
+
"type": "string",
|
|
4895
|
+
"description": "Submits name-start and name-end with a form."
|
|
4896
|
+
},
|
|
4897
|
+
{
|
|
4898
|
+
"name": "id",
|
|
4899
|
+
"type": "string",
|
|
4900
|
+
"description": "The field's id. FormField sets it."
|
|
4901
|
+
},
|
|
4902
|
+
{
|
|
4903
|
+
"name": "aria-describedby",
|
|
4904
|
+
"type": "string",
|
|
4905
|
+
"description": "The ids of the help and error text. FormField sets it."
|
|
4906
|
+
},
|
|
4907
|
+
{
|
|
4908
|
+
"name": "className",
|
|
4909
|
+
"type": "string",
|
|
4910
|
+
"description": "Combined with the field's classes."
|
|
4911
|
+
},
|
|
4912
|
+
{
|
|
4913
|
+
"name": "ref",
|
|
4914
|
+
"type": "Ref<HTMLDivElement>",
|
|
4915
|
+
"description": "The field's group element."
|
|
4916
|
+
},
|
|
4917
|
+
{
|
|
4918
|
+
"name": "useDateRangePicker()",
|
|
4919
|
+
"type": "() => { value: DateRange; select: (range: DateRange) => void } | null",
|
|
4920
|
+
"description": "Inside the popover: the range, and a way to set it and close. For your own aside content."
|
|
4921
|
+
}
|
|
4922
|
+
],
|
|
4923
|
+
"api": [
|
|
4924
|
+
{
|
|
4925
|
+
"name": "DateRange",
|
|
4926
|
+
"kind": "type",
|
|
4927
|
+
"code": "/** A start and an end, each 'YYYY-MM-DD' or '' while not chosen. */\nexport type DateRange = { start: IsoDate | ''; end: IsoDate | '' }",
|
|
4928
|
+
"from": "src/components/molecules/date-range-picker/DateRangePicker"
|
|
4929
|
+
},
|
|
4930
|
+
{
|
|
4931
|
+
"name": "EMPTY_RANGE",
|
|
4932
|
+
"kind": "const",
|
|
4933
|
+
"code": "export const EMPTY_RANGE: DateRange = { start: '', end: '' }",
|
|
4934
|
+
"from": "src/components/molecules/date-range-picker/DateRangePicker"
|
|
4935
|
+
},
|
|
4936
|
+
{
|
|
4937
|
+
"name": "useDateRangePicker",
|
|
4938
|
+
"kind": "const",
|
|
4939
|
+
"code": "/** The open picker's range, and a way to set it and close. DatePresets reads it, so a preset in\n * `aside` needs no wiring. null outside a DateRangePicker. */\nexport const useDateRangePicker = () => useContext(Context)",
|
|
4940
|
+
"from": "src/components/molecules/date-range-picker/DateRangePicker"
|
|
4941
|
+
}
|
|
4942
|
+
],
|
|
4943
|
+
"keyboard": [
|
|
4944
|
+
{
|
|
4945
|
+
"keys": [
|
|
4946
|
+
"0–9",
|
|
4947
|
+
"↑",
|
|
4948
|
+
"↓"
|
|
4949
|
+
],
|
|
4950
|
+
"does": "Types or steps the focused segment of either date."
|
|
4951
|
+
},
|
|
4952
|
+
{
|
|
4953
|
+
"keys": [
|
|
4954
|
+
"←",
|
|
4955
|
+
"→"
|
|
4956
|
+
],
|
|
4957
|
+
"does": "In the field: the next segment, across both dates. In the calendar: the day before or after, moving the band."
|
|
4958
|
+
},
|
|
4959
|
+
{
|
|
4960
|
+
"keys": [
|
|
4961
|
+
"Page Up",
|
|
4962
|
+
"Page Down"
|
|
4963
|
+
],
|
|
4964
|
+
"does": "A month earlier or later. With Shift, a year."
|
|
4965
|
+
},
|
|
4966
|
+
{
|
|
4967
|
+
"keys": [
|
|
4968
|
+
"Enter",
|
|
4969
|
+
"Space"
|
|
4970
|
+
],
|
|
4971
|
+
"does": "Picks the start, then the end, which closes the calendar."
|
|
4972
|
+
},
|
|
4973
|
+
{
|
|
4974
|
+
"keys": [
|
|
4975
|
+
"Esc"
|
|
4976
|
+
],
|
|
4977
|
+
"does": "Closes the calendar. A start picked without an end stays as the start."
|
|
4978
|
+
}
|
|
4979
|
+
],
|
|
4980
|
+
"dos": [
|
|
4981
|
+
"Add DatePresets in aside when people mostly want the usual periods.",
|
|
4982
|
+
"Validate the length yourself if a range has a limit (a 30-day report), and say so in help text."
|
|
4983
|
+
],
|
|
4984
|
+
"donts": [
|
|
4985
|
+
"Don't use two separate DatePickers for a start and an end: nobody sees the band.",
|
|
4986
|
+
"Don't reorder a typed range for the user; the field marks it invalid so they can see what they typed."
|
|
4987
|
+
],
|
|
4988
|
+
"anatomy": [
|
|
4989
|
+
{
|
|
4990
|
+
"part": "Field",
|
|
4991
|
+
"description": "A group named by the label, holding two segmented dates (Start date, End date) and the calendar button. A typed end before the start marks it invalid."
|
|
4314
4992
|
},
|
|
4315
4993
|
{
|
|
4316
|
-
"part": "
|
|
4317
|
-
"description": "
|
|
4994
|
+
"part": "Calendar",
|
|
4995
|
+
"description": "Two months, stacked on narrow screens. The ends are solid accent squares, the days between a light accent band."
|
|
4318
4996
|
},
|
|
4319
4997
|
{
|
|
4320
|
-
"part": "
|
|
4321
|
-
"description": "
|
|
4998
|
+
"part": "Hint",
|
|
4999
|
+
"description": "\"Pick the first day.\" or \"Pick the last day.\", under the months, announced when it changes."
|
|
4322
5000
|
},
|
|
4323
5001
|
{
|
|
4324
|
-
"part": "
|
|
4325
|
-
"description": "
|
|
5002
|
+
"part": "Aside",
|
|
5003
|
+
"description": "An optional column beside the months, for DatePresets."
|
|
4326
5004
|
}
|
|
4327
5005
|
],
|
|
4328
5006
|
"examples": [
|
|
4329
5007
|
{
|
|
4330
|
-
"caption": "
|
|
4331
|
-
"source": "<div className=\"
|
|
5008
|
+
"caption": "Default",
|
|
5009
|
+
"source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker label=\"Stay\" />\n</div>"
|
|
4332
5010
|
},
|
|
4333
5011
|
{
|
|
4334
|
-
"caption": "
|
|
4335
|
-
"source": "<div className=\"
|
|
5012
|
+
"caption": "With DatePresets beside the months",
|
|
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>"
|
|
4336
5014
|
},
|
|
4337
5015
|
{
|
|
4338
|
-
"caption": "
|
|
4339
|
-
"source": "
|
|
5016
|
+
"caption": "Controlled: the page holds the range",
|
|
5017
|
+
"source": "function Controlled() {\n const [range, setRange] = useState<DateRange>({ start: '2026-11-09', end: '2026-11-13' })\n return (\n <div className=\"flex w-full max-w-sm flex-col gap-2\">\n <DateRangePicker label=\"Leave\" value={range} onValueChange={setRange} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n start: {range.start || '—'}, end: {range.end || '—'}\n </span>\n </div>\n )\n}\n\n<Controlled />"
|
|
5018
|
+
},
|
|
5019
|
+
{
|
|
5020
|
+
"caption": "Limits: November and December only",
|
|
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>"
|
|
4340
5022
|
}
|
|
5023
|
+
],
|
|
5024
|
+
"closed": true,
|
|
5025
|
+
"namedByFormField": [
|
|
5026
|
+
"DateRangePicker"
|
|
4341
5027
|
]
|
|
4342
5028
|
},
|
|
4343
5029
|
{
|
|
@@ -4448,7 +5134,8 @@
|
|
|
4448
5134
|
"caption": "Three columns, fewer as the container narrows",
|
|
4449
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>"
|
|
4450
5136
|
}
|
|
4451
|
-
]
|
|
5137
|
+
],
|
|
5138
|
+
"closed": true
|
|
4452
5139
|
},
|
|
4453
5140
|
{
|
|
4454
5141
|
"name": "DropdownMenu",
|
|
@@ -4728,7 +5415,8 @@
|
|
|
4728
5415
|
"caption": "From an icon button",
|
|
4729
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 />"
|
|
4730
5417
|
}
|
|
4731
|
-
]
|
|
5418
|
+
],
|
|
5419
|
+
"closed": false
|
|
4732
5420
|
},
|
|
4733
5421
|
{
|
|
4734
5422
|
"name": "EmptyState",
|
|
@@ -4873,7 +5561,8 @@
|
|
|
4873
5561
|
"caption": "Title only",
|
|
4874
5562
|
"source": "<EmptyState level={4} size=\"sm\" title=\"No returns yet\" />"
|
|
4875
5563
|
}
|
|
4876
|
-
]
|
|
5564
|
+
],
|
|
5565
|
+
"closed": false
|
|
4877
5566
|
},
|
|
4878
5567
|
{
|
|
4879
5568
|
"name": "FormField",
|
|
@@ -4924,7 +5613,7 @@
|
|
|
4924
5613
|
{
|
|
4925
5614
|
"name": "children",
|
|
4926
5615
|
"type": "ReactElement",
|
|
4927
|
-
"description": "Exactly one control: Input, Textarea, Select or
|
|
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."
|
|
4928
5617
|
},
|
|
4929
5618
|
{
|
|
4930
5619
|
"name": "className",
|
|
@@ -5036,7 +5725,8 @@
|
|
|
5036
5725
|
"caption": "A form with an error summary",
|
|
5037
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 />"
|
|
5038
5727
|
}
|
|
5039
|
-
]
|
|
5728
|
+
],
|
|
5729
|
+
"closed": true
|
|
5040
5730
|
},
|
|
5041
5731
|
{
|
|
5042
5732
|
"name": "ListItem",
|
|
@@ -5208,7 +5898,8 @@
|
|
|
5208
5898
|
"caption": "A standalone row, outside a List",
|
|
5209
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>"
|
|
5210
5900
|
}
|
|
5211
|
-
]
|
|
5901
|
+
],
|
|
5902
|
+
"closed": false
|
|
5212
5903
|
},
|
|
5213
5904
|
{
|
|
5214
5905
|
"name": "MultiSelect",
|
|
@@ -5235,7 +5926,7 @@
|
|
|
5235
5926
|
{
|
|
5236
5927
|
"name": "label",
|
|
5237
5928
|
"type": "string",
|
|
5238
|
-
"description": "
|
|
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."
|
|
5239
5930
|
},
|
|
5240
5931
|
{
|
|
5241
5932
|
"name": "options",
|
|
@@ -5391,15 +6082,15 @@
|
|
|
5391
6082
|
"examples": [
|
|
5392
6083
|
{
|
|
5393
6084
|
"caption": "Default",
|
|
5394
|
-
"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
|
|
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>"
|
|
5395
6086
|
},
|
|
5396
6087
|
{
|
|
5397
6088
|
"caption": "Controlled: the page holds the chosen options",
|
|
5398
|
-
"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
|
|
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 />"
|
|
5399
6090
|
},
|
|
5400
6091
|
{
|
|
5401
6092
|
"caption": "Invalid",
|
|
5402
|
-
"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
|
|
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>"
|
|
5403
6094
|
},
|
|
5404
6095
|
{
|
|
5405
6096
|
"caption": "Sizes",
|
|
@@ -5409,6 +6100,10 @@
|
|
|
5409
6100
|
"caption": "Disabled",
|
|
5410
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>"
|
|
5411
6102
|
}
|
|
6103
|
+
],
|
|
6104
|
+
"closed": true,
|
|
6105
|
+
"namedByFormField": [
|
|
6106
|
+
"MultiSelect"
|
|
5412
6107
|
]
|
|
5413
6108
|
},
|
|
5414
6109
|
{
|
|
@@ -5549,7 +6244,8 @@
|
|
|
5549
6244
|
"caption": "Under a breadcrumb trail",
|
|
5550
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/>"
|
|
5551
6246
|
}
|
|
5552
|
-
]
|
|
6247
|
+
],
|
|
6248
|
+
"closed": true
|
|
5553
6249
|
},
|
|
5554
6250
|
{
|
|
5555
6251
|
"name": "Pagination",
|
|
@@ -5772,7 +6468,8 @@
|
|
|
5772
6468
|
"caption": "Table footer",
|
|
5773
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 />"
|
|
5774
6470
|
}
|
|
5775
|
-
]
|
|
6471
|
+
],
|
|
6472
|
+
"closed": true
|
|
5776
6473
|
},
|
|
5777
6474
|
{
|
|
5778
6475
|
"name": "PromptInput",
|
|
@@ -5934,7 +6631,8 @@
|
|
|
5934
6631
|
"caption": "Disabled",
|
|
5935
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>"
|
|
5936
6633
|
}
|
|
5937
|
-
]
|
|
6634
|
+
],
|
|
6635
|
+
"closed": true
|
|
5938
6636
|
},
|
|
5939
6637
|
{
|
|
5940
6638
|
"name": "QuantityStepper",
|
|
@@ -5960,7 +6658,7 @@
|
|
|
5960
6658
|
{
|
|
5961
6659
|
"name": "label",
|
|
5962
6660
|
"type": "string",
|
|
5963
|
-
"description": "
|
|
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."
|
|
5964
6662
|
},
|
|
5965
6663
|
{
|
|
5966
6664
|
"name": "value",
|
|
@@ -6125,6 +6823,10 @@
|
|
|
6125
6823
|
"caption": "In a cart line",
|
|
6126
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 />"
|
|
6127
6825
|
}
|
|
6826
|
+
],
|
|
6827
|
+
"closed": true,
|
|
6828
|
+
"namedByFormField": [
|
|
6829
|
+
"QuantityStepper"
|
|
6128
6830
|
]
|
|
6129
6831
|
},
|
|
6130
6832
|
{
|
|
@@ -6295,7 +6997,8 @@
|
|
|
6295
6997
|
"caption": "Input",
|
|
6296
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 />"
|
|
6297
6999
|
}
|
|
6298
|
-
]
|
|
7000
|
+
],
|
|
7001
|
+
"closed": false
|
|
6299
7002
|
},
|
|
6300
7003
|
{
|
|
6301
7004
|
"name": "Search",
|
|
@@ -6445,7 +7148,8 @@
|
|
|
6445
7148
|
"caption": "Sizes",
|
|
6446
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>"
|
|
6447
7150
|
}
|
|
6448
|
-
]
|
|
7151
|
+
],
|
|
7152
|
+
"closed": true
|
|
6449
7153
|
},
|
|
6450
7154
|
{
|
|
6451
7155
|
"name": "SectionHeading",
|
|
@@ -6566,7 +7270,8 @@
|
|
|
6566
7270
|
"caption": "Naming its section",
|
|
6567
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>"
|
|
6568
7272
|
}
|
|
6569
|
-
]
|
|
7273
|
+
],
|
|
7274
|
+
"closed": true
|
|
6570
7275
|
},
|
|
6571
7276
|
{
|
|
6572
7277
|
"name": "SegmentedControl",
|
|
@@ -6708,7 +7413,8 @@
|
|
|
6708
7413
|
"caption": "Small, with icons, lined up with a small Button",
|
|
6709
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 />"
|
|
6710
7415
|
}
|
|
6711
|
-
]
|
|
7416
|
+
],
|
|
7417
|
+
"closed": true
|
|
6712
7418
|
},
|
|
6713
7419
|
{
|
|
6714
7420
|
"name": "Stat",
|
|
@@ -6821,7 +7527,8 @@
|
|
|
6821
7527
|
"caption": "Without a delta",
|
|
6822
7528
|
"source": "<div className=\"max-w-[10rem] w-full\">\n <Stat label=\"Kits dispatched\" value={18} padTo={3} />\n</div>"
|
|
6823
7529
|
}
|
|
6824
|
-
]
|
|
7530
|
+
],
|
|
7531
|
+
"closed": false
|
|
6825
7532
|
},
|
|
6826
7533
|
{
|
|
6827
7534
|
"name": "StatusTracker",
|
|
@@ -6986,7 +7693,8 @@
|
|
|
6986
7693
|
"caption": "Other status words",
|
|
6987
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>"
|
|
6988
7695
|
}
|
|
6989
|
-
]
|
|
7696
|
+
],
|
|
7697
|
+
"closed": true
|
|
6990
7698
|
},
|
|
6991
7699
|
{
|
|
6992
7700
|
"name": "Stepper",
|
|
@@ -7104,7 +7812,8 @@
|
|
|
7104
7812
|
"caption": "Live: move through the steps with Back and Continue",
|
|
7105
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 />"
|
|
7106
7814
|
}
|
|
7107
|
-
]
|
|
7815
|
+
],
|
|
7816
|
+
"closed": true
|
|
7108
7817
|
},
|
|
7109
7818
|
{
|
|
7110
7819
|
"name": "Tabs",
|
|
@@ -7283,6 +7992,230 @@
|
|
|
7283
7992
|
"caption": "Controlled: the page keeps track of which tab is open",
|
|
7284
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 />"
|
|
7285
7994
|
}
|
|
7995
|
+
],
|
|
7996
|
+
"closed": false
|
|
7997
|
+
},
|
|
7998
|
+
{
|
|
7999
|
+
"name": "TimeField",
|
|
8000
|
+
"title": "Time field",
|
|
8001
|
+
"tier": "molecules",
|
|
8002
|
+
"slug": "time-field",
|
|
8003
|
+
"summary": "A time of day, an add-on to the date pickers. Hours and minutes are segments you type into or step with the arrow keys, like DateField; the clock button opens a list of times every step minutes. With a 12-hour clock there is an AM/PM segment. The value is 'HH:MM' on a 24-hour clock whatever is shown. Put it next to a DatePicker for a date and a time.",
|
|
8004
|
+
"useWhen": "A pickup slot, a meeting start, a shift.",
|
|
8005
|
+
"notFor": "A duration: Input with a unit.",
|
|
8006
|
+
"source": "src/components/molecules/time-field/TimeField.tsx",
|
|
8007
|
+
"docs": "/docs/components/time-field.md",
|
|
8008
|
+
"imports": [
|
|
8009
|
+
{
|
|
8010
|
+
"names": [
|
|
8011
|
+
"TimeField",
|
|
8012
|
+
"hourCycleFor"
|
|
8013
|
+
],
|
|
8014
|
+
"from": "src/components/molecules/time-field/TimeField"
|
|
8015
|
+
}
|
|
8016
|
+
],
|
|
8017
|
+
"props": [
|
|
8018
|
+
{
|
|
8019
|
+
"name": "label",
|
|
8020
|
+
"type": "string",
|
|
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."
|
|
8022
|
+
},
|
|
8023
|
+
{
|
|
8024
|
+
"name": "value",
|
|
8025
|
+
"type": "string ('HH:MM' | '')",
|
|
8026
|
+
"description": "Controlled value, 24-hour."
|
|
8027
|
+
},
|
|
8028
|
+
{
|
|
8029
|
+
"name": "defaultValue",
|
|
8030
|
+
"type": "string",
|
|
8031
|
+
"default": "''",
|
|
8032
|
+
"description": "Starting value when the field holds it itself."
|
|
8033
|
+
},
|
|
8034
|
+
{
|
|
8035
|
+
"name": "onValueChange",
|
|
8036
|
+
"type": "(value: string) => void",
|
|
8037
|
+
"description": "Fires with a whole time, or '' when emptied."
|
|
8038
|
+
},
|
|
8039
|
+
{
|
|
8040
|
+
"name": "hourCycle",
|
|
8041
|
+
"type": "12 | 24",
|
|
8042
|
+
"description": "12 adds an AM/PM segment. The value stays 24-hour. Defaults to the locale's clock."
|
|
8043
|
+
},
|
|
8044
|
+
{
|
|
8045
|
+
"name": "step",
|
|
8046
|
+
"type": "number",
|
|
8047
|
+
"default": "30",
|
|
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."
|
|
8049
|
+
},
|
|
8050
|
+
{
|
|
8051
|
+
"name": "min",
|
|
8052
|
+
"type": "string",
|
|
8053
|
+
"description": "Earliest time, 'HH:MM'. Earlier times are left out of the list; typing one marks the field invalid."
|
|
8054
|
+
},
|
|
8055
|
+
{
|
|
8056
|
+
"name": "max",
|
|
8057
|
+
"type": "string",
|
|
8058
|
+
"description": "Latest time, 'HH:MM'."
|
|
8059
|
+
},
|
|
8060
|
+
{
|
|
8061
|
+
"name": "showSlots",
|
|
8062
|
+
"type": "boolean",
|
|
8063
|
+
"default": "true",
|
|
8064
|
+
"description": "Shows the clock button and its list."
|
|
8065
|
+
},
|
|
8066
|
+
{
|
|
8067
|
+
"name": "locale",
|
|
8068
|
+
"type": "string",
|
|
8069
|
+
"description": "Decides the default clock. Defaults to the browser's."
|
|
8070
|
+
},
|
|
8071
|
+
{
|
|
8072
|
+
"name": "disabled",
|
|
8073
|
+
"type": "boolean",
|
|
8074
|
+
"default": "false",
|
|
8075
|
+
"description": "Disables the field and the button."
|
|
8076
|
+
},
|
|
8077
|
+
{
|
|
8078
|
+
"name": "invalid",
|
|
8079
|
+
"type": "boolean",
|
|
8080
|
+
"description": "Marks the field invalid. FormField sets it."
|
|
8081
|
+
},
|
|
8082
|
+
{
|
|
8083
|
+
"name": "required",
|
|
8084
|
+
"type": "boolean",
|
|
8085
|
+
"description": "Marks the field required. FormField sets it."
|
|
8086
|
+
},
|
|
8087
|
+
{
|
|
8088
|
+
"name": "name",
|
|
8089
|
+
"type": "string",
|
|
8090
|
+
"description": "Submits the 'HH:MM' value with a form."
|
|
8091
|
+
},
|
|
8092
|
+
{
|
|
8093
|
+
"name": "id",
|
|
8094
|
+
"type": "string",
|
|
8095
|
+
"description": "The field's id. FormField sets it."
|
|
8096
|
+
},
|
|
8097
|
+
{
|
|
8098
|
+
"name": "aria-describedby",
|
|
8099
|
+
"type": "string",
|
|
8100
|
+
"description": "The ids of the help and error text. FormField sets it."
|
|
8101
|
+
},
|
|
8102
|
+
{
|
|
8103
|
+
"name": "className",
|
|
8104
|
+
"type": "string",
|
|
8105
|
+
"description": "Combined with the field's classes."
|
|
8106
|
+
},
|
|
8107
|
+
{
|
|
8108
|
+
"name": "ref",
|
|
8109
|
+
"type": "Ref<HTMLDivElement>",
|
|
8110
|
+
"description": "The field's group element."
|
|
8111
|
+
},
|
|
8112
|
+
{
|
|
8113
|
+
"name": "hourCycleFor()",
|
|
8114
|
+
"type": "(locale?: string) => 12 | 24",
|
|
8115
|
+
"description": "The locale's own clock."
|
|
8116
|
+
}
|
|
8117
|
+
],
|
|
8118
|
+
"api": [
|
|
8119
|
+
{
|
|
8120
|
+
"name": "TimeValue",
|
|
8121
|
+
"kind": "type",
|
|
8122
|
+
"code": "/** 'HH:MM', 24-hour, or '' for no time. */\nexport type TimeValue = string",
|
|
8123
|
+
"from": "src/components/molecules/time-field/TimeField"
|
|
8124
|
+
},
|
|
8125
|
+
{
|
|
8126
|
+
"name": "hourCycleFor",
|
|
8127
|
+
"kind": "function",
|
|
8128
|
+
"code": "/** 12 or 24, from the locale's own clock. */\nexport function hourCycleFor(locale?: string): 12 | 24",
|
|
8129
|
+
"from": "src/components/molecules/time-field/TimeField"
|
|
8130
|
+
}
|
|
8131
|
+
],
|
|
8132
|
+
"keyboard": [
|
|
8133
|
+
{
|
|
8134
|
+
"keys": [
|
|
8135
|
+
"0–9"
|
|
8136
|
+
],
|
|
8137
|
+
"does": "Types the hour or minute and moves on once it is full."
|
|
8138
|
+
},
|
|
8139
|
+
{
|
|
8140
|
+
"keys": [
|
|
8141
|
+
"A",
|
|
8142
|
+
"P"
|
|
8143
|
+
],
|
|
8144
|
+
"does": "On the AM/PM segment, sets AM or PM."
|
|
8145
|
+
},
|
|
8146
|
+
{
|
|
8147
|
+
"keys": [
|
|
8148
|
+
"↑",
|
|
8149
|
+
"↓"
|
|
8150
|
+
],
|
|
8151
|
+
"does": "In the field: one more or one less. In the list: the next or previous time."
|
|
8152
|
+
},
|
|
8153
|
+
{
|
|
8154
|
+
"keys": [
|
|
8155
|
+
"←",
|
|
8156
|
+
"→"
|
|
8157
|
+
],
|
|
8158
|
+
"does": "The previous or next segment."
|
|
8159
|
+
},
|
|
8160
|
+
{
|
|
8161
|
+
"keys": [
|
|
8162
|
+
"Page Up",
|
|
8163
|
+
"Page Down"
|
|
8164
|
+
],
|
|
8165
|
+
"does": "In the list: six times earlier or later."
|
|
8166
|
+
},
|
|
8167
|
+
{
|
|
8168
|
+
"keys": [
|
|
8169
|
+
"Enter",
|
|
8170
|
+
"Space"
|
|
8171
|
+
],
|
|
8172
|
+
"does": "In the list: picks the time and closes it."
|
|
8173
|
+
},
|
|
8174
|
+
{
|
|
8175
|
+
"keys": [
|
|
8176
|
+
"Esc"
|
|
8177
|
+
],
|
|
8178
|
+
"does": "Closes the list without a change."
|
|
8179
|
+
}
|
|
8180
|
+
],
|
|
8181
|
+
"dos": [
|
|
8182
|
+
"Set step to the real booking grid, such as 15 for 15-minute slots.",
|
|
8183
|
+
"Store the value with its date and the time zone it means, separately."
|
|
8184
|
+
],
|
|
8185
|
+
"donts": [
|
|
8186
|
+
"Don't parse 'HH:MM' with new Date(): keep it a string until you combine it with a date and a zone."
|
|
8187
|
+
],
|
|
8188
|
+
"anatomy": [
|
|
8189
|
+
{
|
|
8190
|
+
"part": "Field",
|
|
8191
|
+
"description": "A group named by the label: Hour, Minute and, on a 12-hour clock, AM or PM, each a spinbutton."
|
|
8192
|
+
},
|
|
8193
|
+
{
|
|
8194
|
+
"part": "Clock button",
|
|
8195
|
+
"description": "A ghost IconButton. Its name includes the time: \"Choose time, 14:30\"."
|
|
8196
|
+
},
|
|
8197
|
+
{
|
|
8198
|
+
"part": "Time list",
|
|
8199
|
+
"description": "A listbox of times between min and max, every step minutes. It opens on the picked time, or the next slot after now."
|
|
8200
|
+
}
|
|
8201
|
+
],
|
|
8202
|
+
"examples": [
|
|
8203
|
+
{
|
|
8204
|
+
"caption": "Default, 24-hour",
|
|
8205
|
+
"source": "<div className=\"w-36\">\n <TimeField label=\"Start time\" hourCycle={24} />\n</div>"
|
|
8206
|
+
},
|
|
8207
|
+
{
|
|
8208
|
+
"caption": "12-hour, with AM and PM",
|
|
8209
|
+
"source": "<div className=\"w-40\">\n <TimeField label=\"Call time\" hourCycle={12} defaultValue=\"14:30\" />\n</div>"
|
|
8210
|
+
},
|
|
8211
|
+
{
|
|
8212
|
+
"caption": "With a DatePicker: a date and a time",
|
|
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 />"
|
|
8214
|
+
}
|
|
8215
|
+
],
|
|
8216
|
+
"closed": true,
|
|
8217
|
+
"namedByFormField": [
|
|
8218
|
+
"TimeField"
|
|
7286
8219
|
]
|
|
7287
8220
|
},
|
|
7288
8221
|
{
|
|
@@ -7437,7 +8370,8 @@
|
|
|
7437
8370
|
"caption": "Without groups",
|
|
7438
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>"
|
|
7439
8372
|
}
|
|
7440
|
-
]
|
|
8373
|
+
],
|
|
8374
|
+
"closed": true
|
|
7441
8375
|
},
|
|
7442
8376
|
{
|
|
7443
8377
|
"name": "Toast",
|
|
@@ -7584,7 +8518,8 @@
|
|
|
7584
8518
|
"caption": "With an action",
|
|
7585
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 />"
|
|
7586
8520
|
}
|
|
7587
|
-
]
|
|
8521
|
+
],
|
|
8522
|
+
"closed": false
|
|
7588
8523
|
},
|
|
7589
8524
|
{
|
|
7590
8525
|
"name": "ToolCall",
|
|
@@ -7715,7 +8650,8 @@
|
|
|
7715
8650
|
"caption": "Without a title, details open",
|
|
7716
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>"
|
|
7717
8652
|
}
|
|
7718
|
-
]
|
|
8653
|
+
],
|
|
8654
|
+
"closed": true
|
|
7719
8655
|
},
|
|
7720
8656
|
{
|
|
7721
8657
|
"name": "AlertDialog",
|
|
@@ -7936,7 +8872,8 @@
|
|
|
7936
8872
|
"caption": "Type the name to confirm, for the costliest actions",
|
|
7937
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 />"
|
|
7938
8874
|
}
|
|
7939
|
-
]
|
|
8875
|
+
],
|
|
8876
|
+
"closed": true
|
|
7940
8877
|
},
|
|
7941
8878
|
{
|
|
7942
8879
|
"name": "AuthForm",
|
|
@@ -8090,7 +9027,8 @@
|
|
|
8090
9027
|
"caption": "Two-step sign-in with a code",
|
|
8091
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} />"
|
|
8092
9029
|
}
|
|
8093
|
-
]
|
|
9030
|
+
],
|
|
9031
|
+
"closed": true
|
|
8094
9032
|
},
|
|
8095
9033
|
{
|
|
8096
9034
|
"name": "Carousel",
|
|
@@ -8217,7 +9155,8 @@
|
|
|
8217
9155
|
"caption": "Three short items: both buttons do nothing while everything fits",
|
|
8218
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>"
|
|
8219
9157
|
}
|
|
8220
|
-
]
|
|
9158
|
+
],
|
|
9159
|
+
"closed": true
|
|
8221
9160
|
},
|
|
8222
9161
|
{
|
|
8223
9162
|
"name": "Cart",
|
|
@@ -8395,7 +9334,8 @@
|
|
|
8395
9334
|
"caption": "Empty",
|
|
8396
9335
|
"source": "<Cart lines={[]} onQuantityChange={() => {}} onRemove={() => {}} headingLevel={4} title=\"Saved for later\" />"
|
|
8397
9336
|
}
|
|
8398
|
-
]
|
|
9337
|
+
],
|
|
9338
|
+
"closed": true
|
|
8399
9339
|
},
|
|
8400
9340
|
{
|
|
8401
9341
|
"name": "ChartPanel",
|
|
@@ -8637,7 +9577,8 @@
|
|
|
8637
9577
|
"caption": "Radar, two series",
|
|
8638
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/>"
|
|
8639
9579
|
}
|
|
8640
|
-
]
|
|
9580
|
+
],
|
|
9581
|
+
"closed": true
|
|
8641
9582
|
},
|
|
8642
9583
|
{
|
|
8643
9584
|
"name": "ChatThread",
|
|
@@ -8763,7 +9704,8 @@
|
|
|
8763
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",
|
|
8764
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 />"
|
|
8765
9706
|
}
|
|
8766
|
-
]
|
|
9707
|
+
],
|
|
9708
|
+
"closed": true
|
|
8767
9709
|
},
|
|
8768
9710
|
{
|
|
8769
9711
|
"name": "CheckoutForm",
|
|
@@ -8941,7 +9883,8 @@
|
|
|
8941
9883
|
"caption": "Three steps, a demo order",
|
|
8942
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} />"
|
|
8943
9885
|
}
|
|
8944
|
-
]
|
|
9886
|
+
],
|
|
9887
|
+
"closed": true
|
|
8945
9888
|
},
|
|
8946
9889
|
{
|
|
8947
9890
|
"name": "CommandPalette",
|
|
@@ -8967,7 +9910,7 @@
|
|
|
8967
9910
|
{
|
|
8968
9911
|
"name": "commands",
|
|
8969
9912
|
"type": "Command[]",
|
|
8970
|
-
"description": "Required. Each has id, label, group, keywords, shortcut, icon and onSelect."
|
|
9913
|
+
"description": "Required. Each has id, label, description (a quieter second line), group, keywords, shortcut, icon and onSelect."
|
|
8971
9914
|
},
|
|
8972
9915
|
{
|
|
8973
9916
|
"name": "open",
|
|
@@ -9015,6 +9958,12 @@
|
|
|
9015
9958
|
"default": "'No matching commands'",
|
|
9016
9959
|
"description": "Shown when nothing matches the search."
|
|
9017
9960
|
},
|
|
9961
|
+
{
|
|
9962
|
+
"name": "filter",
|
|
9963
|
+
"type": "(commands: readonly Command[], query: string) => Command[]",
|
|
9964
|
+
"default": "filterCommands",
|
|
9965
|
+
"description": "Your own matching and ranking. Gets every command and the query, returns the ones to show in order; groups follow that order. The docs site search uses it to rank with the same scorer as the MCP server."
|
|
9966
|
+
},
|
|
9018
9967
|
{
|
|
9019
9968
|
"name": "ref",
|
|
9020
9969
|
"type": "Ref<HTMLInputElement>",
|
|
@@ -9025,7 +9974,7 @@
|
|
|
9025
9974
|
{
|
|
9026
9975
|
"name": "Command",
|
|
9027
9976
|
"kind": "type",
|
|
9028
|
-
"code": "export type Command = {\n id: string\n label: string\n /** Commands with the same group are drawn together under its heading. */\n group?: string\n /** Extra words a search matches on. */\n keywords?: string[]\n /** A hint shown on the option, e.g. \"N\". Display only: the palette does not bind it. */\n shortcut?: string\n /** Drawn aria-hidden; the label carries the meaning. */\n icon?: ReactNode\n onSelect: () => void\n}",
|
|
9977
|
+
"code": "export type Command = {\n id: string\n label: string\n /** A second, quieter line under the label, e.g. what a search result is for. */\n description?: string\n /** Commands with the same group are drawn together under its heading. */\n group?: string\n /** Extra words a search matches on. */\n keywords?: string[]\n /** A hint shown on the option, e.g. \"N\". Display only: the palette does not bind it. */\n shortcut?: string\n /** Drawn aria-hidden; the label carries the meaning. */\n icon?: ReactNode\n onSelect: () => void\n}",
|
|
9029
9978
|
"from": "src/components/organisms/command-palette/CommandPalette"
|
|
9030
9979
|
},
|
|
9031
9980
|
{
|
|
@@ -9137,7 +10086,8 @@
|
|
|
9137
10086
|
"caption": "Store commands",
|
|
9138
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 />"
|
|
9139
10088
|
}
|
|
9140
|
-
]
|
|
10089
|
+
],
|
|
10090
|
+
"closed": true
|
|
9141
10091
|
},
|
|
9142
10092
|
{
|
|
9143
10093
|
"name": "DataTable",
|
|
@@ -9359,7 +10309,8 @@
|
|
|
9359
10309
|
"caption": "Empty",
|
|
9360
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.\" />"
|
|
9361
10311
|
}
|
|
9362
|
-
]
|
|
10312
|
+
],
|
|
10313
|
+
"closed": true
|
|
9363
10314
|
},
|
|
9364
10315
|
{
|
|
9365
10316
|
"name": "Dialog",
|
|
@@ -9531,7 +10482,8 @@
|
|
|
9531
10482
|
"caption": "Long content",
|
|
9532
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 />"
|
|
9533
10484
|
}
|
|
9534
|
-
]
|
|
10485
|
+
],
|
|
10486
|
+
"closed": true
|
|
9535
10487
|
},
|
|
9536
10488
|
{
|
|
9537
10489
|
"name": "Footer",
|
|
@@ -9745,7 +10697,8 @@
|
|
|
9745
10697
|
"caption": "With a wordmark and a backdrop: a layer behind the letters, with headroom over them",
|
|
9746
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} />"
|
|
9747
10699
|
}
|
|
9748
|
-
]
|
|
10700
|
+
],
|
|
10701
|
+
"closed": true
|
|
9749
10702
|
},
|
|
9750
10703
|
{
|
|
9751
10704
|
"name": "KanbanBoard",
|
|
@@ -9996,7 +10949,8 @@
|
|
|
9996
10949
|
"caption": "Work orders, with an empty column",
|
|
9997
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 />"
|
|
9998
10951
|
}
|
|
9999
|
-
]
|
|
10952
|
+
],
|
|
10953
|
+
"closed": true
|
|
10000
10954
|
},
|
|
10001
10955
|
{
|
|
10002
10956
|
"name": "Navbar",
|
|
@@ -10134,7 +11088,8 @@
|
|
|
10134
11088
|
"caption": "A narrow container",
|
|
10135
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>"
|
|
10136
11090
|
}
|
|
10137
|
-
]
|
|
11091
|
+
],
|
|
11092
|
+
"closed": true
|
|
10138
11093
|
},
|
|
10139
11094
|
{
|
|
10140
11095
|
"name": "NewsletterForm",
|
|
@@ -10286,7 +11241,8 @@
|
|
|
10286
11241
|
"caption": "A sign-up that fails",
|
|
10287
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>"
|
|
10288
11243
|
}
|
|
10289
|
-
]
|
|
11244
|
+
],
|
|
11245
|
+
"closed": true
|
|
10290
11246
|
},
|
|
10291
11247
|
{
|
|
10292
11248
|
"name": "ProductCard",
|
|
@@ -10460,7 +11416,8 @@
|
|
|
10460
11416
|
"caption": "Nothing matches",
|
|
10461
11417
|
"source": "<ProductGrid label=\"Hexline range, filtered\" products={[]} emptyMessage=\"No products match these filters.\" />"
|
|
10462
11418
|
}
|
|
10463
|
-
]
|
|
11419
|
+
],
|
|
11420
|
+
"closed": true
|
|
10464
11421
|
},
|
|
10465
11422
|
{
|
|
10466
11423
|
"name": "Sheet",
|
|
@@ -10615,7 +11572,8 @@
|
|
|
10615
11572
|
"caption": "From the left",
|
|
10616
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 />"
|
|
10617
11574
|
}
|
|
10618
|
-
]
|
|
11575
|
+
],
|
|
11576
|
+
"closed": true
|
|
10619
11577
|
},
|
|
10620
11578
|
{
|
|
10621
11579
|
"name": "Sidebar",
|
|
@@ -10748,7 +11706,8 @@
|
|
|
10748
11706
|
"caption": "In a sheet on a narrow screen: picking a link closes the sheet, through renderLink",
|
|
10749
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>"
|
|
10750
11708
|
}
|
|
10751
|
-
]
|
|
11709
|
+
],
|
|
11710
|
+
"closed": true
|
|
10752
11711
|
}
|
|
10753
11712
|
]
|
|
10754
11713
|
}
|