division-ds 0.3.0 → 0.4.0
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 +2 -2
- 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 +58 -43
- 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/code-block.md +106 -0
- package/public/docs/components/command-palette.md +1 -1
- package/public/docs/components/data-table.md +1 -1
- package/public/docs/components/date-picker.md +3 -3
- package/public/docs/components/date-presets.md +1 -1
- package/public/docs/components/date-range-picker.md +3 -3
- 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 +2 -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 +25 -4
- 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 +55 -4
- 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/panel.md +139 -0
- package/public/docs/components/product-card.md +1 -1
- package/public/docs/components/progress.md +2 -2
- 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 +5 -5
- 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 +516 -92
- package/public/manifest/tokens.json +10 -7
- package/scripts/lib/agent-docs.mjs +1 -1
- package/scripts/lib/props-validator.mjs +36 -2
- package/skills/division-ds/SKILL.md +1 -1
- package/division-ds-0.3.0.tgz +0 -0
|
@@ -118,7 +118,8 @@
|
|
|
118
118
|
"caption": "With an explanation and sources",
|
|
119
119
|
"source": "<div className=\"flex flex-col gap-2 max-w-md\">\n <AiLabel\n label=\"AI summary\"\n explanation=\"Summarised from the three most recent reviews of this replica. It can miss details or get them wrong: verify before you rely on it.\"\n sources={[\n { label: 'Review by Ilse Varga', href: '#review-1' },\n { label: 'Review by Tomas Brandt', href: '#review-2' },\n ]}\n />\n <p className=\"text-sm text-[var(--color-text-primary)]\">\n Reviewers find the gearbox quiet and the hop-up easy to tune; two mention a loose stock.\n </p>\n</div>"
|
|
120
120
|
}
|
|
121
|
-
]
|
|
121
|
+
],
|
|
122
|
+
"closed": false
|
|
122
123
|
},
|
|
123
124
|
{
|
|
124
125
|
"name": "Avatar",
|
|
@@ -256,7 +257,8 @@
|
|
|
256
257
|
"caption": "A row",
|
|
257
258
|
"source": "const FIELD_TEAM = [\n { name: 'Dana Iverson', role: 'Route Lead' },\n { name: 'Ravi Okonkwo', role: 'Comms' },\n { name: 'Mei Tanaka', role: 'Medic' },\n { name: 'Sana Idrissi', role: 'Logistics' },\n]\n\n<div className=\"flex flex-col gap-3\">\n {FIELD_TEAM.map(member => (\n <div key={member.name} className=\"flex items-center gap-3\">\n <Avatar name={member.name} size=\"md\" />\n <div>\n <p className=\"text-sm\">{member.name}</p>\n <p className=\"text-xs text-[var(--color-text-muted)]\">{member.role}</p>\n </div>\n </div>\n ))}\n</div>"
|
|
258
259
|
}
|
|
259
|
-
]
|
|
260
|
+
],
|
|
261
|
+
"closed": false
|
|
260
262
|
},
|
|
261
263
|
{
|
|
262
264
|
"name": "Badge",
|
|
@@ -395,7 +397,8 @@
|
|
|
395
397
|
"caption": "In a row",
|
|
396
398
|
"source": "<div className=\"flex flex-col gap-2 w-full max-w-xs\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">Depot Relay</span>\n <Badge tone=\"ok\" emphasis=\"subtle\">\n Nominal\n </Badge>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">North Sensor Array</span>\n <Badge tone=\"warning\" emphasis=\"subtle\">\n Degraded\n </Badge>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">Northgate Beacon</span>\n <Badge tone=\"hostile\" emphasis=\"solid\">\n Offline\n </Badge>\n </div>\n</div>"
|
|
397
399
|
}
|
|
398
|
-
]
|
|
400
|
+
],
|
|
401
|
+
"closed": false
|
|
399
402
|
},
|
|
400
403
|
{
|
|
401
404
|
"name": "Button",
|
|
@@ -604,7 +607,8 @@
|
|
|
604
607
|
"caption": "Focus ring is never clipped",
|
|
605
608
|
"source": "<div className=\"flex flex-col gap-4\">\n <p className=\"text-sm text-[var(--color-text-secondary)] max-w-md\">\n Press Tab to focus the button below, in either theme. The focus ring (the outline\n that shows which element the keyboard is on) draws a clean rectangle around the whole\n bevelled shape. You'll never see it cut off, because the component draws it on the\n outer wrapper, which isn't clipped, and not on the bevelled button itself.\n </p>\n <Button variant=\"primary\">Dispatch team</Button>\n</div>"
|
|
606
609
|
}
|
|
607
|
-
]
|
|
610
|
+
],
|
|
611
|
+
"closed": false
|
|
608
612
|
},
|
|
609
613
|
{
|
|
610
614
|
"name": "Checkbox",
|
|
@@ -744,7 +748,8 @@
|
|
|
744
748
|
"caption": "Invalid",
|
|
745
749
|
"source": "<div className=\"flex items-center gap-2\">\n <Checkbox id=\"state-invalid\" invalid />\n <label htmlFor=\"state-invalid\">Confirm waiver on file</label>\n</div>"
|
|
746
750
|
}
|
|
747
|
-
]
|
|
751
|
+
],
|
|
752
|
+
"closed": false
|
|
748
753
|
},
|
|
749
754
|
{
|
|
750
755
|
"name": "Divider",
|
|
@@ -835,7 +840,8 @@
|
|
|
835
840
|
"caption": "Drawing on",
|
|
836
841
|
"source": "<div className=\"flex flex-col gap-4 w-full max-w-sm\">\n {/* The Button atom, not a bare <button>: a hand-rolled one here had no system focus\n ring at all (the UA default, rgb(16, 16, 16) auto 1px) and no type, so it defaulted\n to submit. */}\n <Button variant=\"secondary\" size=\"sm\" wrapperClassName=\"w-fit\" onClick={() => setReplayKey(k => k + 1)}>\n Replay\n </Button>\n <Divider key={replayKey} className=\"anim-draw-on\" />\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n This animation is switched off completely when someone has asked their device for\n less motion (the prefers-reduced-motion: reduce setting).\n </p>\n</div>"
|
|
837
842
|
}
|
|
838
|
-
]
|
|
843
|
+
],
|
|
844
|
+
"closed": false
|
|
839
845
|
},
|
|
840
846
|
{
|
|
841
847
|
"name": "IconButton",
|
|
@@ -1000,7 +1006,8 @@
|
|
|
1000
1006
|
"caption": "Disabled",
|
|
1001
1007
|
"source": "<IconButton label=\"Next step\" disabled>\n <ChevronRight aria-hidden=\"true\" />\n</IconButton>"
|
|
1002
1008
|
}
|
|
1003
|
-
]
|
|
1009
|
+
],
|
|
1010
|
+
"closed": false
|
|
1004
1011
|
},
|
|
1005
1012
|
{
|
|
1006
1013
|
"name": "Input",
|
|
@@ -1197,7 +1204,8 @@
|
|
|
1197
1204
|
"caption": "Labelled the manual way",
|
|
1198
1205
|
"source": "<div className=\"flex flex-col gap-1 text-sm max-w-xs\">\n <label htmlFor=\"input-docs-callsign\">Call sign</label>\n <Input id=\"input-docs-callsign\" aria-describedby=\"input-docs-callsign-help\" />\n <p id=\"input-docs-callsign-help\" className=\"text-xs text-[var(--color-text-secondary)]\">\n Letters, numbers and a hyphen. FormField will set this pattern up for you.\n </p>\n</div>"
|
|
1199
1206
|
}
|
|
1200
|
-
]
|
|
1207
|
+
],
|
|
1208
|
+
"closed": false
|
|
1201
1209
|
},
|
|
1202
1210
|
{
|
|
1203
1211
|
"name": "Kbd",
|
|
@@ -1223,6 +1231,18 @@
|
|
|
1223
1231
|
"type": "ReactNode",
|
|
1224
1232
|
"description": "The key's name, for example \"Esc\" or \"⌘\"."
|
|
1225
1233
|
},
|
|
1234
|
+
{
|
|
1235
|
+
"name": "size",
|
|
1236
|
+
"type": "'md' | 'sm'",
|
|
1237
|
+
"default": "'md'",
|
|
1238
|
+
"description": "md is 24px tall, for a cap beside text or in a menu row. sm is 20px tall, for a cap inside a 32px control: a small Button or a search field."
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
"name": "cut",
|
|
1242
|
+
"type": "boolean",
|
|
1243
|
+
"default": "false",
|
|
1244
|
+
"description": "Cuts the bottom-right corner (5px at sm, 8px at md) so the cap nests in the corner of a bevelled control, its diagonal parallel to the control's. Only for a cap inside such a control."
|
|
1245
|
+
},
|
|
1226
1246
|
{
|
|
1227
1247
|
"name": "className",
|
|
1228
1248
|
"type": "string",
|
|
@@ -1243,10 +1263,11 @@
|
|
|
1243
1263
|
"dos": [
|
|
1244
1264
|
"Use one Kbd per physical key, and put them side by side for a chord (keys pressed together).",
|
|
1245
1265
|
"Put it next to the action it triggers, in the same row or the same tooltip.",
|
|
1246
|
-
"Write the key name the way the keyboard prints it."
|
|
1266
|
+
"Write the key name the way the keyboard prints it.",
|
|
1267
|
+
"Inside a 32px control, use size=\"sm\"; inside a bevelled one (a Button), add cut and place the cap at the end, near the cut corner."
|
|
1247
1268
|
],
|
|
1248
1269
|
"donts": [
|
|
1249
|
-
"Don’t
|
|
1270
|
+
"Don’t set cut on a cap that stands alone in text or a menu row. The cut answers a bevelled corner around it; without one it reads as a button.",
|
|
1250
1271
|
"Don’t put a whole chord inside one Kbd (\"Ctrl+K\" in a single cap).",
|
|
1251
1272
|
"Don’t use it for anything that isn’t a key. It’s not a badge."
|
|
1252
1273
|
],
|
|
@@ -1257,11 +1278,11 @@
|
|
|
1257
1278
|
},
|
|
1258
1279
|
{
|
|
1259
1280
|
"part": "Hairline border",
|
|
1260
|
-
"description": "A one-pixel border in border.hairline-strong
|
|
1281
|
+
"description": "A one-pixel border in border.hairline-strong. Square corners by default; with cut, the bottom-right corner is cut at 45 degrees and the border follows the diagonal."
|
|
1261
1282
|
},
|
|
1262
1283
|
{
|
|
1263
1284
|
"part": "Mono legend",
|
|
1264
|
-
"description": "JetBrains Mono, uppercase, 11px (pixels): the tertiary type role."
|
|
1285
|
+
"description": "JetBrains Mono, uppercase, 11px (pixels): the tertiary type role, at both sizes."
|
|
1265
1286
|
}
|
|
1266
1287
|
],
|
|
1267
1288
|
"examples": [
|
|
@@ -1272,8 +1293,17 @@
|
|
|
1272
1293
|
{
|
|
1273
1294
|
"caption": "A chord: one cap per key",
|
|
1274
1295
|
"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>"
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
"caption": "Sizes",
|
|
1299
|
+
"source": "<Kbd>Esc</Kbd>\n<Kbd size=\"sm\">Esc</Kbd>"
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
"caption": "Small and cut, inside a small button",
|
|
1303
|
+
"source": "<Button variant=\"secondary\" size=\"sm\" iconStart={<Search />} aria-keyshortcuts=\"Control+K\">\n Search\n <Kbd aria-hidden=\"true\" size=\"sm\" cut className=\"-mr-1.5 ml-2 -translate-y-[var(--cap-shift,0px)]\">\n Ctrl K\n </Kbd>\n</Button>"
|
|
1275
1304
|
}
|
|
1276
|
-
]
|
|
1305
|
+
],
|
|
1306
|
+
"closed": false
|
|
1277
1307
|
},
|
|
1278
1308
|
{
|
|
1279
1309
|
"name": "Link",
|
|
@@ -1393,7 +1423,8 @@
|
|
|
1393
1423
|
"caption": "External",
|
|
1394
1424
|
"source": "<Link href=\"https://example.com\" external>\n Supplier\n</Link>"
|
|
1395
1425
|
}
|
|
1396
|
-
]
|
|
1426
|
+
],
|
|
1427
|
+
"closed": false
|
|
1397
1428
|
},
|
|
1398
1429
|
{
|
|
1399
1430
|
"name": "LoadingAnimation",
|
|
@@ -1490,7 +1521,157 @@
|
|
|
1490
1521
|
"caption": "Aperture, three sizes",
|
|
1491
1522
|
"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
1523
|
}
|
|
1493
|
-
]
|
|
1524
|
+
],
|
|
1525
|
+
"closed": false
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
"name": "Panel",
|
|
1529
|
+
"title": "Panel",
|
|
1530
|
+
"tier": "atoms",
|
|
1531
|
+
"slug": "panel",
|
|
1532
|
+
"summary": "The system's framed surface: a thin border around a fill, with the crosshair corner marks and the cut corner as options. Card is a Panel with a title, body and footer; Panel is the frame alone, for a feature tile, a demo frame or a tile that is one link.",
|
|
1533
|
+
"useWhen": "Framing content that has no title, body and footer structure: a feature tile, a demo area, or a tile that is one link.",
|
|
1534
|
+
"notFor": "Content with a title, a body and actions: use Card. A page-width layout wrapper: use a plain div with max-w-*.",
|
|
1535
|
+
"source": "src/components/atoms/panel/Panel.tsx",
|
|
1536
|
+
"docs": "/docs/components/panel.md",
|
|
1537
|
+
"imports": [
|
|
1538
|
+
{
|
|
1539
|
+
"names": [
|
|
1540
|
+
"Panel"
|
|
1541
|
+
],
|
|
1542
|
+
"from": "src/components/atoms/panel/Panel"
|
|
1543
|
+
}
|
|
1544
|
+
],
|
|
1545
|
+
"props": [
|
|
1546
|
+
{
|
|
1547
|
+
"name": "padding",
|
|
1548
|
+
"type": "'none' | 'sm' | 'md' | 'lg'",
|
|
1549
|
+
"default": "'md'",
|
|
1550
|
+
"description": "Inner spacing: 0, 12px, 20px or 32px."
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
"name": "tone",
|
|
1554
|
+
"type": "'base' | 'raised' | 'signal'",
|
|
1555
|
+
"default": "'raised'",
|
|
1556
|
+
"description": "base sits flush with the page, raised is one step up, signal is the brand wash for the one panel in a group that leads."
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
"name": "crosshair",
|
|
1560
|
+
"type": "boolean",
|
|
1561
|
+
"default": "false",
|
|
1562
|
+
"description": "Draws the crosshair marks at the corners."
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
"name": "cut",
|
|
1566
|
+
"type": "boolean",
|
|
1567
|
+
"default": "false",
|
|
1568
|
+
"description": "Cuts the bottom-right corner, with the border following the cut."
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
"name": "interactive",
|
|
1572
|
+
"type": "boolean",
|
|
1573
|
+
"default": "false",
|
|
1574
|
+
"description": "Hover and focus states for a panel that is one link or button. Use it with asChild; it adds no behaviour of its own."
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
"name": "asChild",
|
|
1578
|
+
"type": "boolean",
|
|
1579
|
+
"default": "false",
|
|
1580
|
+
"description": "Renders the single child element (an a, a router Link, a button) with the panel classes merged onto it, instead of a div."
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
"name": "className",
|
|
1584
|
+
"type": "string",
|
|
1585
|
+
"description": "Merged with the component's classes. Use it for layout (a grid span, flex, gap), not for the fill or the border."
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
"name": "ref",
|
|
1589
|
+
"type": "Ref<HTMLDivElement>",
|
|
1590
|
+
"description": "Passed through to the div, or to the child with asChild."
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
"name": "...rest",
|
|
1594
|
+
"type": "HTMLAttributes<HTMLDivElement>",
|
|
1595
|
+
"description": "Every native attribute, passed onto the element."
|
|
1596
|
+
}
|
|
1597
|
+
],
|
|
1598
|
+
"keyboard": [
|
|
1599
|
+
{
|
|
1600
|
+
"keys": [
|
|
1601
|
+
"Tab"
|
|
1602
|
+
],
|
|
1603
|
+
"does": "With interactive and asChild on a link or button: moves focus to the panel and shows the focus ring."
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
"keys": [
|
|
1607
|
+
"Enter"
|
|
1608
|
+
],
|
|
1609
|
+
"does": "Follows the link, or presses the button, that the panel was rendered as."
|
|
1610
|
+
}
|
|
1611
|
+
],
|
|
1612
|
+
"dos": [
|
|
1613
|
+
"Use signal on at most one panel in a group: the one that leads.",
|
|
1614
|
+
"Give a clickable panel interactive and asChild, with a real link or button as the child.",
|
|
1615
|
+
"Leave 8px around a panel with the crosshair, so a parent that clips does not cut the marks off."
|
|
1616
|
+
],
|
|
1617
|
+
"donts": [
|
|
1618
|
+
"Don’t put a button near the bottom-right corner of a cut panel; the cut can clip it.",
|
|
1619
|
+
"Don’t set a fill or border colour with a class. Choose a tone.",
|
|
1620
|
+
"Don’t put a second link or button inside an interactive panel."
|
|
1621
|
+
],
|
|
1622
|
+
"pitfalls": [
|
|
1623
|
+
{
|
|
1624
|
+
"wrong": "<Panel className=\"bg-[var(--color-surface-base)] border-[var(--color-border-selection)]\">",
|
|
1625
|
+
"right": "<Panel tone=\"base\">",
|
|
1626
|
+
"why": "The panel's fill and border come from its tone, through CSS that sits outside Tailwind's layers, so a bg- or border- class never wins. Pick the tone."
|
|
1627
|
+
},
|
|
1628
|
+
{
|
|
1629
|
+
"wrong": "<Panel interactive onClick={open}>Replicas</Panel>",
|
|
1630
|
+
"right": "<Panel interactive asChild><Link to=\"/replicas\">Replicas</Link></Panel>",
|
|
1631
|
+
"why": "A div with a click handler cannot be reached by keyboard and is not announced as a link. interactive styles a panel that already is a link or a button; asChild makes it one."
|
|
1632
|
+
}
|
|
1633
|
+
],
|
|
1634
|
+
"anatomy": [
|
|
1635
|
+
{
|
|
1636
|
+
"part": "Frame",
|
|
1637
|
+
"description": "A div (or, with asChild, your element) with the .panel class: a one-pixel border and a fill, both set by the tone."
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
"part": "Tone",
|
|
1641
|
+
"description": "base: surface.base fill, border.hairline border. raised: surface.raised fill, border.hairline border. signal: accent.signal.wash fill, border.selection border. All tokens, so each one is right in both themes."
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
"part": "Crosshair",
|
|
1645
|
+
"description": "Optional. Short marks drawn 5px outside each corner, in text.muted. The parent must not clip its overflow."
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
"part": "Cut corner",
|
|
1649
|
+
"description": "Optional. The bottom-right corner cut at 45 degrees (14px), with the border drawn along the cut, so the outline does not break at that corner. With the crosshair on, the bottom-right marks fall inside the cut and are not drawn."
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
"part": "Interactive state",
|
|
1653
|
+
"description": "Optional. On hover and on keyboard focus the border, diagonal included, turns accent.signal.indicator; keyboard focus also draws the focus ring 2px outside. Nothing moves."
|
|
1654
|
+
}
|
|
1655
|
+
],
|
|
1656
|
+
"examples": [
|
|
1657
|
+
{
|
|
1658
|
+
"caption": "Tones",
|
|
1659
|
+
"source": "<Panel tone=\"base\">\n <p className=\"font-display font-semibold\">Base</p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">Flush with the page.</p>\n</Panel>\n<Panel tone=\"raised\">\n <p className=\"font-display font-semibold\">Raised</p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">One step up.</p>\n</Panel>\n<Panel tone=\"signal\">\n <p className=\"font-display font-semibold\">Signal</p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">The one that leads.</p>\n</Panel>"
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
"caption": "Padding",
|
|
1663
|
+
"source": "<Panel padding=\"none\">none</Panel>\n<Panel padding=\"sm\">sm</Panel>\n<Panel padding=\"md\">md</Panel>\n<Panel padding=\"lg\">lg</Panel>"
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
"caption": "Crosshair and cut corner",
|
|
1667
|
+
"source": "<Panel crosshair>Crosshair</Panel>\n<Panel cut>Cut corner</Panel>\n<Panel crosshair cut tone=\"signal\">Both</Panel>"
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
"caption": "Interactive, as a router link",
|
|
1671
|
+
"source": "<Panel interactive cut padding=\"lg\" asChild>\n <RouterLink to=\"/components/card\" className=\"flex flex-col gap-3\">\n <span className=\"font-display text-lg font-semibold\">Card</span>\n <span className=\"text-sm text-[var(--color-text-secondary)]\">A panel with a title, a body and a footer.</span>\n <span className=\"inline-flex items-center gap-2 font-semibold text-[var(--color-accent-signal-text)]\">\n Open <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n </span>\n </RouterLink>\n</Panel>"
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
"closed": false
|
|
1494
1675
|
},
|
|
1495
1676
|
{
|
|
1496
1677
|
"name": "Progress",
|
|
@@ -1537,7 +1718,7 @@
|
|
|
1537
1718
|
"name": "tone",
|
|
1538
1719
|
"type": "'signal' | 'ok' | 'warning' | 'hostile'",
|
|
1539
1720
|
"default": "'signal'",
|
|
1540
|
-
"description": "The fill colour. signal paints accent.signal.indicator to match Slider. On the track in light theme that’s a 2.
|
|
1721
|
+
"description": "The fill colour. signal paints accent.signal.indicator to match Slider. On the track in light theme that’s a 2.80:1 contrast ratio, below the 3:1 a bar needs when it’s the only sign of progress. So set showValue there."
|
|
1541
1722
|
},
|
|
1542
1723
|
{
|
|
1543
1724
|
"name": "className",
|
|
@@ -1610,7 +1791,8 @@
|
|
|
1610
1791
|
"caption": "Tones",
|
|
1611
1792
|
"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
1793
|
}
|
|
1613
|
-
]
|
|
1794
|
+
],
|
|
1795
|
+
"closed": false
|
|
1614
1796
|
},
|
|
1615
1797
|
{
|
|
1616
1798
|
"name": "Radio",
|
|
@@ -1785,7 +1967,8 @@
|
|
|
1785
1967
|
"caption": "Disabled option",
|
|
1786
1968
|
"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
1969
|
}
|
|
1788
|
-
]
|
|
1970
|
+
],
|
|
1971
|
+
"closed": false
|
|
1789
1972
|
},
|
|
1790
1973
|
{
|
|
1791
1974
|
"name": "ScoreRing",
|
|
@@ -1903,7 +2086,8 @@
|
|
|
1903
2086
|
"caption": "The band boundaries: 49, 50, 89 and 90",
|
|
1904
2087
|
"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
2088
|
}
|
|
1906
|
-
]
|
|
2089
|
+
],
|
|
2090
|
+
"closed": false
|
|
1907
2091
|
},
|
|
1908
2092
|
{
|
|
1909
2093
|
"name": "Select",
|
|
@@ -1930,7 +2114,7 @@
|
|
|
1930
2114
|
{
|
|
1931
2115
|
"name": "label",
|
|
1932
2116
|
"type": "string",
|
|
1933
|
-
"description": "
|
|
2117
|
+
"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
2118
|
},
|
|
1935
2119
|
{
|
|
1936
2120
|
"name": "options",
|
|
@@ -2165,6 +2349,10 @@
|
|
|
2165
2349
|
"caption": "Beside an Input",
|
|
2166
2350
|
"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
2351
|
}
|
|
2352
|
+
],
|
|
2353
|
+
"closed": false,
|
|
2354
|
+
"namedByFormField": [
|
|
2355
|
+
"Select"
|
|
2168
2356
|
]
|
|
2169
2357
|
},
|
|
2170
2358
|
{
|
|
@@ -2301,7 +2489,8 @@
|
|
|
2301
2489
|
"caption": "A profile row loading",
|
|
2302
2490
|
"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
2491
|
}
|
|
2304
|
-
]
|
|
2492
|
+
],
|
|
2493
|
+
"closed": false
|
|
2305
2494
|
},
|
|
2306
2495
|
{
|
|
2307
2496
|
"name": "Slider",
|
|
@@ -2477,7 +2666,8 @@
|
|
|
2477
2666
|
"caption": "Disabled",
|
|
2478
2667
|
"source": "<div className=\"w-full max-w-sm\">\n <Slider label=\"Locked ration\" defaultValue={[8]} max={20} disabled />\n</div>"
|
|
2479
2668
|
}
|
|
2480
|
-
]
|
|
2669
|
+
],
|
|
2670
|
+
"closed": false
|
|
2481
2671
|
},
|
|
2482
2672
|
{
|
|
2483
2673
|
"name": "Sparkline",
|
|
@@ -2617,7 +2807,8 @@
|
|
|
2617
2807
|
"caption": "No values, one value, a flat run",
|
|
2618
2808
|
"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
2809
|
}
|
|
2620
|
-
]
|
|
2810
|
+
],
|
|
2811
|
+
"closed": true
|
|
2621
2812
|
},
|
|
2622
2813
|
{
|
|
2623
2814
|
"name": "Spinner",
|
|
@@ -2723,7 +2914,8 @@
|
|
|
2723
2914
|
"caption": "Decorative, inside a control",
|
|
2724
2915
|
"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
2916
|
}
|
|
2726
|
-
]
|
|
2917
|
+
],
|
|
2918
|
+
"closed": false
|
|
2727
2919
|
},
|
|
2728
2920
|
{
|
|
2729
2921
|
"name": "Switch",
|
|
@@ -2874,7 +3066,8 @@
|
|
|
2874
3066
|
"caption": "A settings row",
|
|
2875
3067
|
"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
3068
|
}
|
|
2877
|
-
]
|
|
3069
|
+
],
|
|
3070
|
+
"closed": false
|
|
2878
3071
|
},
|
|
2879
3072
|
{
|
|
2880
3073
|
"name": "Tag",
|
|
@@ -2985,7 +3178,8 @@
|
|
|
2985
3178
|
"caption": "Long text",
|
|
2986
3179
|
"source": "<Tag onDismiss={() => {}}>Stick battery 11.1 V, 1300 mAh, lithium polymer</Tag>"
|
|
2987
3180
|
}
|
|
2988
|
-
]
|
|
3181
|
+
],
|
|
3182
|
+
"closed": false
|
|
2989
3183
|
},
|
|
2990
3184
|
{
|
|
2991
3185
|
"name": "Textarea",
|
|
@@ -3111,7 +3305,8 @@
|
|
|
3111
3305
|
"caption": "With a counter",
|
|
3112
3306
|
"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
3307
|
}
|
|
3114
|
-
]
|
|
3308
|
+
],
|
|
3309
|
+
"closed": false
|
|
3115
3310
|
},
|
|
3116
3311
|
{
|
|
3117
3312
|
"name": "Tooltip",
|
|
@@ -3215,7 +3410,8 @@
|
|
|
3215
3410
|
"caption": "Keyboard",
|
|
3216
3411
|
"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
3412
|
}
|
|
3218
|
-
]
|
|
3413
|
+
],
|
|
3414
|
+
"closed": true
|
|
3219
3415
|
},
|
|
3220
3416
|
{
|
|
3221
3417
|
"name": "Accordion",
|
|
@@ -3396,7 +3592,8 @@
|
|
|
3396
3592
|
"caption": "Controlled: the page keeps track of which sections are open",
|
|
3397
3593
|
"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
3594
|
}
|
|
3399
|
-
]
|
|
3595
|
+
],
|
|
3596
|
+
"closed": false
|
|
3400
3597
|
},
|
|
3401
3598
|
{
|
|
3402
3599
|
"name": "Alert",
|
|
@@ -3553,7 +3750,8 @@
|
|
|
3553
3750
|
"caption": "Live: it appears after a click, and a screen reader announces it straight away",
|
|
3554
3751
|
"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
3752
|
}
|
|
3556
|
-
]
|
|
3753
|
+
],
|
|
3754
|
+
"closed": false
|
|
3557
3755
|
},
|
|
3558
3756
|
{
|
|
3559
3757
|
"name": "ApprovalRequest",
|
|
@@ -3707,7 +3905,8 @@
|
|
|
3707
3905
|
"caption": "Resolved",
|
|
3708
3906
|
"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
3907
|
}
|
|
3710
|
-
]
|
|
3908
|
+
],
|
|
3909
|
+
"closed": true
|
|
3711
3910
|
},
|
|
3712
3911
|
{
|
|
3713
3912
|
"name": "AvatarGroup",
|
|
@@ -3858,7 +4057,8 @@
|
|
|
3858
4057
|
"caption": "In a row, beside the words it summarises",
|
|
3859
4058
|
"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
4059
|
}
|
|
3861
|
-
]
|
|
4060
|
+
],
|
|
4061
|
+
"closed": false
|
|
3862
4062
|
},
|
|
3863
4063
|
{
|
|
3864
4064
|
"name": "Breadcrumbs",
|
|
@@ -3984,7 +4184,8 @@
|
|
|
3984
4184
|
"caption": "A level that is not a page",
|
|
3985
4185
|
"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
4186
|
}
|
|
3987
|
-
]
|
|
4187
|
+
],
|
|
4188
|
+
"closed": true
|
|
3988
4189
|
},
|
|
3989
4190
|
{
|
|
3990
4191
|
"name": "Card",
|
|
@@ -4172,7 +4373,8 @@
|
|
|
4172
4373
|
"caption": "As a section",
|
|
4173
4374
|
"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
4375
|
}
|
|
4175
|
-
]
|
|
4376
|
+
],
|
|
4377
|
+
"closed": false
|
|
4176
4378
|
},
|
|
4177
4379
|
{
|
|
4178
4380
|
"name": "ChatMessage",
|
|
@@ -4338,7 +4540,155 @@
|
|
|
4338
4540
|
"caption": "Feedback on a reply: thumbs up or down, one vote, and an optional detail step after Not helpful",
|
|
4339
4541
|
"source": "const pretendSend = () => new Promise<void>(resolve => setTimeout(resolve, 800))\n\n<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage\n role=\"assistant\"\n author=\"Assistant\"\n actions={<ResponseFeedback onSubmit={pretendSend} onDetail={() => {}} />}\n >\n Depot 4 holds 1,240 replicas. Eleven are below their reorder point.\n </ChatMessage>\n</div>"
|
|
4340
4542
|
}
|
|
4341
|
-
]
|
|
4543
|
+
],
|
|
4544
|
+
"closed": true
|
|
4545
|
+
},
|
|
4546
|
+
{
|
|
4547
|
+
"name": "CodeBlock",
|
|
4548
|
+
"title": "Code block",
|
|
4549
|
+
"tier": "molecules",
|
|
4550
|
+
"slug": "code-block",
|
|
4551
|
+
"summary": "Code or a command in a panel, with a title bar that names the file or language and a button that copies the text. Long lines scroll sideways inside it, and code longer than one line gets line numbers.",
|
|
4552
|
+
"useWhen": "Showing code or a command someone will read or copy: an install step, a config file, an example.",
|
|
4553
|
+
"notFor": "A word of code inside a sentence: use a code element. A key to press: use Kbd.",
|
|
4554
|
+
"source": "src/components/molecules/code-block/CodeBlock.tsx",
|
|
4555
|
+
"docs": "/docs/components/code-block.md",
|
|
4556
|
+
"imports": [
|
|
4557
|
+
{
|
|
4558
|
+
"names": [
|
|
4559
|
+
"CodeBlock"
|
|
4560
|
+
],
|
|
4561
|
+
"from": "src/components/molecules/code-block/CodeBlock"
|
|
4562
|
+
}
|
|
4563
|
+
],
|
|
4564
|
+
"requires": [
|
|
4565
|
+
"The app’s one Toaster at the root: the copy is confirmed with a toast."
|
|
4566
|
+
],
|
|
4567
|
+
"props": [
|
|
4568
|
+
{
|
|
4569
|
+
"name": "code",
|
|
4570
|
+
"type": "string",
|
|
4571
|
+
"description": "The text shown and copied, exactly."
|
|
4572
|
+
},
|
|
4573
|
+
{
|
|
4574
|
+
"name": "label",
|
|
4575
|
+
"type": "string",
|
|
4576
|
+
"description": "What the code is, in a few words (\"the theme CSS\"). Names the region and the copy button (\"Copy the theme CSS\")."
|
|
4577
|
+
},
|
|
4578
|
+
{
|
|
4579
|
+
"name": "title",
|
|
4580
|
+
"type": "string",
|
|
4581
|
+
"description": "Shown in the title bar: a file name (\"index.css\") or a language (\"bash\")."
|
|
4582
|
+
},
|
|
4583
|
+
{
|
|
4584
|
+
"name": "language",
|
|
4585
|
+
"type": "'css' | 'text'",
|
|
4586
|
+
"default": "'text'",
|
|
4587
|
+
"description": "css colours comments, at-rules, selectors and property names; text is plain."
|
|
4588
|
+
},
|
|
4589
|
+
{
|
|
4590
|
+
"name": "lineNumbers",
|
|
4591
|
+
"type": "boolean",
|
|
4592
|
+
"description": "Line numbers in a gutter. Unset, they show when the code has more than one line."
|
|
4593
|
+
},
|
|
4594
|
+
{
|
|
4595
|
+
"name": "copyable",
|
|
4596
|
+
"type": "boolean",
|
|
4597
|
+
"default": "true",
|
|
4598
|
+
"description": "The copy button. Turn it off for code nobody should paste, such as a wrong example."
|
|
4599
|
+
},
|
|
4600
|
+
{
|
|
4601
|
+
"name": "wrap",
|
|
4602
|
+
"type": "boolean",
|
|
4603
|
+
"default": "false",
|
|
4604
|
+
"description": "Wraps long lines instead of scrolling them sideways. For prose, such as a prompt; leave code unwrapped."
|
|
4605
|
+
},
|
|
4606
|
+
{
|
|
4607
|
+
"name": "className",
|
|
4608
|
+
"type": "string",
|
|
4609
|
+
"description": "Merged onto the frame. Use it for width (max-w-3xl) and spacing."
|
|
4610
|
+
},
|
|
4611
|
+
{
|
|
4612
|
+
"name": "ref",
|
|
4613
|
+
"type": "Ref<HTMLDivElement>",
|
|
4614
|
+
"description": "Passed through to the frame."
|
|
4615
|
+
}
|
|
4616
|
+
],
|
|
4617
|
+
"keyboard": [
|
|
4618
|
+
{
|
|
4619
|
+
"keys": [
|
|
4620
|
+
"Tab"
|
|
4621
|
+
],
|
|
4622
|
+
"does": "Moves to the copy button, then to the code region."
|
|
4623
|
+
},
|
|
4624
|
+
{
|
|
4625
|
+
"keys": [
|
|
4626
|
+
"←",
|
|
4627
|
+
"→"
|
|
4628
|
+
],
|
|
4629
|
+
"does": "In the focused code region: scrolls a long line sideways."
|
|
4630
|
+
},
|
|
4631
|
+
{
|
|
4632
|
+
"keys": [
|
|
4633
|
+
"Enter"
|
|
4634
|
+
],
|
|
4635
|
+
"does": "On the copy button: copies the code."
|
|
4636
|
+
}
|
|
4637
|
+
],
|
|
4638
|
+
"dos": [
|
|
4639
|
+
"Write a label that says what the code is, so the copy button reads \"Copy the install command\".",
|
|
4640
|
+
"Give it a title: the file name it belongs in, or the language.",
|
|
4641
|
+
"Set a max width with className; long lines scroll inside it, so it never widens the page."
|
|
4642
|
+
],
|
|
4643
|
+
"donts": [
|
|
4644
|
+
"Don’t put several unrelated commands in one block. One block per thing to copy.",
|
|
4645
|
+
"Don’t set copyable on an example of what not to write.",
|
|
4646
|
+
"Don’t wrap it in another bordered panel; it brings its own frame."
|
|
4647
|
+
],
|
|
4648
|
+
"pitfalls": [
|
|
4649
|
+
{
|
|
4650
|
+
"wrong": "<pre className=\"font-mono text-sm\">{command}</pre>",
|
|
4651
|
+
"right": "<CodeBlock code={command} label=\"the install command\" title=\"bash\" />",
|
|
4652
|
+
"why": "A bare pre has no copy button, no name, and cannot be scrolled with a keyboard when a line runs past the edge."
|
|
4653
|
+
}
|
|
4654
|
+
],
|
|
4655
|
+
"anatomy": [
|
|
4656
|
+
{
|
|
4657
|
+
"part": "Frame",
|
|
4658
|
+
"description": "A thin border around a surface.base fill. The syntax colours are measured for contrast on surface.base."
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
"part": "Title bar",
|
|
4662
|
+
"description": "The title on the left, in 12px uppercase mono, and the copy button on the right, above a thin line."
|
|
4663
|
+
},
|
|
4664
|
+
{
|
|
4665
|
+
"part": "Code region",
|
|
4666
|
+
"description": "A pre with role region, named by the title and the label, focusable so a keyboard can scroll it. Up to 32rem tall, then it scrolls."
|
|
4667
|
+
},
|
|
4668
|
+
{
|
|
4669
|
+
"part": "Line numbers",
|
|
4670
|
+
"description": "A gutter in text.muted, hidden from screen readers and from text selection."
|
|
4671
|
+
},
|
|
4672
|
+
{
|
|
4673
|
+
"part": "Copy button",
|
|
4674
|
+
"description": "An IconButton named \"Copy\" plus the label. After copying, its icon turns into a tick for a moment and a toast confirms it."
|
|
4675
|
+
}
|
|
4676
|
+
],
|
|
4677
|
+
"examples": [
|
|
4678
|
+
{
|
|
4679
|
+
"caption": "A command",
|
|
4680
|
+
"source": "<CodeBlock title=\"bash\" label=\"the check command\" code=\"npx division-ds check src\" className=\"max-w-3xl\" />"
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
"caption": "A file, with CSS colours and line numbers",
|
|
4684
|
+
"source": "<CodeBlock\n title=\"index.css\"\n language=\"css\"\n label=\"the theme CSS\"\n className=\"max-w-3xl\"\n code={'@import \"./division-ds/styles/division-ds.css\";\\n\\n/* Square corners */\\n:root {\\n --shape-bevel: 0;\\n --shape-radius: 4px;\\n}\\n'}\n/>"
|
|
4685
|
+
},
|
|
4686
|
+
{
|
|
4687
|
+
"caption": "Not copyable, for code nobody should paste",
|
|
4688
|
+
"source": "<CodeBlock title=\"Wrong\" label=\"a bare pre\" copyable={false} code={'<pre>{command}</pre>'} className=\"max-w-3xl\" />"
|
|
4689
|
+
}
|
|
4690
|
+
],
|
|
4691
|
+
"closed": true
|
|
4342
4692
|
},
|
|
4343
4693
|
{
|
|
4344
4694
|
"name": "DatePicker",
|
|
@@ -4374,7 +4724,7 @@
|
|
|
4374
4724
|
{
|
|
4375
4725
|
"name": "label",
|
|
4376
4726
|
"type": "string",
|
|
4377
|
-
"description": "
|
|
4727
|
+
"description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
|
|
4378
4728
|
},
|
|
4379
4729
|
{
|
|
4380
4730
|
"name": "value",
|
|
@@ -4621,7 +4971,7 @@
|
|
|
4621
4971
|
},
|
|
4622
4972
|
{
|
|
4623
4973
|
"caption": "In a FormField, with help text",
|
|
4624
|
-
"source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Trip date\" help=\"Type it, or open the calendar.\">\n <DatePicker
|
|
4974
|
+
"source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Trip date\" help=\"Type it, or open the calendar.\">\n <DatePicker />\n </FormField>\n</div>"
|
|
4625
4975
|
},
|
|
4626
4976
|
{
|
|
4627
4977
|
"caption": "Controlled: the page holds the value",
|
|
@@ -4639,6 +4989,11 @@
|
|
|
4639
4989
|
"caption": "The calendar on its own",
|
|
4640
4990
|
"source": "function InlineCalendar() {\n const [date, setDate] = useState<string | null>(null)\n return (\n <div className=\"flex flex-col gap-2\">\n <Calendar value={date} onSelect={setDate} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">{date ? formatLong(date) : 'No day picked'}</span>\n </div>\n )\n}\n\n<InlineCalendar />"
|
|
4641
4991
|
}
|
|
4992
|
+
],
|
|
4993
|
+
"closed": true,
|
|
4994
|
+
"namedByFormField": [
|
|
4995
|
+
"DateField",
|
|
4996
|
+
"DatePicker"
|
|
4642
4997
|
]
|
|
4643
4998
|
},
|
|
4644
4999
|
{
|
|
@@ -4754,7 +5109,8 @@
|
|
|
4754
5109
|
"caption": "Your own periods",
|
|
4755
5110
|
"source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker\n label=\"Season\"\n aside={\n <DatePresets\n label=\"Seasons\"\n presets={[\n { label: 'Winter 2026', range: { start: '2026-01-01', end: '2026-03-31' } },\n { label: 'Spring 2026', range: { start: '2026-04-01', end: '2026-06-30' } },\n { label: 'Summer 2026', range: { start: '2026-07-01', end: '2026-09-30' } },\n ]}\n />\n }\n />\n</div>"
|
|
4756
5111
|
}
|
|
4757
|
-
]
|
|
5112
|
+
],
|
|
5113
|
+
"closed": true
|
|
4758
5114
|
},
|
|
4759
5115
|
{
|
|
4760
5116
|
"name": "DateRangePicker",
|
|
@@ -4780,7 +5136,7 @@
|
|
|
4780
5136
|
{
|
|
4781
5137
|
"name": "label",
|
|
4782
5138
|
"type": "string",
|
|
4783
|
-
"description": "
|
|
5139
|
+
"description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
|
|
4784
5140
|
},
|
|
4785
5141
|
{
|
|
4786
5142
|
"name": "value",
|
|
@@ -4971,7 +5327,7 @@
|
|
|
4971
5327
|
},
|
|
4972
5328
|
{
|
|
4973
5329
|
"caption": "With DatePresets beside the months",
|
|
4974
|
-
"source": "<div className=\"w-full max-w-sm\">\n <FormField label=\"Report period\" help=\"Pick a preset, or two days.\">\n <DateRangePicker
|
|
5330
|
+
"source": "<div className=\"w-full max-w-sm\">\n <FormField label=\"Report period\" help=\"Pick a preset, or two days.\">\n <DateRangePicker aside={<DatePresets />} />\n </FormField>\n</div>"
|
|
4975
5331
|
},
|
|
4976
5332
|
{
|
|
4977
5333
|
"caption": "Controlled: the page holds the range",
|
|
@@ -4981,6 +5337,10 @@
|
|
|
4981
5337
|
"caption": "Limits: November and December only",
|
|
4982
5338
|
"source": "<div className=\"w-full max-w-sm\">\n <DateRangePicker label=\"Rental\" min=\"2026-11-01\" max=\"2026-12-30\" defaultValue={{ start: '2026-11-20', end: '2026-11-23' }} />\n</div>"
|
|
4983
5339
|
}
|
|
5340
|
+
],
|
|
5341
|
+
"closed": true,
|
|
5342
|
+
"namedByFormField": [
|
|
5343
|
+
"DateRangePicker"
|
|
4984
5344
|
]
|
|
4985
5345
|
},
|
|
4986
5346
|
{
|
|
@@ -5091,7 +5451,8 @@
|
|
|
5091
5451
|
"caption": "Three columns, fewer as the container narrows",
|
|
5092
5452
|
"source": "<div className=\"w-full\">\n <DescriptionList\n columns={3}\n items={[\n { term: 'Order', description: 'ORD-2291' },\n { term: 'Placed', description: '12 Oct 2026' },\n { term: 'Ships', description: '16 Oct 2026' },\n { term: 'Depot', description: 'Cluj, bay 4' },\n { term: 'Carrier', description: 'Regular courier' },\n { term: 'Weight', description: '22.1 kg' },\n ]}\n />\n</div>"
|
|
5093
5453
|
}
|
|
5094
|
-
]
|
|
5454
|
+
],
|
|
5455
|
+
"closed": true
|
|
5095
5456
|
},
|
|
5096
5457
|
{
|
|
5097
5458
|
"name": "DropdownMenu",
|
|
@@ -5178,6 +5539,12 @@
|
|
|
5178
5539
|
"default": "'default'",
|
|
5179
5540
|
"description": "Use hostile for a destructive action: hostile-coloured text at rest, the hostile fill with ink text when highlighted."
|
|
5180
5541
|
},
|
|
5542
|
+
{
|
|
5543
|
+
"name": "asChild (Item)",
|
|
5544
|
+
"type": "boolean",
|
|
5545
|
+
"default": "false",
|
|
5546
|
+
"description": "Makes the one child (a link) the item, for a menu of links. icon and shortcut are ignored then; put the content inside the link."
|
|
5547
|
+
},
|
|
5181
5548
|
{
|
|
5182
5549
|
"name": "icon (Item)",
|
|
5183
5550
|
"type": "ReactNode",
|
|
@@ -5371,7 +5738,8 @@
|
|
|
5371
5738
|
"caption": "From an icon button",
|
|
5372
5739
|
"source": "function IconButtonTriggerExample() {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton label=\"Depot actions\" variant=\"secondary\">\n <MoreHorizontal aria-hidden=\"true\" />\n </IconButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem>View manifest</DropdownMenuItem>\n <DropdownMenuItem>Reassign</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem tone=\"hostile\">Decommission</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n<IconButtonTriggerExample />"
|
|
5373
5740
|
}
|
|
5374
|
-
]
|
|
5741
|
+
],
|
|
5742
|
+
"closed": false
|
|
5375
5743
|
},
|
|
5376
5744
|
{
|
|
5377
5745
|
"name": "EmptyState",
|
|
@@ -5516,7 +5884,8 @@
|
|
|
5516
5884
|
"caption": "Title only",
|
|
5517
5885
|
"source": "<EmptyState level={4} size=\"sm\" title=\"No returns yet\" />"
|
|
5518
5886
|
}
|
|
5519
|
-
]
|
|
5887
|
+
],
|
|
5888
|
+
"closed": false
|
|
5520
5889
|
},
|
|
5521
5890
|
{
|
|
5522
5891
|
"name": "FormField",
|
|
@@ -5567,7 +5936,7 @@
|
|
|
5567
5936
|
{
|
|
5568
5937
|
"name": "children",
|
|
5569
5938
|
"type": "ReactElement",
|
|
5570
|
-
"description": "Exactly one control: Input, Textarea, Select or
|
|
5939
|
+
"description": "Exactly one control: Input, Textarea, Select, MultiSelect, QuantityStepper, DatePicker, DateRangePicker or TimeField. The controls that name themselves (all but Input and Textarea) take the FormField label, so leave their own label out. An id you give the control is kept (point an ErrorSummary entry at it); without one, FormField makes one up."
|
|
5571
5940
|
},
|
|
5572
5941
|
{
|
|
5573
5942
|
"name": "className",
|
|
@@ -5679,7 +6048,8 @@
|
|
|
5679
6048
|
"caption": "A form with an error summary",
|
|
5680
6049
|
"source": "const RESERVE_COUNTRIES = [\n { value: 'ro', label: 'Romania' },\n { value: 'ie', label: 'Ireland' },\n { value: 'de', label: 'Germany' },\n]\n\nfunction ReserveExample() {\n const [country, setCountry] = useState('')\n const [magazines, setMagazines] = useState(0)\n const [errors, setErrors] = useState<FormError[]>([])\n const [attempt, setAttempt] = useState(0)\n const summary = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (attempt > 0) summary.current?.focus()\n }, [attempt])\n\n const countryError = errors.find(current => current.fieldId === 'reserve-country')?.message\n const magazinesError = errors.find(current => current.fieldId === 'reserve-magazines')?.message\n\n return (\n <form\n noValidate\n onSubmit={event => {\n event.preventDefault()\n const next: FormError[] = []\n if (!country) next.push({ fieldId: 'reserve-country', message: 'Choose a country.' })\n if (magazines < 4) next.push({ fieldId: 'reserve-magazines', message: 'Order at least 4 spare magazines.' })\n setErrors(next)\n if (next.length > 0) setAttempt(value => value + 1)\n }}\n className=\"flex flex-col gap-4 w-full max-w-sm p-5 bg-[var(--color-surface-base)]\"\n >\n <ErrorSummary ref={summary} errors={errors} headingLevel={4} />\n <FormField label=\"Country\" required error={countryError}>\n <Select\n id=\"reserve-country\"\n label=\"Country\"\n options={RESERVE_COUNTRIES}\n value={country}\n onValueChange={value => {\n setCountry(value)\n if (value) setErrors(current => current.filter(item => item.fieldId !== 'reserve-country'))\n }}\n />\n </FormField>\n <FormField label=\"Spare magazines\" help=\"Four per replica.\" error={magazinesError}>\n <QuantityStepper\n id=\"reserve-magazines\"\n label=\"Spare magazines\"\n max={40}\n value={magazines}\n onValueChange={value => {\n setMagazines(value)\n if (value >= 4) setErrors(current => current.filter(item => item.fieldId !== 'reserve-magazines'))\n }}\n />\n </FormField>\n <Button type=\"submit\">Reserve</Button>\n </form>\n )\n}\n\n<ReserveExample />"
|
|
5681
6050
|
}
|
|
5682
|
-
]
|
|
6051
|
+
],
|
|
6052
|
+
"closed": true
|
|
5683
6053
|
},
|
|
5684
6054
|
{
|
|
5685
6055
|
"name": "ListItem",
|
|
@@ -5851,7 +6221,8 @@
|
|
|
5851
6221
|
"caption": "A standalone row, outside a List",
|
|
5852
6222
|
"source": "<div className=\"w-full max-w-xl\">\n <ListItem as=\"div\" title=\"Spare magazine, 120 rounds\" description=\"Mid-cap, steel\" meta=\"€14.90\" />\n</div>"
|
|
5853
6223
|
}
|
|
5854
|
-
]
|
|
6224
|
+
],
|
|
6225
|
+
"closed": false
|
|
5855
6226
|
},
|
|
5856
6227
|
{
|
|
5857
6228
|
"name": "MultiSelect",
|
|
@@ -5878,7 +6249,7 @@
|
|
|
5878
6249
|
{
|
|
5879
6250
|
"name": "label",
|
|
5880
6251
|
"type": "string",
|
|
5881
|
-
"description": "
|
|
6252
|
+
"description": "The name screen readers give the text field and the list. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
|
|
5882
6253
|
},
|
|
5883
6254
|
{
|
|
5884
6255
|
"name": "options",
|
|
@@ -6034,15 +6405,15 @@
|
|
|
6034
6405
|
"examples": [
|
|
6035
6406
|
{
|
|
6036
6407
|
"caption": "Default",
|
|
6037
|
-
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\">\n <MultiSelect
|
|
6408
|
+
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\">\n <MultiSelect options={LOADOUT} placeholder=\"Choose your kit…\" />\n </FormField>\n</div>"
|
|
6038
6409
|
},
|
|
6039
6410
|
{
|
|
6040
6411
|
"caption": "Controlled: the page holds the chosen options",
|
|
6041
|
-
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\nfunction ControlledExample() {\n const [kit, setKit] = useState<string[]>(['mags', 'goggles', 'gloves'])\n return (\n <div className=\"flex flex-col gap-2 w-full max-w-md\">\n <FormField label=\"Loadout\" help=\"Remove a tag and watch the others close the gap.\">\n <MultiSelect
|
|
6412
|
+
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\nfunction ControlledExample() {\n const [kit, setKit] = useState<string[]>(['mags', 'goggles', 'gloves'])\n return (\n <div className=\"flex flex-col gap-2 w-full max-w-md\">\n <FormField label=\"Loadout\" help=\"Remove a tag and watch the others close the gap.\">\n <MultiSelect options={LOADOUT} value={kit} onValueChange={setKit} />\n </FormField>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">value: [{kit.join(', ')}]</span>\n </div>\n )\n}\n\n<ControlledExample />"
|
|
6042
6413
|
},
|
|
6043
6414
|
{
|
|
6044
6415
|
"caption": "Invalid",
|
|
6045
|
-
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\" error=\"Pick eye protection.\">\n <MultiSelect
|
|
6416
|
+
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <FormField label=\"Loadout\" error=\"Pick eye protection.\">\n <MultiSelect options={LOADOUT} defaultValue={['mags']} />\n </FormField>\n</div>"
|
|
6046
6417
|
},
|
|
6047
6418
|
{
|
|
6048
6419
|
"caption": "Sizes",
|
|
@@ -6052,6 +6423,10 @@
|
|
|
6052
6423
|
"caption": "Disabled",
|
|
6053
6424
|
"source": "const LOADOUT = [\n { value: 'mags', label: 'Spare magazines' },\n { value: 'goggles', label: 'Full-seal goggles' },\n { value: 'mesh', label: 'Mesh face guard' },\n { value: 'radio', label: 'Team radio', disabled: true },\n { value: 'gloves', label: 'Gloves' },\n { value: 'loader', label: 'Speed loader' },\n { value: 'tracer', label: 'Tracer unit' },\n { value: 'pouch', label: 'Dump pouch' },\n]\n\n<div className=\"w-full max-w-md\">\n <MultiSelect label=\"Locked loadout\" options={LOADOUT} defaultValue={['mags', 'gloves']} disabled />\n</div>"
|
|
6054
6425
|
}
|
|
6426
|
+
],
|
|
6427
|
+
"closed": true,
|
|
6428
|
+
"namedByFormField": [
|
|
6429
|
+
"MultiSelect"
|
|
6055
6430
|
]
|
|
6056
6431
|
},
|
|
6057
6432
|
{
|
|
@@ -6192,7 +6567,8 @@
|
|
|
6192
6567
|
"caption": "Under a breadcrumb trail",
|
|
6193
6568
|
"source": "<PageHeader\n level={2}\n breadcrumbs={\n <Breadcrumbs\n items={[\n { label: 'Account', href: '#account' },\n { label: 'Settings', href: '#settings' },\n { label: 'Notifications' },\n ]}\n />\n }\n title=\"Notifications\"\n description=\"What we send, and where.\"\n actions={<Button variant=\"secondary\">Reset to defaults</Button>}\n/>"
|
|
6194
6569
|
}
|
|
6195
|
-
]
|
|
6570
|
+
],
|
|
6571
|
+
"closed": true
|
|
6196
6572
|
},
|
|
6197
6573
|
{
|
|
6198
6574
|
"name": "Pagination",
|
|
@@ -6415,7 +6791,8 @@
|
|
|
6415
6791
|
"caption": "Table footer",
|
|
6416
6792
|
"source": "function TableFooterExample() {\n const total = 397\n const [page, setPage] = useState(2)\n const [pageSize, setPageSize] = useState(20)\n return (\n <div className=\"w-full\">\n <Pagination\n page={page}\n pageCount={Math.ceil(total / pageSize)}\n onPageChange={setPage}\n totalItems={total}\n pageSize={pageSize}\n onPageSizeChange={size => {\n setPageSize(size)\n setPage(1)\n }}\n showFirstLast\n label=\"Pagination, table footer\"\n />\n </div>\n )\n}\n\n<TableFooterExample />"
|
|
6417
6793
|
}
|
|
6418
|
-
]
|
|
6794
|
+
],
|
|
6795
|
+
"closed": true
|
|
6419
6796
|
},
|
|
6420
6797
|
{
|
|
6421
6798
|
"name": "PromptInput",
|
|
@@ -6577,7 +6954,8 @@
|
|
|
6577
6954
|
"caption": "Disabled",
|
|
6578
6955
|
"source": "<div className=\"flex w-full max-w-xl flex-col gap-2\">\n <PromptInput label=\"Message the assistant (disabled)\" value=\"\" onValueChange={() => {}} onSubmit={() => {}} disabled />\n <p className=\"font-body text-sm text-[var(--color-text-secondary)]\">The assistant is offline until 14:00.</p>\n</div>"
|
|
6579
6956
|
}
|
|
6580
|
-
]
|
|
6957
|
+
],
|
|
6958
|
+
"closed": true
|
|
6581
6959
|
},
|
|
6582
6960
|
{
|
|
6583
6961
|
"name": "QuantityStepper",
|
|
@@ -6603,7 +6981,7 @@
|
|
|
6603
6981
|
{
|
|
6604
6982
|
"name": "label",
|
|
6605
6983
|
"type": "string",
|
|
6606
|
-
"description": "
|
|
6984
|
+
"description": "Names the field and both buttons (\"Decrease Speed loader quantity\"). Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
|
|
6607
6985
|
},
|
|
6608
6986
|
{
|
|
6609
6987
|
"name": "value",
|
|
@@ -6768,6 +7146,10 @@
|
|
|
6768
7146
|
"caption": "In a cart line",
|
|
6769
7147
|
"source": "function CartLine() {\n const price = 38\n const [qty, setQty] = useState(2)\n return (\n <div className=\"flex items-center gap-4 w-full\">\n <span className=\"flex-1 font-body text-sm text-[var(--color-text-primary)]\">Speed loader</span>\n <QuantityStepper\n label=\"Speed loader quantity\"\n defaultValue={2}\n min={1}\n max={9}\n onValueChange={setQty}\n />\n <span className=\"w-16 text-right font-display font-semibold tabular-nums text-[var(--color-text-primary)]\">\n €{price * qty}\n </span>\n </div>\n )\n}\n\n<CartLine />"
|
|
6770
7148
|
}
|
|
7149
|
+
],
|
|
7150
|
+
"closed": true,
|
|
7151
|
+
"namedByFormField": [
|
|
7152
|
+
"QuantityStepper"
|
|
6771
7153
|
]
|
|
6772
7154
|
},
|
|
6773
7155
|
{
|
|
@@ -6938,7 +7320,8 @@
|
|
|
6938
7320
|
"caption": "Input",
|
|
6939
7321
|
"source": "function LiveInput() {\n const [value, setValue] = useState(4)\n return (\n <div className=\"flex items-center gap-3\">\n <RatingInput label=\"Rate the Prism holo sight\" value={value} onValueChange={setValue} />\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n You rated it {value} {value === 1 ? 'star' : 'stars'}\n </span>\n </div>\n )\n}\n\n<LiveInput />"
|
|
6940
7322
|
}
|
|
6941
|
-
]
|
|
7323
|
+
],
|
|
7324
|
+
"closed": false
|
|
6942
7325
|
},
|
|
6943
7326
|
{
|
|
6944
7327
|
"name": "Search",
|
|
@@ -7088,7 +7471,8 @@
|
|
|
7088
7471
|
"caption": "Sizes",
|
|
7089
7472
|
"source": "<div className=\"max-w-xs w-full\">\n <Search label=\"Search, small\" placeholder=\"Small\" inputSize=\"sm\" />\n</div>\n<div className=\"max-w-xs w-full\">\n <Search label=\"Search, medium\" placeholder=\"Medium\" inputSize=\"md\" />\n</div>\n<div className=\"max-w-xs w-full\">\n <Search label=\"Search, large\" placeholder=\"Large\" inputSize=\"lg\" />\n</div>"
|
|
7090
7473
|
}
|
|
7091
|
-
]
|
|
7474
|
+
],
|
|
7475
|
+
"closed": true
|
|
7092
7476
|
},
|
|
7093
7477
|
{
|
|
7094
7478
|
"name": "SectionHeading",
|
|
@@ -7209,7 +7593,8 @@
|
|
|
7209
7593
|
"caption": "Naming its section",
|
|
7210
7594
|
"source": "<section aria-labelledby=\"section-heading-docs-care\">\n <SectionHeading\n level={4}\n id=\"section-heading-docs-care\"\n title=\"Care plans\"\n description=\"The section below is a region named “Care plans”. It takes that name from this heading, which carries the id.\"\n />\n <p className=\"mt-4 font-body text-sm text-[var(--color-text-primary)]\">\n A chrono check, a tune and upgrade fitting, billed monthly.\n </p>\n</section>"
|
|
7211
7595
|
}
|
|
7212
|
-
]
|
|
7596
|
+
],
|
|
7597
|
+
"closed": true
|
|
7213
7598
|
},
|
|
7214
7599
|
{
|
|
7215
7600
|
"name": "SegmentedControl",
|
|
@@ -7351,7 +7736,8 @@
|
|
|
7351
7736
|
"caption": "Small, with icons, lined up with a small Button",
|
|
7352
7737
|
"source": "function ToolbarExample() {\n const [view, setView] = useState('board')\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <SegmentedControl\n label=\"View\"\n size=\"sm\"\n options={[\n { value: 'board', label: 'Board', icon: <Columns3 /> },\n { value: 'table', label: 'Table', icon: <Table /> },\n ]}\n value={view}\n onValueChange={setView}\n />\n <Button size=\"sm\" variant=\"secondary\">\n Export\n </Button>\n </div>\n )\n}\n\n<ToolbarExample />"
|
|
7353
7738
|
}
|
|
7354
|
-
]
|
|
7739
|
+
],
|
|
7740
|
+
"closed": true
|
|
7355
7741
|
},
|
|
7356
7742
|
{
|
|
7357
7743
|
"name": "Stat",
|
|
@@ -7464,7 +7850,8 @@
|
|
|
7464
7850
|
"caption": "Without a delta",
|
|
7465
7851
|
"source": "<div className=\"max-w-[10rem] w-full\">\n <Stat label=\"Kits dispatched\" value={18} padTo={3} />\n</div>"
|
|
7466
7852
|
}
|
|
7467
|
-
]
|
|
7853
|
+
],
|
|
7854
|
+
"closed": false
|
|
7468
7855
|
},
|
|
7469
7856
|
{
|
|
7470
7857
|
"name": "StatusTracker",
|
|
@@ -7629,7 +8016,8 @@
|
|
|
7629
8016
|
"caption": "Other status words",
|
|
7630
8017
|
"source": "<div className=\"w-full max-w-xs\">\n <StatusTracker\n label=\"Door sensor, last 8 hours\"\n statusLabels={{ ok: 'Closed', warning: 'Left open', hostile: 'Forced', neutral: 'Offline' }}\n items={[\n { id: 'h1', status: 'ok', label: '02:00 to 03:00' },\n { id: 'h2', status: 'ok', label: '03:00 to 04:00' },\n { id: 'h3', status: 'warning', label: '04:00 to 05:00, open 9 min' },\n { id: 'h4', status: 'ok', label: '05:00 to 06:00' },\n { id: 'h5', status: 'neutral', label: '06:00 to 07:00' },\n { id: 'h6', status: 'ok', label: '07:00 to 08:00' },\n { id: 'h7', status: 'ok', label: '08:00 to 09:00' },\n { id: 'h8', status: 'ok', label: '09:00 to 10:00' },\n ]}\n />\n</div>"
|
|
7631
8018
|
}
|
|
7632
|
-
]
|
|
8019
|
+
],
|
|
8020
|
+
"closed": true
|
|
7633
8021
|
},
|
|
7634
8022
|
{
|
|
7635
8023
|
"name": "Stepper",
|
|
@@ -7747,7 +8135,8 @@
|
|
|
7747
8135
|
"caption": "Live: move through the steps with Back and Continue",
|
|
7748
8136
|
"source": "const CHECKOUT_STEPS: Step[] = [\n { label: 'Shipping' },\n { label: 'Payment' },\n { label: 'Review', description: 'Check the manifest' },\n]\n\nfunction LiveStepper() {\n const [current, setCurrent] = useState(0)\n return (\n <div className=\"flex flex-col gap-6 w-full\">\n <Stepper steps={CHECKOUT_STEPS} current={current} label=\"Checkout progress\" />\n <div className=\"flex gap-3\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={current === 0}\n onClick={() => setCurrent(c => Math.max(0, c - 1))}\n >\n Back\n </Button>\n <Button\n size=\"sm\"\n disabled={current >= CHECKOUT_STEPS.length}\n onClick={() => setCurrent(c => Math.min(CHECKOUT_STEPS.length, c + 1))}\n >\n Continue\n </Button>\n </div>\n </div>\n )\n}\n\n<LiveStepper />"
|
|
7749
8137
|
}
|
|
7750
|
-
]
|
|
8138
|
+
],
|
|
8139
|
+
"closed": true
|
|
7751
8140
|
},
|
|
7752
8141
|
{
|
|
7753
8142
|
"name": "Tabs",
|
|
@@ -7926,7 +8315,8 @@
|
|
|
7926
8315
|
"caption": "Controlled: the page keeps track of which tab is open",
|
|
7927
8316
|
"source": "function ControlledExample() {\n const [value, setValue] = useState('replica')\n\n return (\n <div className=\"max-w-sm w-full\">\n <div className=\"flex gap-2 mb-4\">\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setValue('replica')}>\n Show replica\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setValue('optics')}>\n Show optics\n </Button>\n </div>\n <Tabs value={value} onValueChange={setValue}>\n <TabsList aria-label=\"Kit sections\">\n <TabsTrigger value=\"replica\">Replica</TabsTrigger>\n <TabsTrigger value=\"optics\">Optics</TabsTrigger>\n </TabsList>\n <TabsContent value=\"replica\">Electric carbine replica.</TabsContent>\n <TabsContent value=\"optics\">Tube sight and holo sight.</TabsContent>\n </Tabs>\n </div>\n )\n}\n\n<ControlledExample />"
|
|
7928
8317
|
}
|
|
7929
|
-
]
|
|
8318
|
+
],
|
|
8319
|
+
"closed": false
|
|
7930
8320
|
},
|
|
7931
8321
|
{
|
|
7932
8322
|
"name": "TimeField",
|
|
@@ -7951,7 +8341,7 @@
|
|
|
7951
8341
|
{
|
|
7952
8342
|
"name": "label",
|
|
7953
8343
|
"type": "string",
|
|
7954
|
-
"description": "
|
|
8344
|
+
"description": "Names the field. Leave it out inside a FormField, which sets it from its own label. Outside one it is required, and `division-ds check` flags a missing one."
|
|
7955
8345
|
},
|
|
7956
8346
|
{
|
|
7957
8347
|
"name": "value",
|
|
@@ -7978,7 +8368,7 @@
|
|
|
7978
8368
|
"name": "step",
|
|
7979
8369
|
"type": "number",
|
|
7980
8370
|
"default": "30",
|
|
7981
|
-
"description": "Minutes between the times in the list."
|
|
8371
|
+
"description": "Minutes between the times in the list. It only spaces the list: a typed time between steps (09:07) is kept as typed. Check it yourself if it must fall on a step."
|
|
7982
8372
|
},
|
|
7983
8373
|
{
|
|
7984
8374
|
"name": "min",
|
|
@@ -8143,8 +8533,12 @@
|
|
|
8143
8533
|
},
|
|
8144
8534
|
{
|
|
8145
8535
|
"caption": "With a DatePicker: a date and a time",
|
|
8146
|
-
"source": "function DateAndTime() {\n const [date, setDate] = useState('2026-11-14')\n const [time, setTime] = useState('09:30')\n return (\n <div className=\"flex w-full max-w-md flex-col gap-3\">\n <div className=\"grid gap-3 sm:grid-cols-[1fr_9rem]\">\n <FormField label=\"Pickup date\">\n <DatePicker
|
|
8536
|
+
"source": "function DateAndTime() {\n const [date, setDate] = useState('2026-11-14')\n const [time, setTime] = useState('09:30')\n return (\n <div className=\"flex w-full max-w-md flex-col gap-3\">\n <div className=\"grid gap-3 sm:grid-cols-[1fr_9rem]\">\n <FormField label=\"Pickup date\">\n <DatePicker value={date} onValueChange={setDate} />\n </FormField>\n <FormField label=\"Pickup time\">\n <TimeField value={time} onValueChange={setTime} step={15} min=\"08:00\" max=\"18:00\" />\n </FormField>\n </div>\n <span className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n {date && time ? `${date}T${time}` : 'Pick a date and a time'}\n </span>\n </div>\n )\n}\n\n<DateAndTime />"
|
|
8147
8537
|
}
|
|
8538
|
+
],
|
|
8539
|
+
"closed": true,
|
|
8540
|
+
"namedByFormField": [
|
|
8541
|
+
"TimeField"
|
|
8148
8542
|
]
|
|
8149
8543
|
},
|
|
8150
8544
|
{
|
|
@@ -8299,7 +8693,8 @@
|
|
|
8299
8693
|
"caption": "Without groups",
|
|
8300
8694
|
"source": "<div className=\"w-full max-w-xl\">\n <Timeline label=\"Work order WO-29\">\n <TimelineItem icon={<Wrench />} title=\"Opened\" time=\"2026-10-09T08:00:00+03:00\" timeLabel=\"Fri 9 Oct\" />\n <TimelineItem\n icon={<Wrench />}\n tone=\"signal\"\n title=\"Assigned to Mara Lind\"\n time=\"2026-10-12T11:20:00+03:00\"\n timeLabel=\"Mon 12 Oct\"\n />\n <TimelineItem\n icon={<CircleCheck />}\n tone=\"ok\"\n title=\"Closed: pallet jack serviced\"\n time=\"2026-10-13T15:45:00+03:00\"\n timeLabel=\"Tue 13 Oct\"\n />\n </Timeline>\n</div>"
|
|
8301
8695
|
}
|
|
8302
|
-
]
|
|
8696
|
+
],
|
|
8697
|
+
"closed": true
|
|
8303
8698
|
},
|
|
8304
8699
|
{
|
|
8305
8700
|
"name": "Toast",
|
|
@@ -8446,7 +8841,8 @@
|
|
|
8446
8841
|
"caption": "With an action",
|
|
8447
8842
|
"source": "function WithActionExample() {\n const { toast } = useToast()\n\n return (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() =>\n toast({\n title: 'Item removed',\n action: {\n label: 'Undo',\n altText: 'Undo from the manifest history',\n onAction: () => toast({ title: 'Item restored', tone: 'ok' }),\n },\n })\n }\n >\n Remove item\n </Button>\n )\n}\n\n<WithActionExample />"
|
|
8448
8843
|
}
|
|
8449
|
-
]
|
|
8844
|
+
],
|
|
8845
|
+
"closed": false
|
|
8450
8846
|
},
|
|
8451
8847
|
{
|
|
8452
8848
|
"name": "ToolCall",
|
|
@@ -8577,7 +8973,8 @@
|
|
|
8577
8973
|
"caption": "Without a title, details open",
|
|
8578
8974
|
"source": "<div className=\"flex w-full max-w-xl flex-col gap-3\">\n <ToolCall\n name=\"count_pallets\"\n status=\"done\"\n duration=\"0.3 s\"\n input={'{ \"depot\": 4, \"bay\": 2 }'}\n output=\"14 pallets\"\n defaultOpen\n />\n</div>"
|
|
8579
8975
|
}
|
|
8580
|
-
]
|
|
8976
|
+
],
|
|
8977
|
+
"closed": true
|
|
8581
8978
|
},
|
|
8582
8979
|
{
|
|
8583
8980
|
"name": "AlertDialog",
|
|
@@ -8798,7 +9195,8 @@
|
|
|
8798
9195
|
"caption": "Type the name to confirm, for the costliest actions",
|
|
8799
9196
|
"source": "const request = (fail = false) =>\n new Promise<void>((resolve, reject) =>\n window.setTimeout(() => (fail ? reject(new Error('The server did not answer.')) : resolve()), 1000)\n )\n\nconst ACCOUNT = 'northfield-supply'\n\nfunction TypeToConfirmExample() {\n const [typed, setTyped] = useState('')\n const matches = typed.trim() === ACCOUNT\n return (\n <AlertDialog onOpenChange={() => setTyped('')}>\n <AlertDialogTrigger asChild>\n <Button variant=\"danger\">Delete account</Button>\n </AlertDialogTrigger>\n <AlertDialogContent\n title=\"Delete the account?\"\n description=\"Its 212 orders, saved kit lists and team access are removed for good.\"\n >\n <FormField label=\"Account name\" help={`Type ${ACCOUNT} to enable Delete account.`}>\n <Input\n value={typed}\n onChange={event => setTyped(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"font-mono\"\n />\n </FormField>\n <AlertDialogFooter>\n <AlertDialogCancel />\n <AlertDialogAction disabled={!matches} loadingLabel=\"Deleting account\" onClick={() => request()}>\n Delete account\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )\n}\n\n<TypeToConfirmExample />"
|
|
8800
9197
|
}
|
|
8801
|
-
]
|
|
9198
|
+
],
|
|
9199
|
+
"closed": true
|
|
8802
9200
|
},
|
|
8803
9201
|
{
|
|
8804
9202
|
"name": "AuthForm",
|
|
@@ -8952,7 +9350,8 @@
|
|
|
8952
9350
|
"caption": "Two-step sign-in with a code",
|
|
8953
9351
|
"source": "const wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\n<AuthForm mode=\"verify-code\" onSubmit={() => wait(600).then(() => undefined)} headingLevel={4} />"
|
|
8954
9352
|
}
|
|
8955
|
-
]
|
|
9353
|
+
],
|
|
9354
|
+
"closed": true
|
|
8956
9355
|
},
|
|
8957
9356
|
{
|
|
8958
9357
|
"name": "Carousel",
|
|
@@ -9079,7 +9478,8 @@
|
|
|
9079
9478
|
"caption": "Three short items: both buttons do nothing while everything fits",
|
|
9080
9479
|
"source": "<Carousel label=\"Kestrel lengths\" itemWidth=\"8rem\">\n {['Kestrel K', 'Kestrel C', 'Kestrel L'].map(name => (\n <div\n key={name}\n className=\"flex h-20 items-center justify-center border border-[var(--color-border-hairline-strong)] font-display font-semibold text-[var(--color-text-primary)]\"\n >\n {name}\n </div>\n ))}\n</Carousel>"
|
|
9081
9480
|
}
|
|
9082
|
-
]
|
|
9481
|
+
],
|
|
9482
|
+
"closed": true
|
|
9083
9483
|
},
|
|
9084
9484
|
{
|
|
9085
9485
|
"name": "Cart",
|
|
@@ -9257,7 +9657,8 @@
|
|
|
9257
9657
|
"caption": "Empty",
|
|
9258
9658
|
"source": "<Cart lines={[]} onQuantityChange={() => {}} onRemove={() => {}} headingLevel={4} title=\"Saved for later\" />"
|
|
9259
9659
|
}
|
|
9260
|
-
]
|
|
9660
|
+
],
|
|
9661
|
+
"closed": true
|
|
9261
9662
|
},
|
|
9262
9663
|
{
|
|
9263
9664
|
"name": "ChartPanel",
|
|
@@ -9469,7 +9870,7 @@
|
|
|
9469
9870
|
"examples": [
|
|
9470
9871
|
{
|
|
9471
9872
|
"caption": "Line, two series",
|
|
9472
|
-
"source": "type Week = { week: string; online: number; store: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online:
|
|
9873
|
+
"source": "type Week = { week: string; online: number; store: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online: 118, store: 150 },\n { week: 'W2', online: 149, store: 172 },\n { week: 'W3', online: 168, store: 158 },\n { week: 'W4', online: 141, store: 131 },\n { week: 'W5', online: 126, store: 128 },\n { week: 'W6', online: 174, store: 150 },\n { week: 'W7', online: 231, store: 141 },\n { week: 'W8', online: 183, store: 122 },\n { week: 'W9', online: 139, store: 149 },\n { week: 'W10', online: 162, store: 185 },\n { week: 'W11', online: 196, store: 163 },\n { week: 'W12', online: 170, store: 140 },\n]\n\n<ChartPanel<Week>\n title=\"Weekly orders\"\n summary=\"Online orders peaked three times in twelve weeks, highest at 231 in week 7; in-store orders peaked highest at 185 in week 10.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n data={WEEKS}\n xKey=\"week\"\n xLabel=\"Week\"\n series={[\n { key: 'online', label: 'Online' },\n { key: 'store', label: 'In store' },\n ]}\n headingLevel={4}\n/>"
|
|
9473
9874
|
},
|
|
9474
9875
|
{
|
|
9475
9876
|
"caption": "Bar, one series",
|
|
@@ -9477,11 +9878,11 @@
|
|
|
9477
9878
|
},
|
|
9478
9879
|
{
|
|
9479
9880
|
"caption": "Area under a smooth line",
|
|
9480
|
-
"source": "type Day = { day: string; visits: number; orders: number }\n\nconst DAYS: Day[] = [\n { day: 'Mon', visits: 1840, orders: 61 },\n { day: 'Tue', visits:
|
|
9881
|
+
"source": "type Day = { day: string; visits: number; orders: number }\n\nconst DAYS: Day[] = [\n { day: 'Mon', visits: 1840, orders: 61 },\n { day: 'Tue', visits: 2960, orders: 104 },\n { day: 'Wed', visits: 2210, orders: 74 },\n { day: 'Thu', visits: 1720, orders: 55 },\n { day: 'Fri', visits: 2650, orders: 92 },\n { day: 'Sat', visits: 3890, orders: 141 },\n { day: 'Sun', visits: 3120, orders: 118 },\n]\n\n<ChartPanel<Day>\n title=\"Visits this week\"\n summary=\"Visits peaked on Tuesday at 2,960, fell to a Thursday low of 1,720, then peaked higher on Saturday at 3,890.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n data={DAYS}\n xKey=\"day\"\n xLabel=\"Day\"\n series={[{ key: 'visits', label: 'Visits' }]}\n headline=\"18,390\"\n headingLevel={4}\n/>"
|
|
9481
9882
|
},
|
|
9482
9883
|
{
|
|
9483
9884
|
"caption": "No grid lines: only the labels along the axes",
|
|
9484
|
-
"source": "type Week = { week: string; online: number; store: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online:
|
|
9885
|
+
"source": "type Week = { week: string; online: number; store: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online: 118, store: 150 },\n { week: 'W2', online: 149, store: 172 },\n { week: 'W3', online: 168, store: 158 },\n { week: 'W4', online: 141, store: 131 },\n { week: 'W5', online: 126, store: 128 },\n { week: 'W6', online: 174, store: 150 },\n { week: 'W7', online: 231, store: 141 },\n { week: 'W8', online: 183, store: 122 },\n { week: 'W9', online: 139, store: 149 },\n { week: 'W10', online: 162, store: 185 },\n { week: 'W11', online: 196, store: 163 },\n { week: 'W12', online: 170, store: 140 },\n]\n\n<ChartPanel<Week>\n title=\"Weekly orders, gridless\"\n summary=\"Online orders peaked three times in twelve weeks, highest at 231 in week 7; in-store orders peaked highest at 185 in week 10.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n grid={false}\n data={WEEKS}\n xKey=\"week\"\n xLabel=\"Week\"\n series={[\n { key: 'online', label: 'Online' },\n { key: 'store', label: 'In store' },\n ]}\n headingLevel={4}\n/>"
|
|
9485
9886
|
},
|
|
9486
9887
|
{
|
|
9487
9888
|
"caption": "A shaded band: night hours on a 24-hour series (the band wraps past midnight)",
|
|
@@ -9489,7 +9890,7 @@
|
|
|
9489
9890
|
},
|
|
9490
9891
|
{
|
|
9491
9892
|
"caption": "Small: stat cards, each with a sparkline (a tiny line chart with no axes)",
|
|
9492
|
-
"source": "type Week = { week: string; online: number; store: number }\n\ntype Day = { day: string; visits: number; orders: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online:
|
|
9893
|
+
"source": "type Week = { week: string; online: number; store: number }\n\ntype Day = { day: string; visits: number; orders: number }\n\nconst WEEKS: Week[] = [\n { week: 'W1', online: 118, store: 150 },\n { week: 'W2', online: 149, store: 172 },\n { week: 'W3', online: 168, store: 158 },\n { week: 'W4', online: 141, store: 131 },\n { week: 'W5', online: 126, store: 128 },\n { week: 'W6', online: 174, store: 150 },\n { week: 'W7', online: 231, store: 141 },\n { week: 'W8', online: 183, store: 122 },\n { week: 'W9', online: 139, store: 149 },\n { week: 'W10', online: 162, store: 185 },\n { week: 'W11', online: 196, store: 163 },\n { week: 'W12', online: 170, store: 140 },\n]\n\nconst DAYS: Day[] = [\n { day: 'Mon', visits: 1840, orders: 61 },\n { day: 'Tue', visits: 2960, orders: 104 },\n { day: 'Wed', visits: 2210, orders: 74 },\n { day: 'Thu', visits: 1720, orders: 55 },\n { day: 'Fri', visits: 2650, orders: 92 },\n { day: 'Sat', visits: 3890, orders: 141 },\n { day: 'Sun', visits: 3120, orders: 118 },\n]\n\n<ChartPanel<Day>\n title=\"Visits\"\n headline=\"18,390\"\n summary=\"Two peaks: Tuesday, then a higher Saturday.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n size=\"sm\"\n data={DAYS}\n xKey=\"day\"\n xLabel=\"Day\"\n series={[{ key: 'visits', label: 'Visits' }]}\n headingLevel={4}\n/>\n<ChartPanel<Day>\n title=\"Orders\"\n headline=\"670\"\n summary=\"Saturday took 141, the most of the week.\"\n kind=\"bar\"\n size=\"sm\"\n data={DAYS}\n xKey=\"day\"\n xLabel=\"Day\"\n series={[{ key: 'orders', label: 'Orders' }]}\n headingLevel={4}\n/>\n<ChartPanel<Week>\n title=\"Online orders\"\n headline=\"170\"\n summary=\"Three peaks in twelve weeks; the highest, 231, in week 7.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n size=\"sm\"\n data={WEEKS}\n xKey=\"week\"\n xLabel=\"Week\"\n series={[{ key: 'online', label: 'Online' }]}\n headingLevel={4}\n/>"
|
|
9493
9894
|
},
|
|
9494
9895
|
{
|
|
9495
9896
|
"caption": "Donut with a centre total",
|
|
@@ -9499,7 +9900,8 @@
|
|
|
9499
9900
|
"caption": "Radar, two series",
|
|
9500
9901
|
"source": "type Profile = { measure: string; m4: number; ak: number }\n\nconst PROFILES: Profile[] = [\n { measure: 'Range', m4: 78, ak: 64 },\n { measure: 'Accuracy', m4: 82, ak: 70 },\n { measure: 'Rate of fire', m4: 74, ak: 86 },\n { measure: 'Weight', m4: 58, ak: 44 },\n { measure: 'Reliability', m4: 70, ak: 90 },\n { measure: 'Upgrades', m4: 92, ak: 62 },\n]\n\n<ChartPanel<Profile>\n title=\"Replica profiles\"\n summary=\"The M4 replica leads on range, accuracy and upgrades; the AK replica on rate of fire and reliability.\"\n kind=\"radar\"\n data={PROFILES}\n xKey=\"measure\"\n xLabel=\"Measure\"\n series={[\n { key: 'm4', label: 'M4 replica' },\n { key: 'ak', label: 'AK replica' },\n ]}\n headingLevel={4}\n/>"
|
|
9501
9902
|
}
|
|
9502
|
-
]
|
|
9903
|
+
],
|
|
9904
|
+
"closed": true
|
|
9503
9905
|
},
|
|
9504
9906
|
{
|
|
9505
9907
|
"name": "ChatThread",
|
|
@@ -9625,7 +10027,8 @@
|
|
|
9625
10027
|
"caption": "A live conversation: send anything, watch the reply stream in, press Stop to end it, or scroll up while it's still arriving",
|
|
9626
10028
|
"source": "type Turn =\n | { id: number; role: 'user'; text: string }\n | { id: number; role: 'assistant'; text: string; tool?: ToolCallStatus; approval?: boolean }\n | { id: number; role: 'system'; text: string }\n\nconst REPLIES = [\n 'Depot 4 holds 1,240 replicas across 38 lines. Two lines are under their reorder point: the K-41 field radio has 6 left against a reorder point of 10, and the trail pack has 3 left against 8. Everything else has at least two weeks of cover at the current rate. The reorder above covers both lines; once it is approved, the supplier confirms within a working day.',\n 'Inbound this week: one delivery from Arad on Thursday morning, 22 pallets, booked into bay 2. Outbound: nine shipments, the largest to Sibiu on Friday. Nothing is waiting on a person except the reorder above.',\n]\n\nconst HISTORY: Turn[] = [\n { id: 1, role: 'user', text: 'Summarise stock at Depot 4.' },\n {\n id: 2,\n role: 'assistant',\n text: 'Depot 4 is in good shape: 1,240 replicas on hand and nothing overdue. Two lines are low, so I have drafted a reorder for you to check.',\n tool: 'done',\n approval: true,\n },\n]\n\nfunction LiveConversation() {\n const [turns, setTurns] = useState<Turn[]>(HISTORY)\n const [draft, setDraft] = useState('')\n const [phase, setPhase] = useState<'idle' | 'tool' | 'streaming'>('idle')\n const [approval, setApproval] = useState<ApprovalState>('pending')\n const [replyIndex, setReplyIndex] = useState(0)\n const nextId = useRef(HISTORY.length + 1)\n const activeId = useRef<number | null>(null)\n const reply = useStreamedText(REPLIES[replyIndex % REPLIES.length], { active: phase === 'streaming' })\n\n const updateActive = (change: (turn: Turn & { role: 'assistant' }) => Turn) =>\n setTurns(list => list.map(turn => (turn.id === activeId.current && turn.role === 'assistant' ? change(turn) : turn)))\n\n const addStopped = () => {\n const id = nextId.current++\n setTurns(list => [...list, { id, role: 'system', text: 'Assistant stopped' }])\n }\n\n // The tool \"runs\" for a moment, then the reply starts streaming.\n useEffect(() => {\n if (phase !== 'tool') return\n const timer = window.setTimeout(() => {\n updateActive(turn => ({ ...turn, tool: 'done' }))\n setPhase('streaming')\n }, 1200)\n return () => window.clearTimeout(timer)\n }, [phase])\n\n // Finished or stopped: keep what arrived, and hand the thread a message that is no longer streaming.\n useEffect(() => {\n if (phase !== 'streaming' || !reply.done) return\n updateActive(turn => ({ ...turn, text: reply.text }))\n if (reply.stopped) addStopped()\n setPhase('idle')\n setReplyIndex(index => index + 1)\n }, [phase, reply.done, reply.text, reply.stopped])\n\n const send = (text: string) => {\n const question = nextId.current++\n const answer = nextId.current++\n activeId.current = answer\n setTurns(list => [...list, { id: question, role: 'user', text }, { id: answer, role: 'assistant', text: '', tool: 'running' }])\n setDraft('')\n setPhase('tool')\n }\n\n const stop = () => {\n if (phase === 'streaming') {\n reply.stop()\n return\n }\n // Stopped before any text: the empty reply goes, the system line says what happened.\n setTurns(list => list.filter(turn => turn.id !== activeId.current))\n addStopped()\n setPhase('idle')\n }\n\n const busy = phase !== 'idle'\n\n return (\n <div className=\"flex w-full max-w-2xl flex-col border border-[var(--color-border-hairline-strong)]\">\n <ChatThread label=\"Conversation with the assistant\" busy={phase === 'tool'} className=\"h-[28rem]\">\n {turns.map(turn => {\n if (turn.role === 'user') {\n return (\n <ChatMessage key={turn.id} role=\"user\" author=\"You\">\n {turn.text}\n </ChatMessage>\n )\n }\n if (turn.role === 'system') {\n return (\n <ChatMessage key={turn.id} role=\"system\" author=\"Status\">\n {turn.text}\n </ChatMessage>\n )\n }\n const streaming = phase === 'streaming' && turn.id === activeId.current\n const text = streaming ? reply.text : turn.text\n return (\n <ChatMessage key={turn.id} role=\"assistant\" author=\"Assistant\" streaming={streaming}>\n {turn.tool === undefined ? null : (\n <ToolCall\n name=\"query_stock_levels\"\n title=\"Read stock levels at Depot 4\"\n status={turn.tool}\n duration={turn.tool === 'done' ? '1.2 s' : undefined}\n input={'{ \"depot\": 4 }'}\n output={turn.tool === 'done' ? '{ \"on_hand\": 1240, \"lines\": 38, \"below_reorder_point\": 2 }' : undefined}\n />\n )}\n {text === '' ? null : <p>{text}</p>}\n {turn.approval ? (\n <ApprovalRequest\n title=\"Send this reorder to the supplier?\"\n state={approval}\n onApprove={() => setApproval('approved')}\n onReject={() => setApproval('rejected')}\n approveLabel=\"Send reorder\"\n rejectLabel=\"Don't send\"\n >\n <p>To: Arad replica supply</p>\n <p>10 × K-41 field radio, 8 × trail pack, to Depot 4 by Thursday 22 October.</p>\n </ApprovalRequest>\n ) : null}\n </ChatMessage>\n )\n })}\n </ChatThread>\n <PromptInput\n label=\"Message the assistant\"\n placeholder=\"Ask about stock, orders or depots\"\n value={draft}\n onValueChange={setDraft}\n onSubmit={send}\n busy={busy}\n onStop={stop}\n className=\"p-4 border-t border-[var(--color-border-hairline-strong)]\"\n />\n </div>\n )\n}\n\n<LiveConversation />"
|
|
9627
10029
|
}
|
|
9628
|
-
]
|
|
10030
|
+
],
|
|
10031
|
+
"closed": true
|
|
9629
10032
|
},
|
|
9630
10033
|
{
|
|
9631
10034
|
"name": "CheckoutForm",
|
|
@@ -9803,7 +10206,8 @@
|
|
|
9803
10206
|
"caption": "Three steps, a demo order",
|
|
9804
10207
|
"source": "const LINES: CartLine[] = [\n { id: 'kestrel-c', name: 'Kestrel C carbine replica', unitPrice: 13900, quantity: 1 },\n { id: 'prism-holo-sight', name: 'Prism holo sight', unitPrice: 4900, quantity: 2 },\n { id: 'beam-600', name: 'Beam 600 rail light', unitPrice: 2400, quantity: 1 },\n]\n\nconst COUNTRIES = [\n { value: 'ie', label: 'Ireland' },\n { value: 'ro', label: 'Romania' },\n { value: 'de', label: 'Germany' },\n { value: 'fr', label: 'France' },\n { value: 'es', label: 'Spain' },\n]\n\nconst wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\n<CheckoutForm lines={LINES} countries={COUNTRIES} onPlaceOrder={() => wait(800).then(() => undefined)} headingLevel={3} />"
|
|
9805
10208
|
}
|
|
9806
|
-
]
|
|
10209
|
+
],
|
|
10210
|
+
"closed": true
|
|
9807
10211
|
},
|
|
9808
10212
|
{
|
|
9809
10213
|
"name": "CommandPalette",
|
|
@@ -10005,7 +10409,8 @@
|
|
|
10005
10409
|
"caption": "Store commands",
|
|
10006
10410
|
"source": "function StoreCommands() {\n const [open, setOpen] = useState(false)\n const [last, setLast] = useState('none')\n const make = (id: string, label: string, group: string, icon: ReactNode, extra: Partial<Command> = {}): Command => ({\n id,\n label,\n group,\n icon,\n onSelect: () => setLast(label),\n ...extra,\n })\n const commands = [\n make('orders', 'Go to orders', 'Navigate', <Package />, { keywords: ['sales'] }),\n make('shipments', 'Go to shipments', 'Navigate', <Truck />),\n make('returns', 'Go to returns', 'Navigate', <Inbox />),\n make('new-shipment', 'New shipment', 'Create', <Plus />, { shortcut: 'N' }),\n make('new-product', 'New product', 'Create', <Box />),\n make('invite', 'Invite a teammate', 'Account', <Users />, { keywords: ['team', 'people'] }),\n make('settings', 'Open settings', 'Account', <Settings />),\n ]\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button variant=\"secondary\" aria-keyshortcuts=\"Control+K Meta+K\" onClick={() => setOpen(true)}>\n Open command palette\n </Button>\n <span aria-hidden=\"true\" className=\"inline-flex gap-1\">\n <Kbd>Ctrl</Kbd>\n <Kbd>K</Kbd>\n </span>\n <p role=\"status\" className=\"font-mono text-xs text-[var(--color-text-secondary)]\">\n Last command: {last}\n </p>\n <CommandPalette commands={commands} open={open} onOpenChange={setOpen} />\n </div>\n )\n}\n\n<StoreCommands />"
|
|
10007
10411
|
}
|
|
10008
|
-
]
|
|
10412
|
+
],
|
|
10413
|
+
"closed": true
|
|
10009
10414
|
},
|
|
10010
10415
|
{
|
|
10011
10416
|
"name": "DataTable",
|
|
@@ -10227,7 +10632,8 @@
|
|
|
10227
10632
|
"caption": "Empty",
|
|
10228
10633
|
"source": "type Status = 'In transit' | 'Delivered' | 'Held'\n\ntype Shipment = { ref: string; destination: string; weight: number; items: number; status: Status }\n\nconst STATUS_TONE: Record<Status, 'info' | 'ok' | 'warning'> = {\n 'In transit': 'info',\n Delivered: 'ok',\n Held: 'warning',\n}\n\nconst COLUMNS: DataTableColumn<Shipment>[] = [\n { key: 'ref', header: 'Reference', cell: s => s.ref, sortValue: s => s.ref },\n { key: 'destination', header: 'Destination', cell: s => s.destination },\n { key: 'weight', header: 'Weight (kg)', cell: s => s.weight.toFixed(1), sortValue: s => s.weight, numeric: true },\n { key: 'items', header: 'Items', cell: s => String(s.items), sortValue: s => s.items, numeric: true },\n {\n key: 'status',\n header: 'Status',\n cell: s => (\n <Badge emphasis=\"solid\" tone={STATUS_TONE[s.status]}>\n {s.status}\n </Badge>\n ),\n },\n]\n\n<DataTable caption=\"Returns\" columns={COLUMNS} rows={[]} getRowId={s => s.ref} rowHeader=\"ref\" emptyMessage=\"No returns yet.\" />"
|
|
10229
10634
|
}
|
|
10230
|
-
]
|
|
10635
|
+
],
|
|
10636
|
+
"closed": true
|
|
10231
10637
|
},
|
|
10232
10638
|
{
|
|
10233
10639
|
"name": "Dialog",
|
|
@@ -10399,7 +10805,8 @@
|
|
|
10399
10805
|
"caption": "Long content",
|
|
10400
10806
|
"source": "function LongContentExample() {\n const contentRef = useRef<HTMLDivElement>(null)\n return (\n <Dialog>\n <DialogTrigger asChild>\n <Button variant=\"secondary\">Read shipping terms</Button>\n </DialogTrigger>\n <DialogContent\n ref={contentRef}\n title=\"Shipping terms\"\n size=\"lg\"\n tabIndex={-1}\n onOpenAutoFocus={event => {\n event.preventDefault()\n contentRef.current?.focus()\n }}\n >\n <div className=\"flex flex-col gap-4\">\n <p>Orders placed before 14:00 local time ship the same day; anything after ships the next business day.</p>\n <p>Every parcel leaves in double-walled board with corner protection; ask for crate packaging on anything over 30kg.</p>\n <p>Photograph the box before opening and report damage within 48 hours; we replace or refund, never both.</p>\n <p>Unopened stock returns within 30 days for a full refund; opened stock returns for store credit only.</p>\n <p>Depots outside the primary freight lanes add one to three business days and a remote-area surcharge.</p>\n <p>A single parcel tops out at 40kg; heavier loads split automatically at checkout.</p>\n <p>Cross-border shipments carry the recipient's own duties and taxes, billed separately by the carrier.</p>\n <p>Reach the shipping desk at shipping@example.com for anything this page does not answer.</p>\n </div>\n <DialogFooter>\n <DialogClose asChild>\n <Button>Done</Button>\n </DialogClose>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n<LongContentExample />"
|
|
10401
10807
|
}
|
|
10402
|
-
]
|
|
10808
|
+
],
|
|
10809
|
+
"closed": true
|
|
10403
10810
|
},
|
|
10404
10811
|
{
|
|
10405
10812
|
"name": "Footer",
|
|
@@ -10613,7 +11020,8 @@
|
|
|
10613
11020
|
"caption": "With a wordmark and a backdrop: a layer behind the letters, with headroom over them",
|
|
10614
11021
|
"source": "const STORE_COLUMNS: FooterColumn[] = [\n {\n title: 'Shop',\n links: [\n { label: 'Replicas', href: '#replicas' },\n { label: 'Optics', href: '#optics' },\n { label: 'Parts', href: '#parts' },\n { label: 'Care', href: '#care' },\n ],\n },\n {\n title: 'Support',\n links: [\n { label: 'Shipping', href: '#shipping' },\n { label: 'Returns', href: '#returns' },\n { label: 'Warranty', href: '#warranty' },\n { label: 'Contact', href: '#contact' },\n ],\n },\n {\n title: 'Company',\n links: [\n { label: 'About', href: '#about' },\n { label: 'Field notes', href: '#field-notes' },\n { label: 'Careers', href: '#careers' },\n ],\n },\n {\n title: 'Elsewhere',\n links: [\n { label: 'Site finder', href: 'https://example.com/sites', external: true },\n { label: 'Field guides', href: 'https://example.com/field-guides', external: true },\n ],\n },\n]\n\nconst LEGAL = '© 2026 Hexline. A demo brand for this design system.'\n\nconst BACKDROP = (\n <div\n className=\"absolute inset-0\"\n style={{\n backgroundImage: 'radial-gradient(var(--color-text-muted) 1px, transparent 1.5px)',\n backgroundSize: '14px 14px',\n maskImage: 'linear-gradient(to bottom, transparent, black 70%)',\n }}\n />\n)\n\n<Footer label=\"Backdrop footer\" headingLevel={4} columns={STORE_COLUMNS.slice(0, 2)} legal={LEGAL} wordmark=\"Hexline\" backdrop={BACKDROP} />"
|
|
10615
11022
|
}
|
|
10616
|
-
]
|
|
11023
|
+
],
|
|
11024
|
+
"closed": true
|
|
10617
11025
|
},
|
|
10618
11026
|
{
|
|
10619
11027
|
"name": "KanbanBoard",
|
|
@@ -10864,7 +11272,8 @@
|
|
|
10864
11272
|
"caption": "Work orders, with an empty column",
|
|
10865
11273
|
"source": "type Order = { id: string; title: string; bay: string; status: 'open' | 'parts' | 'done' }\n\nconst ORDER_COLUMNS: KanbanColumn[] = [\n { id: 'open', title: 'Open' },\n { id: 'parts', title: 'Waiting for parts' },\n { id: 'done', title: 'Done' },\n]\n\nconst ORDERS: Order[] = [\n { id: 'wo-31', title: 'Replace dock door seal', bay: 'Bay 2', status: 'open' },\n { id: 'wo-32', title: 'Recalibrate floor scale', bay: 'Bay 5', status: 'open' },\n { id: 'wo-29', title: 'Service pallet jack', bay: 'Bay 1', status: 'done' },\n]\n\nconst orderAccessors = {\n getItemId: (order: Order) => order.id,\n getColumnId: (order: Order) => order.status,\n setColumnId: (order: Order, status: string) => ({ ...order, status: status as Order['status'] }),\n}\n\nfunction WorkOrders() {\n const [orders, setOrders] = useState(ORDERS)\n return (\n <div className=\"w-full min-w-0\">\n <KanbanBoard\n label=\"Depot work orders\"\n columns={ORDER_COLUMNS}\n items={orders}\n getItemId={orderAccessors.getItemId}\n getColumnId={orderAccessors.getColumnId}\n getItemLabel={order => order.title}\n renderCard={order => (\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-semibold\">{order.title}</p>\n <p className=\"font-mono text-xs uppercase tracking-[0.12em] text-[var(--color-text-secondary)]\">{order.bay}</p>\n </div>\n )}\n onMove={(id, status, index) => setOrders(current => applyKanbanMove(current, id, status, index, orderAccessors))}\n emptyColumnMessage=\"No orders waiting\"\n columnHeadingLevel={4}\n />\n </div>\n )\n}\n\n<WorkOrders />"
|
|
10866
11274
|
}
|
|
10867
|
-
]
|
|
11275
|
+
],
|
|
11276
|
+
"closed": true
|
|
10868
11277
|
},
|
|
10869
11278
|
{
|
|
10870
11279
|
"name": "Navbar",
|
|
@@ -10898,7 +11307,7 @@
|
|
|
10898
11307
|
{
|
|
10899
11308
|
"name": "items",
|
|
10900
11309
|
"type": "NavbarItem[]",
|
|
10901
|
-
"description": "Required.
|
|
11310
|
+
"description": "Required. A link is { label, href, current }. A menu among the links is { label, menu, current }: menu lists { label, href, description } entries, opened from a dropdown on a wide bar and listed as a labelled group in the menu sheet on a narrow one."
|
|
10902
11311
|
},
|
|
10903
11312
|
{
|
|
10904
11313
|
"name": "actions",
|
|
@@ -10920,7 +11329,13 @@
|
|
|
10920
11329
|
{
|
|
10921
11330
|
"name": "menuFooter",
|
|
10922
11331
|
"type": "ReactNode",
|
|
10923
|
-
"description": "Shown in the menu sheet under the links. Use it for an action that doesn't fit in actions on a narrow bar. Hide the matching copy in actions below the breakpoint by wrapping it in an element with the classes hidden @2xl:flex."
|
|
11332
|
+
"description": "Shown in the menu sheet under the links. Use it for an action that doesn't fit in actions on a narrow bar. Hide the matching copy in actions below the breakpoint by wrapping it in an element with the classes hidden @2xl:flex (hidden @4xl:flex when collapseBelow is '4xl')."
|
|
11333
|
+
},
|
|
11334
|
+
{
|
|
11335
|
+
"name": "collapseBelow",
|
|
11336
|
+
"type": "'2xl' | '4xl'",
|
|
11337
|
+
"default": "'2xl'",
|
|
11338
|
+
"description": "The bar's own width below which the links fold into the menu sheet: 2xl is 42rem, 4xl is 56rem. Pick the one at which the brand, every link and the actions still fit on one row with room to spare."
|
|
10924
11339
|
},
|
|
10925
11340
|
{
|
|
10926
11341
|
"name": "className",
|
|
@@ -10937,7 +11352,7 @@
|
|
|
10937
11352
|
{
|
|
10938
11353
|
"name": "NavbarItem",
|
|
10939
11354
|
"kind": "type",
|
|
10940
|
-
"code": "export type NavbarItem =
|
|
11355
|
+
"code": "export type NavbarItem = NavbarLink | NavbarMenu",
|
|
10941
11356
|
"from": "src/components/organisms/navbar/Navbar"
|
|
10942
11357
|
}
|
|
10943
11358
|
],
|
|
@@ -10998,11 +11413,16 @@
|
|
|
10998
11413
|
"caption": "Store navigation",
|
|
10999
11414
|
"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<Navbar label=\"Store navigation\" brand={BRAND} items={ITEMS} actions={ACTIONS} />"
|
|
11000
11415
|
},
|
|
11416
|
+
{
|
|
11417
|
+
"caption": "A menu among the links",
|
|
11418
|
+
"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<Navbar\n label=\"Store navigation with a menu\"\n brand={BRAND}\n items={[\n ...ITEMS.slice(0, 3),\n {\n label: 'Guides',\n menu: [\n { label: 'Sizing', href: '#sizing', description: 'Fit charts for every replica.' },\n { label: 'Returns', href: '#returns', description: 'Thirty days, no questions.' },\n ],\n },\n ]}\n actions={ACTIONS}\n/>"
|
|
11419
|
+
},
|
|
11001
11420
|
{
|
|
11002
11421
|
"caption": "A narrow container",
|
|
11003
11422
|
"source": "const ITEMS = [\n { label: 'Replicas', href: '#replicas' },\n { label: 'Optics', href: '#optics', current: true },\n { label: 'Parts', href: '#parts' },\n { label: 'Care', href: '#care' },\n { label: 'Field notes', href: '#field-notes' },\n]\n\nconst BRAND = (\n <a\n href=\"#home\"\n className=\"inline-flex items-center min-h-10 font-display font-bold uppercase tracking-[0.14em] text-[var(--color-text-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-focus-ring)]\"\n >\n Hexline\n </a>\n)\n\nconst ACTIONS = (\n <>\n <IconButton label=\"Search\" variant=\"ghost\">\n <Search aria-hidden=\"true\" className=\"w-5 h-5\" />\n </IconButton>\n <IconButton label=\"Cart, 2 items\" variant=\"ghost\" asChild>\n <a href=\"#cart\">\n <ShoppingBag aria-hidden=\"true\" className=\"w-5 h-5\" />\n </a>\n </IconButton>\n </>\n)\n\n<div className=\"max-w-sm\">\n <Navbar label=\"Store navigation, narrow\" brand={BRAND} items={ITEMS} actions={ACTIONS} />\n</div>"
|
|
11004
11423
|
}
|
|
11005
|
-
]
|
|
11424
|
+
],
|
|
11425
|
+
"closed": true
|
|
11006
11426
|
},
|
|
11007
11427
|
{
|
|
11008
11428
|
"name": "NewsletterForm",
|
|
@@ -11154,7 +11574,8 @@
|
|
|
11154
11574
|
"caption": "A sign-up that fails",
|
|
11155
11575
|
"source": "const wait = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms))\n\nasync function refuse() {\n await wait(700)\n throw new Error('The demo server is switched off.')\n}\n\n<div className=\"flex w-full flex-col gap-3\">\n <p className=\"font-body text-sm text-[var(--color-text-secondary)]\">\n Demo: this one rejects every address, so you can see the alert, and the address you typed stays in the field.\n </p>\n <NewsletterForm\n label=\"Restock alerts by email\"\n consent=\"An email when a sold-out replica is back. Nothing else.\"\n submitLabel=\"Notify me\"\n busyLabel=\"Signing you up\"\n onSubmit={refuse}\n headingLevel={4}\n />\n</div>"
|
|
11156
11576
|
}
|
|
11157
|
-
]
|
|
11577
|
+
],
|
|
11578
|
+
"closed": true
|
|
11158
11579
|
},
|
|
11159
11580
|
{
|
|
11160
11581
|
"name": "ProductCard",
|
|
@@ -11328,7 +11749,8 @@
|
|
|
11328
11749
|
"caption": "Nothing matches",
|
|
11329
11750
|
"source": "<ProductGrid label=\"Hexline range, filtered\" products={[]} emptyMessage=\"No products match these filters.\" />"
|
|
11330
11751
|
}
|
|
11331
|
-
]
|
|
11752
|
+
],
|
|
11753
|
+
"closed": true
|
|
11332
11754
|
},
|
|
11333
11755
|
{
|
|
11334
11756
|
"name": "Sheet",
|
|
@@ -11483,7 +11905,8 @@
|
|
|
11483
11905
|
"caption": "From the left",
|
|
11484
11906
|
"source": "const DEPOTS = ['Brasov north', 'Brasov south', 'Sibiu', 'Cluj', 'Timisoara', 'Oradea']\n\nfunction FromTheLeftExample() {\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"secondary\">Open depot list</Button>\n </SheetTrigger>\n <SheetContent side=\"left\" title=\"Depots\">\n <ul role=\"list\" className=\"flex flex-col gap-2\">\n {DEPOTS.map(depot => (\n <li key={depot}>{depot}</li>\n ))}\n </ul>\n </SheetContent>\n </Sheet>\n )\n}\n\n<FromTheLeftExample />"
|
|
11485
11907
|
}
|
|
11486
|
-
]
|
|
11908
|
+
],
|
|
11909
|
+
"closed": true
|
|
11487
11910
|
},
|
|
11488
11911
|
{
|
|
11489
11912
|
"name": "Sidebar",
|
|
@@ -11616,7 +12039,8 @@
|
|
|
11616
12039
|
"caption": "In a sheet on a narrow screen: picking a link closes the sheet, through renderLink",
|
|
11617
12040
|
"source": "const GROUPS: SidebarGroup[] = [\n {\n title: 'Operations',\n icon: <Warehouse />,\n items: [\n { label: 'Overview', href: '#overview', icon: <LayoutDashboard /> },\n { label: 'Orders', href: '#orders', icon: <Package />, current: true, count: 12 },\n { label: 'Shipments', href: '#shipments', icon: <Truck /> },\n { label: 'Returns', href: '#returns', icon: <Inbox />, count: 3 },\n ],\n },\n {\n title: 'Catalogue',\n icon: <Boxes />,\n defaultOpen: false,\n items: [\n { label: 'Products', href: '#products', icon: <Box /> },\n { label: 'Inventory', href: '#inventory', icon: <Map /> },\n ],\n },\n {\n title: 'Account',\n icon: <UserCog />,\n collapsible: false,\n items: [\n { label: 'Settings', href: '#settings', icon: <Settings /> },\n { label: 'Team', href: '#team', icon: <Users /> },\n ],\n },\n]\n\nconst HEADER = <p className=\"font-display font-semibold\">Hexline ops</p>\n\nconst FOOTER = <p className=\"text-xs text-[var(--color-text-secondary)]\">Signed in as Ana Pop</p>\n\nconst closeOnChoose = ({ onClick, ...props }: LinkRenderProps) => (\n <SheetClose asChild type={undefined}>\n <a {...props} onClick={onClick} />\n </SheetClose>\n)\n\n<Sheet>\n <SheetTrigger asChild>\n <Button variant=\"secondary\">Open navigation</Button>\n </SheetTrigger>\n <SheetContent side=\"left\" title=\"Navigation\">\n <Sidebar\n label=\"Dashboard navigation, in a sheet\"\n groups={GROUPS}\n header={HEADER}\n footer={FOOTER}\n renderLink={closeOnChoose}\n />\n </SheetContent>\n</Sheet>"
|
|
11618
12041
|
}
|
|
11619
|
-
]
|
|
12042
|
+
],
|
|
12043
|
+
"closed": true
|
|
11620
12044
|
}
|
|
11621
12045
|
]
|
|
11622
12046
|
}
|