@arbor-education/design-system.components 0.25.3 → 0.25.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.gather/skills/write-stories/README.md +1 -1
  2. package/.gather/skills/write-stories/SKILL.md +1 -1
  3. package/.gather/skills/write-stories/meta.md +1 -1
  4. package/CHANGELOG.md +16 -0
  5. package/component-library.md +26 -1
  6. package/dist/components/breadcrumbs/BreadcrumbCopyButton.d.ts +3 -0
  7. package/dist/components/breadcrumbs/BreadcrumbCopyButton.d.ts.map +1 -0
  8. package/dist/components/breadcrumbs/BreadcrumbCopyButton.js +13 -0
  9. package/dist/components/breadcrumbs/BreadcrumbCopyButton.js.map +1 -0
  10. package/dist/components/breadcrumbs/BreadcrumbDropdown.d.ts +3 -0
  11. package/dist/components/breadcrumbs/BreadcrumbDropdown.d.ts.map +1 -0
  12. package/dist/components/breadcrumbs/BreadcrumbDropdown.js +8 -0
  13. package/dist/components/breadcrumbs/BreadcrumbDropdown.js.map +1 -0
  14. package/dist/components/breadcrumbs/BreadcrumbEllipsis.d.ts +3 -0
  15. package/dist/components/breadcrumbs/BreadcrumbEllipsis.d.ts.map +1 -0
  16. package/dist/components/breadcrumbs/BreadcrumbEllipsis.js +7 -0
  17. package/dist/components/breadcrumbs/BreadcrumbEllipsis.js.map +1 -0
  18. package/dist/components/breadcrumbs/BreadcrumbItemSeparator.d.ts +2 -0
  19. package/dist/components/breadcrumbs/BreadcrumbItemSeparator.d.ts.map +1 -0
  20. package/dist/components/breadcrumbs/BreadcrumbItemSeparator.js +5 -0
  21. package/dist/components/breadcrumbs/BreadcrumbItemSeparator.js.map +1 -0
  22. package/dist/components/breadcrumbs/BreadcrumbLink.d.ts +6 -0
  23. package/dist/components/breadcrumbs/BreadcrumbLink.d.ts.map +1 -0
  24. package/dist/components/breadcrumbs/BreadcrumbLink.js +9 -0
  25. package/dist/components/breadcrumbs/BreadcrumbLink.js.map +1 -0
  26. package/dist/components/breadcrumbs/Breadcrumbs.d.ts +21 -0
  27. package/dist/components/breadcrumbs/Breadcrumbs.d.ts.map +1 -0
  28. package/dist/components/breadcrumbs/Breadcrumbs.js +51 -0
  29. package/dist/components/breadcrumbs/Breadcrumbs.js.map +1 -0
  30. package/dist/components/breadcrumbs/Breadcrumbs.stories.d.ts +81 -0
  31. package/dist/components/breadcrumbs/Breadcrumbs.stories.d.ts.map +1 -0
  32. package/dist/components/breadcrumbs/Breadcrumbs.stories.js +681 -0
  33. package/dist/components/breadcrumbs/Breadcrumbs.stories.js.map +1 -0
  34. package/dist/components/breadcrumbs/Breadcrumbs.test.d.ts +2 -0
  35. package/dist/components/breadcrumbs/Breadcrumbs.test.d.ts.map +1 -0
  36. package/dist/components/breadcrumbs/Breadcrumbs.test.js +160 -0
  37. package/dist/components/breadcrumbs/Breadcrumbs.test.js.map +1 -0
  38. package/dist/components/breadcrumbs/types.d.ts +27 -0
  39. package/dist/components/breadcrumbs/types.d.ts.map +1 -0
  40. package/dist/components/breadcrumbs/types.js +2 -0
  41. package/dist/components/breadcrumbs/types.js.map +1 -0
  42. package/dist/components/fileUpload/FileUpload.d.ts +21 -0
  43. package/dist/components/fileUpload/FileUpload.d.ts.map +1 -0
  44. package/dist/components/fileUpload/FileUpload.js +23 -0
  45. package/dist/components/fileUpload/FileUpload.js.map +1 -0
  46. package/dist/components/fileUpload/FileUpload.stories.d.ts +71 -0
  47. package/dist/components/fileUpload/FileUpload.stories.d.ts.map +1 -0
  48. package/dist/components/fileUpload/FileUpload.stories.js +736 -0
  49. package/dist/components/fileUpload/FileUpload.stories.js.map +1 -0
  50. package/dist/components/fileUpload/FileUpload.test.d.ts +2 -0
  51. package/dist/components/fileUpload/FileUpload.test.d.ts.map +1 -0
  52. package/dist/components/fileUpload/FileUpload.test.js +99 -0
  53. package/dist/components/fileUpload/FileUpload.test.js.map +1 -0
  54. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +1 -1
  55. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  56. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
  57. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +14 -15
  58. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
  59. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js +11 -0
  60. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -1
  61. package/dist/components/icon/allowedIcons.d.ts +1 -0
  62. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  63. package/dist/components/icon/allowedIcons.js +2 -1
  64. package/dist/components/icon/allowedIcons.js.map +1 -1
  65. package/dist/components/skeleton/Skeleton.d.ts +10 -0
  66. package/dist/components/skeleton/Skeleton.d.ts.map +1 -0
  67. package/dist/components/skeleton/Skeleton.js +9 -0
  68. package/dist/components/skeleton/Skeleton.js.map +1 -0
  69. package/dist/components/skeleton/Skeleton.stories.d.ts +99 -0
  70. package/dist/components/skeleton/Skeleton.stories.d.ts.map +1 -0
  71. package/dist/components/skeleton/Skeleton.stories.js +539 -0
  72. package/dist/components/skeleton/Skeleton.stories.js.map +1 -0
  73. package/dist/components/skeleton/Skeleton.test.d.ts +2 -0
  74. package/dist/components/skeleton/Skeleton.test.d.ts.map +1 -0
  75. package/dist/components/skeleton/Skeleton.test.js +49 -0
  76. package/dist/components/skeleton/Skeleton.test.js.map +1 -0
  77. package/dist/index.css +225 -0
  78. package/dist/index.css.map +1 -1
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +3 -0
  82. package/dist/index.js.map +1 -1
  83. package/package.json +2 -1
  84. package/src/components/breadcrumbs/BreadcrumbCopyButton.tsx +27 -0
  85. package/src/components/breadcrumbs/BreadcrumbDropdown.tsx +27 -0
  86. package/src/components/breadcrumbs/BreadcrumbEllipsis.tsx +16 -0
  87. package/src/components/breadcrumbs/BreadcrumbItemSeparator.tsx +3 -0
  88. package/src/components/breadcrumbs/BreadcrumbLink.tsx +29 -0
  89. package/src/components/breadcrumbs/Breadcrumbs.stories.tsx +870 -0
  90. package/src/components/breadcrumbs/Breadcrumbs.test.tsx +185 -0
  91. package/src/components/breadcrumbs/Breadcrumbs.tsx +92 -0
  92. package/src/components/breadcrumbs/breadcrumbs.scss +158 -0
  93. package/src/components/breadcrumbs/types.ts +31 -0
  94. package/src/components/button/button.scss +2 -0
  95. package/src/components/dropdown/dropdown.scss +1 -0
  96. package/src/components/fileUpload/FileUpload.stories.tsx +920 -0
  97. package/src/components/fileUpload/FileUpload.test.tsx +172 -0
  98. package/src/components/fileUpload/FileUpload.tsx +144 -0
  99. package/src/components/fileUpload/fileUpload.scss +74 -0
  100. package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +15 -16
  101. package/src/components/formField/inputs/selectDropdown/SelectDropdown.test.tsx +19 -0
  102. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +1 -1
  103. package/src/components/icon/allowedIcons.tsx +2 -0
  104. package/src/components/progress/progress.scss +1 -0
  105. package/src/components/skeleton/Skeleton.stories.tsx +748 -0
  106. package/src/components/skeleton/Skeleton.test.tsx +55 -0
  107. package/src/components/skeleton/Skeleton.tsx +36 -0
  108. package/src/components/skeleton/skeleton.scss +23 -0
  109. package/src/index.scss +3 -0
  110. package/src/index.ts +3 -0
@@ -0,0 +1,681 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
4
+ import { Breadcrumbs } from './Breadcrumbs.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Docs page content
7
+ // ---------------------------------------------------------------------------
8
+ const DESCRIPTION_INTRO = [
9
+ 'Breadcrumbs render a navigation trail that shows users where they are within',
10
+ 'the application hierarchy. Items can be plain links, dropdown menus for branching',
11
+ 'at a level, or an ellipsis for collapsing deep trails. An automatic copy-to-clipboard',
12
+ 'button appears on the final (active) item.',
13
+ ].join(' ');
14
+ const USAGE_GUIDANCE = [
15
+ '### When to use',
16
+ '',
17
+ '- To orient users within a multi-level navigation hierarchy (e.g. Home → Students → Year 11 → Attendance).',
18
+ '- When users need a quick way to navigate back to any ancestor page.',
19
+ '- When the current page sits more than one level deep in the application.',
20
+ '- When you want to give users a copyable trail for sharing context — the copy button is automatic.',
21
+ '',
22
+ '---',
23
+ '',
24
+ '### When NOT to use',
25
+ '',
26
+ '| Situation | Use instead |',
27
+ '|---|---|',
28
+ '| The page is at the top level (no parent) | No breadcrumb needed |',
29
+ '| Navigation hierarchy changes via user action on the same URL | [`Tabs`](?path=/docs/components-tabs--docs) |',
30
+ '| The trail contains only one item | Omit the breadcrumb |',
31
+ '| Step-by-step wizard progress | Stepper component |',
32
+ ].join('\n');
33
+ const DEVELOPER_NOTES = [
34
+ '### Critical usage patterns',
35
+ '',
36
+ '**Active item renders as `<a aria-current="page">` (non-navigable)**',
37
+ '',
38
+ 'The last item always renders as an anchor with `aria-current="page"`. Its `href` is intentionally',
39
+ 'discarded — the current page anchor is non-navigable to avoid the no-op of navigating to the',
40
+ 'current page.',
41
+ '',
42
+ '**Copy button is automatic**',
43
+ '',
44
+ '`BreadcrumbCopyButton` renders automatically after the last non-ellipsis item. By default it',
45
+ 'concatenates non-ellipsis `item.label` values with " > ". When the trail is collapsed (contains',
46
+ 'an ellipsis), pass `copyText` with the full path so hidden items are included in the copy.',
47
+ 'When `navigator.clipboard` is unavailable (non-HTTPS contexts), the button is a silent no-op.',
48
+ '',
49
+ '**`breadcrumbLabel` and WCAG**',
50
+ '',
51
+ 'The `breadcrumbLabel` prop sets `aria-label` on the `<nav>` wrapper. When a page contains',
52
+ 'more than one `<nav>` landmark, every nav must have a distinct accessible name. Always supply',
53
+ '`breadcrumbLabel="Breadcrumb"` to avoid a WCAG 2.4.1 failure.',
54
+ '',
55
+ '**Ellipsis expand pattern**',
56
+ '',
57
+ 'Set `onClick` on the ellipsis item in the `items` array — the component forwards it to the',
58
+ 'underlying button. Manage the visible `items` in component state, swapping the collapsed list',
59
+ '(containing the ellipsis item with `onClick`) for the full list when the user clicks.',
60
+ 'See the **Expandable ellipsis** story for the exact pattern.',
61
+ '',
62
+ '---',
63
+ '',
64
+ '### Accessibility',
65
+ '',
66
+ '- The `<nav>` landmark is labelled via `breadcrumbLabel` → `aria-label`.',
67
+ '- The `<ol>` communicates order and count to screen readers.',
68
+ '- The active (last) item carries `aria-current="page"` automatically.',
69
+ '- Separators (`/`) are wrapped in `aria-hidden="true"` — invisible to assistive technology.',
70
+ '- The copy button tooltip ("Copy breadcrumb path" / "Breadcrumb path copied") is its accessible label.',
71
+ '- Dropdown items use a Radix UI-powered popover with full keyboard navigation.',
72
+ '- The ellipsis `item.label` must be descriptive — e.g. "Show hidden pages", not "...".',
73
+ '',
74
+ '---',
75
+ '',
76
+ '### TypeScript types',
77
+ '',
78
+ '| Type | Description |',
79
+ '|---|---|',
80
+ '| `Breadcrumbs.Props` | Full props for the `<Breadcrumbs>` component |',
81
+ '| `Breadcrumbs.LinkProps` | Props for `Breadcrumbs.Link` sub-component |',
82
+ '| `Breadcrumbs.EllipsisProps` | Props for `Breadcrumbs.Ellipsis` sub-component |',
83
+ '| `Breadcrumbs.DropdownProps` | Props for `Breadcrumbs.Dropdown` sub-component |',
84
+ '| `Breadcrumbs.CopyButtonProps` | Props for `Breadcrumbs.CopyButton` sub-component |',
85
+ ].join('\n');
86
+ const RELATED_COMPONENTS = [
87
+ '## Related components',
88
+ '',
89
+ '[Dropdown](?path=/docs/components-dropdown--docs) · [Tooltip / TooltipWrapper](?path=/docs/components-tooltip-tooltipwrapper--docs)',
90
+ ].join('\n');
91
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
92
+ // ---------------------------------------------------------------------------
93
+ // Docs page
94
+ // ---------------------------------------------------------------------------
95
+ function BreadcrumbsDocsPage() {
96
+ return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(DocHeading, { children: "Interactive example" }), _jsx(Markdown, { children: PROPS_INTRO }), _jsx(DocPrimary, {}), _jsx(Controls, {}), _jsx(DocHeading, { children: "Usage guidance" }), _jsx(Markdown, { children: USAGE_GUIDANCE }), _jsx(DocHeading, { children: "Developer notes" }), _jsx(Markdown, { children: DEVELOPER_NOTES }), _jsx(DocHeading, { children: "Examples" }), _jsx(Stories, { title: "" }), _jsx(Markdown, { children: RELATED_COMPONENTS })] }));
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Meta
100
+ // ---------------------------------------------------------------------------
101
+ const meta = {
102
+ title: 'Components/Breadcrumbs',
103
+ component: Breadcrumbs,
104
+ tags: ['autodocs'],
105
+ parameters: {
106
+ layout: 'padded',
107
+ docs: { page: BreadcrumbsDocsPage },
108
+ },
109
+ argTypes: {
110
+ items: {
111
+ control: 'object',
112
+ description: [
113
+ 'Array of `BreadcrumbItem` objects that define the trail.',
114
+ 'Each item has: `label` (string, required — also used as the clipboard copy text),',
115
+ '`href` (string, optional — used for all items except the last, which is always non-navigable regardless of whether `href` is set),',
116
+ '`dropdownOptions` (array of `BreadcrumbItem`, optional — renders a dropdown trigger; items may themselves have `dropdownOptions` for nested menus),',
117
+ '`isEllipsis` (boolean, optional — renders an icon-only ellipsis button),',
118
+ '`onClick` (function, optional — forwarded to the ellipsis button; use to expand a collapsed trail).',
119
+ ].join(' '),
120
+ table: { type: { summary: 'BreadcrumbItem[]' } },
121
+ },
122
+ breadcrumbLabel: {
123
+ control: 'text',
124
+ description: [
125
+ 'Sets `aria-label` on the wrapping `<nav>` landmark. Required.',
126
+ 'When a page contains more than one `<nav>` element, every nav must have a distinct accessible name.',
127
+ 'Use `"Breadcrumb"` as the conventional value.',
128
+ ].join(' '),
129
+ table: {
130
+ type: { summary: 'string' },
131
+ },
132
+ },
133
+ copyText: {
134
+ control: 'text',
135
+ description: [
136
+ 'Overrides the string written to the clipboard when the copy button is clicked.',
137
+ 'Use this when the visible `items` array is collapsed (contains an ellipsis) and',
138
+ 'the copy should reflect the full navigation path rather than only the visible labels.',
139
+ 'Defaults to non-ellipsis item labels joined with " > ".',
140
+ ].join(' '),
141
+ table: { type: { summary: 'string' }, defaultValue: { summary: 'non-ellipsis labels joined with " > "' } },
142
+ },
143
+ className: {
144
+ control: false,
145
+ description: 'Additional CSS class names applied to the `<nav>` container.',
146
+ table: { type: { summary: 'string' } },
147
+ },
148
+ },
149
+ };
150
+ export default meta;
151
+ // ---------------------------------------------------------------------------
152
+ // Helper: attach a per-story description to docs
153
+ // ---------------------------------------------------------------------------
154
+ const withDescription = (story, description) => ({
155
+ ...story,
156
+ parameters: {
157
+ ...story.parameters,
158
+ docs: {
159
+ ...story.parameters?.docs,
160
+ description: {
161
+ story: description,
162
+ },
163
+ },
164
+ },
165
+ });
166
+ // ---------------------------------------------------------------------------
167
+ // Template components for stateful and composed stories.
168
+ // ---------------------------------------------------------------------------
169
+ const FULL_ITEMS = [
170
+ { label: 'Home', href: '/' },
171
+ { label: 'Students', href: '/students' },
172
+ { label: 'Year 11', href: '/students/year-11' },
173
+ { label: 'Class 11B', href: '/students/year-11/11b' },
174
+ { label: 'Attendance', href: '/students/year-11/11b/attendance' },
175
+ { label: 'Weekly summary' },
176
+ ];
177
+ const FULL_COPY_TEXT = FULL_ITEMS.map(item => item.label).join(' > ');
178
+ const ExpandableEllipsisTemplate = () => {
179
+ const [expanded, setExpanded] = useState(false);
180
+ const items = expanded
181
+ ? FULL_ITEMS
182
+ : [
183
+ FULL_ITEMS[0],
184
+ { label: 'Show hidden pages', isEllipsis: true, onClick: () => setExpanded(true) },
185
+ FULL_ITEMS[FULL_ITEMS.length - 2],
186
+ FULL_ITEMS[FULL_ITEMS.length - 1],
187
+ ];
188
+ return _jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: items, copyText: FULL_COPY_TEXT });
189
+ };
190
+ const EllipsisAsLastItemTemplate = () => (_jsxs("div", { style: {
191
+ padding: 'var(--spacing-xlarge)',
192
+ display: 'flex',
193
+ flexDirection: 'column',
194
+ gap: 'var(--spacing-large)',
195
+ }, children: [_jsxs("div", { style: {
196
+ display: 'flex',
197
+ flexDirection: 'column',
198
+ gap: 'var(--spacing-xsmall)',
199
+ }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-semantic-destructive-600)' }, children: "No copy button \u2014 last item is ellipsis (avoid this)" }), _jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
200
+ { label: 'Home', href: '/' },
201
+ { label: 'Students', href: '/students' },
202
+ { label: 'Show more pages', isEllipsis: true },
203
+ ] })] }), _jsxs("div", { style: {
204
+ display: 'flex',
205
+ flexDirection: 'column',
206
+ gap: 'var(--spacing-xsmall)',
207
+ }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "Copy button present \u2014 last item is the active page (correct)" }), _jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
208
+ { label: 'Home', href: '/' },
209
+ { label: 'Students', href: '/students' },
210
+ { label: 'Attendance' },
211
+ ] })] })] }));
212
+ const WithBreadcrumbLabelTemplate = () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
213
+ { label: 'Home', href: '/' },
214
+ { label: 'Settings', href: '/settings' },
215
+ { label: 'Notifications' },
216
+ ] }));
217
+ const ActiveItemTemplate = () => (_jsxs("div", { style: {
218
+ padding: 'var(--spacing-xlarge)',
219
+ display: 'flex',
220
+ flexDirection: 'column',
221
+ gap: 'var(--spacing-xsmall)',
222
+ }, children: [_jsxs("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: ["The last item has", ' ', _jsx("code", { children: "href=\"/behaviour/sanctions\"" }), ' ', "in the items array, but the active item is always non-navigable \u2014 inspect the DOM and you will find", ' ', _jsx("code", { children: "<a aria-current=\"page\">" }), ' ', "with no href. This prevents the current-page no-op navigation."] }), _jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
223
+ { label: 'Home', href: '/' },
224
+ { label: 'Behaviour', href: '/behaviour' },
225
+ { label: 'Sanctions log', href: '/behaviour/sanctions' },
226
+ ] })] }));
227
+ // ---------------------------------------------------------------------------
228
+ // Stories
229
+ // ---------------------------------------------------------------------------
230
+ export const Default = withDescription({
231
+ args: {
232
+ items: [
233
+ { label: 'Home', href: '/' },
234
+ { label: 'Students', href: '/students' },
235
+ { label: 'Year 11', href: '/students/year-11' },
236
+ { label: 'Attendance', href: '/students/year-11/attendance' },
237
+ { label: 'Weekly summary' },
238
+ ],
239
+ breadcrumbLabel: 'Breadcrumb',
240
+ },
241
+ render: args => _jsx(Breadcrumbs, { ...args }),
242
+ }, [
243
+ 'The interactive canvas — every prop is wired to the Controls panel.',
244
+ 'Adjust `items` to change the trail. The last item always renders as the active page',
245
+ '(`<a aria-current="page">` without an href), regardless of whether the item has an `href`.',
246
+ ].join(' '));
247
+ export const TwoItems = withDescription({
248
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [{ label: 'Home', href: '/' }, { label: 'Dashboard' }] })),
249
+ parameters: {
250
+ controls: { disable: true },
251
+ docs: {
252
+ source: {
253
+ language: 'tsx',
254
+ code: `
255
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
256
+
257
+ function TwoItemsBreadcrumb() {
258
+ return (
259
+ <Breadcrumbs
260
+ breadcrumbLabel="Breadcrumb"
261
+ items={[
262
+ { label: 'Home', href: '/' },
263
+ { label: 'Dashboard' },
264
+ ]}
265
+ />
266
+ );
267
+ }
268
+ export default TwoItemsBreadcrumb;
269
+ `.trim(),
270
+ },
271
+ },
272
+ },
273
+ }, 'The minimum meaningful trail: one parent link and the active page. The copy button appears automatically on the active item.');
274
+ export const LongTrail = withDescription({
275
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
276
+ { label: 'Home', href: '/' },
277
+ { label: 'Students', href: '/students' },
278
+ { label: 'Year 11', href: '/students/year-11' },
279
+ { label: 'Class 11B', href: '/students/year-11/11b' },
280
+ { label: 'Attendance', href: '/students/year-11/11b/attendance' },
281
+ {
282
+ label: 'Term reports',
283
+ href: '/students/year-11/11b/attendance/term-reports',
284
+ },
285
+ { label: 'Autumn term 2024' },
286
+ ] })),
287
+ parameters: {
288
+ controls: { disable: true },
289
+ docs: {
290
+ source: {
291
+ language: 'tsx',
292
+ code: `
293
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
294
+
295
+ function LongTrailBreadcrumb() {
296
+ return (
297
+ <Breadcrumbs
298
+ breadcrumbLabel="Breadcrumb"
299
+ items={[
300
+ { label: 'Home', href: '/' },
301
+ { label: 'Students', href: '/students' },
302
+ { label: 'Year 11', href: '/students/year-11' },
303
+ { label: 'Class 11B', href: '/students/year-11/11b' },
304
+ { label: 'Attendance', href: '/students/year-11/11b/attendance' },
305
+ { label: 'Term reports', href: '/students/year-11/11b/attendance/term-reports' },
306
+ { label: 'Autumn term 2024' },
307
+ ]}
308
+ />
309
+ );
310
+ }
311
+ export default LongTrailBreadcrumb;
312
+ `.trim(),
313
+ },
314
+ },
315
+ },
316
+ }, 'A seven-item trail demonstrating depth and line-wrapping. All intermediate items render as links; the final item is the active page.');
317
+ export const WithDropdown = withDescription({
318
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
319
+ { label: 'Home', href: '/' },
320
+ {
321
+ label: 'Students',
322
+ href: '/students',
323
+ dropdownOptions: [
324
+ { label: 'All students', href: '/students' },
325
+ { label: 'Year 10', href: '/students/year-10' },
326
+ { label: 'Year 11', href: '/students/year-11' },
327
+ { label: 'Year 12', href: '/students/year-12' },
328
+ ],
329
+ },
330
+ { label: 'Behaviour' },
331
+ ] })),
332
+ parameters: {
333
+ controls: { disable: true },
334
+ docs: {
335
+ source: {
336
+ language: 'tsx',
337
+ code: `
338
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
339
+
340
+ function WithDropdownBreadcrumb() {
341
+ return (
342
+ <Breadcrumbs
343
+ breadcrumbLabel="Breadcrumb"
344
+ items={[
345
+ { label: 'Home', href: '/' },
346
+ {
347
+ label: 'Students',
348
+ href: '/students',
349
+ dropdownOptions: [
350
+ { label: 'All students', href: '/students' },
351
+ { label: 'Year 10', href: '/students/year-10' },
352
+ { label: 'Year 11', href: '/students/year-11' },
353
+ { label: 'Year 12', href: '/students/year-12' },
354
+ ],
355
+ },
356
+ { label: 'Behaviour' },
357
+ ]}
358
+ />
359
+ );
360
+ }
361
+ export default WithDropdownBreadcrumb;
362
+ `.trim(),
363
+ },
364
+ },
365
+ },
366
+ }, [
367
+ 'When an item has `dropdownOptions`, it renders as a dropdown trigger (chevron icon).',
368
+ 'Clicking opens a Radix UI-powered popover with the supplied links.',
369
+ 'The popover portals to the document body — do not place `overflow: hidden` on any ancestor container.',
370
+ ].join(' '));
371
+ export const MultipleDropdowns = withDescription({
372
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
373
+ { label: 'Home', href: '/' },
374
+ {
375
+ label: 'Students',
376
+ href: '/students',
377
+ dropdownOptions: [
378
+ { label: 'All students', href: '/students' },
379
+ { label: 'Year 10', href: '/students/year-10' },
380
+ { label: 'Year 11', href: '/students/year-11' },
381
+ ],
382
+ },
383
+ {
384
+ label: 'Attendance',
385
+ href: '/students/year-11/attendance',
386
+ dropdownOptions: [
387
+ {
388
+ label: 'Daily register',
389
+ href: '/students/year-11/attendance/daily',
390
+ },
391
+ {
392
+ label: 'Weekly summary',
393
+ href: '/students/year-11/attendance/weekly',
394
+ },
395
+ {
396
+ label: 'Term report',
397
+ href: '/students/year-11/attendance/term',
398
+ },
399
+ ],
400
+ },
401
+ { label: 'Registration' },
402
+ ] })),
403
+ parameters: {
404
+ controls: { disable: true },
405
+ docs: {
406
+ source: {
407
+ language: 'tsx',
408
+ code: `
409
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
410
+
411
+ function MultipleDropdownsBreadcrumb() {
412
+ return (
413
+ <Breadcrumbs
414
+ breadcrumbLabel="Breadcrumb"
415
+ items={[
416
+ { label: 'Home', href: '/' },
417
+ {
418
+ label: 'Students',
419
+ href: '/students',
420
+ dropdownOptions: [
421
+ { label: 'All students', href: '/students' },
422
+ { label: 'Year 10', href: '/students/year-10' },
423
+ { label: 'Year 11', href: '/students/year-11' },
424
+ ],
425
+ },
426
+ {
427
+ label: 'Attendance',
428
+ href: '/students/year-11/attendance',
429
+ dropdownOptions: [
430
+ { label: 'Daily register', href: '/students/year-11/attendance/daily' },
431
+ { label: 'Weekly summary', href: '/students/year-11/attendance/weekly' },
432
+ { label: 'Term report', href: '/students/year-11/attendance/term' },
433
+ ],
434
+ },
435
+ { label: 'Registration' },
436
+ ]}
437
+ />
438
+ );
439
+ }
440
+ export default MultipleDropdownsBreadcrumb;
441
+ `.trim(),
442
+ },
443
+ },
444
+ },
445
+ }, 'Two intermediate items each have their own independent dropdown. Each opens a separate Radix UI popover.');
446
+ export const WithEllipsis = withDescription({
447
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
448
+ { label: 'Home', href: '/' },
449
+ { label: 'Show hidden pages', isEllipsis: true },
450
+ { label: 'Year 11', href: '/students/year-11' },
451
+ { label: 'Attendance' },
452
+ ] })),
453
+ parameters: {
454
+ controls: { disable: true },
455
+ docs: {
456
+ source: {
457
+ language: 'tsx',
458
+ code: `
459
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
460
+
461
+ function WithEllipsisBreadcrumb() {
462
+ return (
463
+ <Breadcrumbs
464
+ breadcrumbLabel="Breadcrumb"
465
+ items={[
466
+ { label: 'Home', href: '/' },
467
+ { label: 'Show hidden pages', isEllipsis: true },
468
+ { label: 'Year 11', href: '/students/year-11' },
469
+ { label: 'Attendance' },
470
+ ]}
471
+ />
472
+ );
473
+ }
474
+ export default WithEllipsisBreadcrumb;
475
+ `.trim(),
476
+ },
477
+ },
478
+ },
479
+ }, [
480
+ 'An ellipsis item (`isEllipsis: true`) renders as an icon-only button.',
481
+ 'The `item.label` value ("Show hidden pages") becomes the accessible button label — make it descriptive.',
482
+ 'This static example has no click handler. See **Expandable ellipsis** for the interactive version.',
483
+ ].join(' '));
484
+ export const ExpandableEllipsis = withDescription({
485
+ render: ExpandableEllipsisTemplate,
486
+ parameters: {
487
+ controls: { disable: true },
488
+ docs: {
489
+ source: {
490
+ language: 'tsx',
491
+ code: `
492
+ import { useState } from 'react';
493
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
494
+
495
+ type BreadcrumbItem = Breadcrumbs.Props['items'][number];
496
+
497
+ const FULL_ITEMS: BreadcrumbItem[] = [
498
+ { label: 'Home', href: '/' },
499
+ { label: 'Students', href: '/students' },
500
+ { label: 'Year 11', href: '/students/year-11' },
501
+ { label: 'Class 11B', href: '/students/year-11/11b' },
502
+ { label: 'Attendance', href: '/students/year-11/11b/attendance' },
503
+ { label: 'Weekly summary' },
504
+ ];
505
+
506
+ function ExpandableEllipsisBreadcrumb() {
507
+ const [expanded, setExpanded] = useState(false);
508
+
509
+ const items = expanded
510
+ ? FULL_ITEMS
511
+ : [
512
+ FULL_ITEMS[0],
513
+ { label: 'Show hidden pages', isEllipsis: true, onClick: () => setExpanded(true) },
514
+ FULL_ITEMS[FULL_ITEMS.length - 2],
515
+ FULL_ITEMS[FULL_ITEMS.length - 1],
516
+ ];
517
+
518
+ const copyText = FULL_ITEMS.map(item => item.label).join(' > ');
519
+
520
+ return <Breadcrumbs breadcrumbLabel="Breadcrumb" items={items} copyText={copyText} />;
521
+ }
522
+ export default ExpandableEllipsisBreadcrumb;
523
+ `.trim(),
524
+ },
525
+ },
526
+ },
527
+ }, [
528
+ 'Click the ellipsis button to expand the full trail.',
529
+ 'Set `onClick` on the ellipsis item and manage the visible `items` array in state —',
530
+ 'swap the collapsed list for the full list on click.',
531
+ 'The `item.label` on the ellipsis becomes its accessible button label, so make it descriptive.',
532
+ ].join(' '));
533
+ export const WithDropdownAndEllipsis = withDescription({
534
+ render: () => (_jsx(Breadcrumbs, { breadcrumbLabel: "Breadcrumb", items: [
535
+ { label: 'Home', href: '/' },
536
+ { label: 'Show hidden pages', isEllipsis: true },
537
+ {
538
+ label: 'Year 11',
539
+ href: '/students/year-11',
540
+ dropdownOptions: [
541
+ { label: 'Year 9', href: '/students/year-9' },
542
+ { label: 'Year 10', href: '/students/year-10' },
543
+ { label: 'Year 11', href: '/students/year-11' },
544
+ { label: 'Year 12', href: '/students/year-12' },
545
+ ],
546
+ },
547
+ { label: 'Reports' },
548
+ ] })),
549
+ parameters: {
550
+ controls: { disable: true },
551
+ docs: {
552
+ source: {
553
+ language: 'tsx',
554
+ code: `
555
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
556
+
557
+ function WithDropdownAndEllipsisBreadcrumb() {
558
+ return (
559
+ <Breadcrumbs
560
+ breadcrumbLabel="Breadcrumb"
561
+ items={[
562
+ { label: 'Home', href: '/' },
563
+ { label: 'Show hidden pages', isEllipsis: true },
564
+ {
565
+ label: 'Year 11',
566
+ href: '/students/year-11',
567
+ dropdownOptions: [
568
+ { label: 'Year 9', href: '/students/year-9' },
569
+ { label: 'Year 10', href: '/students/year-10' },
570
+ { label: 'Year 11', href: '/students/year-11' },
571
+ { label: 'Year 12', href: '/students/year-12' },
572
+ ],
573
+ },
574
+ { label: 'Reports' },
575
+ ]}
576
+ />
577
+ );
578
+ }
579
+ export default WithDropdownAndEllipsisBreadcrumb;
580
+ `.trim(),
581
+ },
582
+ },
583
+ },
584
+ }, 'Ellipsis and dropdown items can appear together. The ellipsis collapses hidden ancestors; the dropdown exposes sibling pages at a given level.');
585
+ export const EllipsisAsLastItem = withDescription({
586
+ render: EllipsisAsLastItemTemplate,
587
+ parameters: {
588
+ controls: { disable: true },
589
+ docs: {
590
+ source: {
591
+ language: 'tsx',
592
+ code: `
593
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
594
+
595
+ // When isEllipsis is true on the last item, the copy button is suppressed.
596
+ // Avoid placing an ellipsis last — always put the active page last.
597
+ function EllipsisAsLastItemBreadcrumb() {
598
+ return (
599
+ <Breadcrumbs
600
+ breadcrumbLabel="Breadcrumb"
601
+ items={[
602
+ { label: 'Home', href: '/' },
603
+ { label: 'Students', href: '/students' },
604
+ { label: 'Show more pages', isEllipsis: true },
605
+ ]}
606
+ />
607
+ );
608
+ }
609
+ export default EllipsisAsLastItemBreadcrumb;
610
+ `.trim(),
611
+ },
612
+ },
613
+ },
614
+ }, [
615
+ 'When `isEllipsis: true` is set on the last item, the copy button is suppressed.',
616
+ 'This is current behaviour — avoid placing an ellipsis last; use it only for collapsed intermediate items.',
617
+ ].join(' '));
618
+ export const WithBreadcrumbLabel = withDescription({
619
+ render: WithBreadcrumbLabelTemplate,
620
+ parameters: {
621
+ controls: { disable: true },
622
+ docs: {
623
+ source: {
624
+ language: 'tsx',
625
+ code: `
626
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
627
+
628
+ // Always supply breadcrumbLabel when the page contains more than one <nav>.
629
+ function WithBreadcrumbLabelBreadcrumb() {
630
+ return (
631
+ <Breadcrumbs
632
+ breadcrumbLabel="Breadcrumb"
633
+ items={[
634
+ { label: 'Home', href: '/' },
635
+ { label: 'Settings', href: '/settings' },
636
+ { label: 'Notifications' },
637
+ ]}
638
+ />
639
+ );
640
+ }
641
+ export default WithBreadcrumbLabelBreadcrumb;
642
+ `.trim(),
643
+ },
644
+ },
645
+ },
646
+ }, '`breadcrumbLabel` is required. When a page contains more than one `<nav>` landmark, each needs a distinct `aria-label`. "Breadcrumb" is the conventional value.');
647
+ export const ActiveItemAriaCurrent = withDescription({
648
+ render: ActiveItemTemplate,
649
+ parameters: {
650
+ controls: { disable: true },
651
+ docs: {
652
+ source: {
653
+ language: 'tsx',
654
+ code: `
655
+ import { Breadcrumbs } from '@arbor-education/design-system.components';
656
+
657
+ function ActiveItemBreadcrumb() {
658
+ return (
659
+ // The last item renders as <a aria-current="page"> with no href.
660
+ // The href from the items array is intentionally discarded for active items.
661
+ <Breadcrumbs
662
+ breadcrumbLabel="Breadcrumb"
663
+ items={[
664
+ { label: 'Home', href: '/' },
665
+ { label: 'Behaviour', href: '/behaviour' },
666
+ { label: 'Sanctions log', href: '/behaviour/sanctions' },
667
+ ]}
668
+ />
669
+ );
670
+ }
671
+ export default ActiveItemBreadcrumb;
672
+ `.trim(),
673
+ },
674
+ },
675
+ },
676
+ }, [
677
+ 'The last item has `href="/behaviour/sanctions"` in the items array, but the active item is always non-navigable.',
678
+ 'Inspect the DOM and you will find `<a aria-current="page">` — no `href`. This prevents the no-op of',
679
+ 'navigating to the current page.',
680
+ ].join(' '));
681
+ //# sourceMappingURL=Breadcrumbs.stories.js.map