@arbor-education/design-system.components 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
  2. package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
  3. package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
  4. package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
  5. package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
  6. package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
  7. package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
  8. package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
  9. package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
  10. package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
  11. package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
  12. package/.gather/gather.yaml +9 -0
  13. package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
  14. package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
  15. package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
  16. package/.gather/skills/analyze-design/meta.md +4 -0
  17. package/{.claude → .gather}/skills/create-page/README.md +5 -0
  18. package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
  19. package/.gather/skills/create-page/meta.md +4 -0
  20. package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
  21. package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
  22. package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
  23. package/.gather/skills/map-legacy/meta.md +4 -0
  24. package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
  25. package/.gather/skills/migrate-page/meta.md +4 -0
  26. package/.gather/skills/write-stories/README.md +157 -0
  27. package/.gather/skills/write-stories/SKILL.md +841 -0
  28. package/.gather/skills/write-stories/meta.md +4 -0
  29. package/.ralph/storybook-upgrade/knowledge.md +308 -0
  30. package/.ralph/storybook-upgrade/prd.json +777 -0
  31. package/.ralph/storybook-upgrade/progress.md +342 -0
  32. package/.storybook/DocsTemplate.tsx +122 -0
  33. package/.storybook/preview.ts +40 -0
  34. package/.stylelintignore +2 -0
  35. package/CHANGELOG.md +6 -0
  36. package/{.claude/component-library.md → component-library.md} +27 -10
  37. package/dist/components/badge/Badge.stories.d.ts +85 -6
  38. package/dist/components/badge/Badge.stories.d.ts.map +1 -1
  39. package/dist/components/badge/Badge.stories.js +626 -27
  40. package/dist/components/badge/Badge.stories.js.map +1 -1
  41. package/dist/components/banner/Banner.stories.d.ts +129 -63
  42. package/dist/components/banner/Banner.stories.d.ts.map +1 -1
  43. package/dist/components/banner/Banner.stories.js +855 -39
  44. package/dist/components/banner/Banner.stories.js.map +1 -1
  45. package/dist/components/button/Button.stories.d.ts +148 -8
  46. package/dist/components/button/Button.stories.d.ts.map +1 -1
  47. package/dist/components/button/Button.stories.js +1089 -80
  48. package/dist/components/button/Button.stories.js.map +1 -1
  49. package/dist/components/dot/Dot.stories.d.ts +46 -11
  50. package/dist/components/dot/Dot.stories.d.ts.map +1 -1
  51. package/dist/components/dot/Dot.stories.js +504 -15
  52. package/dist/components/dot/Dot.stories.js.map +1 -1
  53. package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
  54. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
  55. package/dist/components/dropdown/Dropdown.stories.js +769 -17
  56. package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
  57. package/dist/components/formField/FormField.stories.d.ts +95 -35
  58. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  59. package/dist/components/formField/FormField.stories.js +1174 -69
  60. package/dist/components/formField/FormField.stories.js.map +1 -1
  61. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
  62. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  63. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
  64. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  65. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
  66. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
  67. package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
  68. package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
  69. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
  70. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  71. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
  72. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  73. package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
  74. package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
  75. package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
  76. package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
  77. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
  78. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
  79. package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
  80. package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
  81. package/dist/components/heading/Heading.stories.d.ts +449 -50
  82. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  83. package/dist/components/heading/Heading.stories.js +536 -60
  84. package/dist/components/heading/Heading.stories.js.map +1 -1
  85. package/dist/components/icon/Icon.stories.d.ts +81 -10
  86. package/dist/components/icon/Icon.stories.d.ts.map +1 -1
  87. package/dist/components/icon/Icon.stories.js +979 -8
  88. package/dist/components/icon/Icon.stories.js.map +1 -1
  89. package/dist/components/pill/Pill.stories.d.ts +71 -19
  90. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  91. package/dist/components/pill/Pill.stories.js +573 -14
  92. package/dist/components/pill/Pill.stories.js.map +1 -1
  93. package/dist/components/progress/Progress.stories.d.ts +75 -298
  94. package/dist/components/progress/Progress.stories.d.ts.map +1 -1
  95. package/dist/components/progress/Progress.stories.js +449 -52
  96. package/dist/components/progress/Progress.stories.js.map +1 -1
  97. package/dist/components/separator/Separator.stories.d.ts +58 -5
  98. package/dist/components/separator/Separator.stories.d.ts.map +1 -1
  99. package/dist/components/separator/Separator.stories.js +443 -4
  100. package/dist/components/separator/Separator.stories.js.map +1 -1
  101. package/dist/components/tag/Tag.stories.d.ts +116 -5
  102. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  103. package/dist/components/tag/Tag.stories.js +581 -28
  104. package/dist/components/tag/Tag.stories.js.map +1 -1
  105. package/dist/index.css +8 -1
  106. package/dist/index.css.map +1 -1
  107. package/eslint.config.mts +5 -1
  108. package/package.json +3 -3
  109. package/src/components/badge/Badge.stories.tsx +869 -42
  110. package/src/components/banner/Banner.stories.tsx +1081 -63
  111. package/src/components/button/Button.stories.tsx +1394 -99
  112. package/src/components/dot/Dot.stories.tsx +723 -32
  113. package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
  114. package/src/components/formField/FormField.stories.tsx +1522 -105
  115. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
  116. package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
  117. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
  118. package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
  119. package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
  120. package/src/components/heading/Heading.stories.tsx +752 -120
  121. package/src/components/icon/Icon.stories.tsx +1446 -12
  122. package/src/components/pill/Pill.stories.tsx +867 -21
  123. package/src/components/progress/Progress.stories.tsx +625 -58
  124. package/src/components/separator/Separator.stories.tsx +730 -8
  125. package/src/components/separator/separator.scss +12 -3
  126. package/src/components/tag/Tag.stories.tsx +755 -53
  127. package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
  128. package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
  129. package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
  130. package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
  131. package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
  132. package/.claude/figma-assessment-7154-58899.md +0 -404
  133. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
  134. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
  135. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
  136. package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
  137. package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
  138. /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
  139. /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
  140. /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
  141. /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
  142. /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
@@ -1,24 +1,186 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
3
+ import { Icon } from '../icon/Icon';
2
4
  import { Badge } from './Badge';
3
- const colours = [
4
- 'purple',
5
- 'salmon',
6
- 'teal',
7
- 'yellow',
8
- 'green',
9
- 'orange',
10
- 'blue',
11
- ];
5
+ // ---------------------------------------------------------------------------
6
+ // Docs page content
7
+ // ---------------------------------------------------------------------------
8
+ const DESCRIPTION_INTRO = [
9
+ 'Badge is a compact numeric count indicator that sits alongside icons, labels, or controls to communicate',
10
+ 'quantities at a glance — unread messages, behaviour event totals, pending form responses.',
11
+ ].join('\n');
12
+ const USAGE_GUIDANCE = [
13
+ '### When to use',
14
+ '',
15
+ '- **Unread notification counts** — messages, alerts, or action items awaiting review',
16
+ '- **Status totals** — number of students flagged, responses submitted, events logged',
17
+ '- **Count-adjacent icons** — a bell icon with a Badge communicates "3 unread notifications"',
18
+ '- **Tab bar counts** — urgent items in a specific tab need a visual quantity signal',
19
+ '',
20
+ '---',
21
+ '',
22
+ '### When NOT to use',
23
+ '',
24
+ 'Badge is for **numbers only**. It is not a label, filter toggle, or status dot.',
25
+ '',
26
+ '| Component | Use for |',
27
+ '|---|---|',
28
+ '| `Badge` | Numeric counts: `3`, `12`, `99+` |',
29
+ '| [`Tag`](?path=/docs/components-tag--docs) | Category labels: `English`, `Year 9`, `GCSE` |',
30
+ '| [`Pill`](?path=/docs/components-pill--docs) | Filter toggles: `Active`, `Archived`, `Flagged` |',
31
+ '| [`Dot`](?path=/docs/components-dot--docs) | Status indicator (no number): presence, colour-coded state |',
32
+ '',
33
+ '```tsx',
34
+ '// BAD — numbers require different emphasis and spacing than category labels',
35
+ '<Badge colour="purple">English</Badge>',
36
+ '',
37
+ '// GOOD — use Tag for category labels',
38
+ '<Tag color="purple">English</Tag>',
39
+ '```',
40
+ '',
41
+ '---',
42
+ '',
43
+ '### Design guidance',
44
+ '',
45
+ '- **Three sizes** — `sm` (`--badge-small-size`, 1.125rem / 18px), `md` (`--badge-medium-size`,',
46
+ ' 1.25rem / 20px, default), `lg` (`--badge-large-size`, 1.5rem / 24px). All three use',
47
+ ' `--border-radius-round` (a fully circular radius) so single-digit badges appear as perfect circles',
48
+ ' and multi-digit badges expand into pills.',
49
+ '- **Font weight** — `--badge-font-weight` resolves to `--font-weight-semi-bold` (600) across all sizes.',
50
+ '- **Default colour** — no `colour` prop renders with `--color-grey-800` background and',
51
+ ' `--color-mono-white` text (charcoal). All seven extended-palette colours are also available.',
52
+ '- **Colour contrast pairings** — white text on salmon, green, and the default charcoal.',
53
+ ' Dark text (`--color-extended-colours-{colour}-800`) on purple, teal, yellow, orange, and blue.',
54
+ '- **`99+` convention** — when a count exceeds 99, display `"99+"` rather than the real number to',
55
+ ' keep the badge compact and avoid layout overflow.',
56
+ ].join('\n');
57
+ const DEVELOPER_NOTES = [
58
+ '### Accessibility',
59
+ '',
60
+ '- **Keyboard** — Badge is a purely presentational `<span>`. It has no interactive behaviour and is',
61
+ ' not focusable.',
62
+ '- **Screen readers** — by default, the badge content is read as inline text. If the badge sits',
63
+ ' next to an icon or label that already provides context, the count alone may be confusing',
64
+ ' ("3" without context means nothing).',
65
+ '- **`a11yLabel` pattern** — pass a full human-readable string like `"3 unread messages"`. The',
66
+ ' component sets `aria-label` on the wrapper `<span>` and wraps `children` in an',
67
+ ' `aria-hidden="true"` span to suppress the duplicate numeric announcement. The screen reader',
68
+ ' then announces the full label instead of just the number.',
69
+ '- **Do not rely on colour alone** — the numeric value must be visible. Never convey meaning',
70
+ ' exclusively through badge colour without a label or supporting text.',
71
+ '',
72
+ '---',
73
+ '',
74
+ '### TypeScript types',
75
+ '',
76
+ '```ts',
77
+ "import { Badge } from '@arbor-education/design-system.components';",
78
+ '',
79
+ 'function MyBadge(props: Badge.Props) { ... }',
80
+ '```',
81
+ '',
82
+ '| Type | Description |',
83
+ '|---|---|',
84
+ '| `Badge.Props` | Full props interface |',
85
+ "| `Badge.Size` | `'sm' \\| 'md' \\| 'lg'` |",
86
+ "| `Badge.Colour` | `'purple' \\| 'salmon' \\| 'teal' \\| 'yellow' \\| 'green' \\| 'orange' \\| 'blue'` (same scale as `Dot.Colour`) |",
87
+ ].join('\n');
88
+ const RELATED_COMPONENTS = [
89
+ '## Related components',
90
+ '',
91
+ '[Tag](?path=/docs/components-tag--docs) · [Pill](?path=/docs/components-pill--docs) · [Dot](?path=/docs/components-dot--docs) · [Icon](?path=/docs/components-icon--docs)',
92
+ ].join('\n');
93
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
94
+ function BadgeDocsPage() {
95
+ 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 })] }));
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Data
99
+ // ---------------------------------------------------------------------------
100
+ const colours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'];
101
+ // ---------------------------------------------------------------------------
102
+ // Meta
103
+ // ---------------------------------------------------------------------------
12
104
  const meta = {
13
- tags: ['autodocs'],
14
105
  title: 'Components/Badge',
15
106
  component: Badge,
107
+ parameters: {
108
+ layout: 'centered',
109
+ docs: {
110
+ page: BadgeDocsPage,
111
+ },
112
+ },
113
+ tags: ['autodocs'],
16
114
  argTypes: {
17
- size: { control: 'select', options: ['sm', 'md', 'lg'] },
18
- colour: { control: 'select', options: [undefined, ...colours] },
115
+ children: {
116
+ control: 'text',
117
+ description: [
118
+ 'The count to display inside the badge. Should always be a number or short numeric string.',
119
+ 'For counts above 99, use `"99+"` to keep the badge compact.',
120
+ 'Badge is for numbers only — do not pass category labels or text strings.',
121
+ ].join(' '),
122
+ table: {
123
+ type: { summary: 'React.ReactNode' },
124
+ },
125
+ },
126
+ size: {
127
+ control: 'select',
128
+ options: ['sm', 'md', 'lg'],
129
+ description: [
130
+ 'Controls the height, min-width, padding, font-size, and border-radius of the badge as a single unit.',
131
+ '`sm` — 1.125rem / 18px via `--badge-small-size`.',
132
+ '`md` — 1.25rem / 20px via `--badge-medium-size` (default).',
133
+ '`lg` — 1.5rem / 24px via `--badge-large-size`.',
134
+ 'All sizes use `--border-radius-round` so single-digit badges appear as circles and multi-digit badges expand into pills.',
135
+ ].join(' '),
136
+ table: {
137
+ type: { summary: "'sm' | 'md' | 'lg'" },
138
+ defaultValue: { summary: "'md'" },
139
+ },
140
+ },
141
+ colour: {
142
+ control: 'select',
143
+ options: [undefined, ...colours],
144
+ description: [
145
+ 'Extended-palette colour applied to the badge background.',
146
+ 'When omitted (default), the badge renders with `--color-grey-800` (charcoal) background and white text.',
147
+ 'White text pairings: salmon, green, and the default charcoal.',
148
+ 'Dark text pairings (`--color-extended-colours-{colour}-800`): purple, teal, yellow, orange, blue.',
149
+ 'Do not rely on colour alone to communicate meaning — the numeric value must always be visible.',
150
+ ].join(' '),
151
+ table: {
152
+ type: { summary: "'purple' | 'salmon' | 'teal' | 'yellow' | 'green' | 'orange' | 'blue' | undefined" },
153
+ defaultValue: { summary: 'undefined (charcoal)' },
154
+ },
155
+ },
156
+ a11yLabel: {
157
+ control: 'text',
158
+ description: [
159
+ 'Accessible label for the badge. Sets `aria-label` on the wrapper `<span>` and wraps',
160
+ '`children` in an `aria-hidden="true"` span to suppress the duplicate numeric announcement.',
161
+ 'Use when the badge sits next to an icon or element that provides no visible label — e.g.',
162
+ '`a11yLabel="5 unread messages"` next to a bell icon.',
163
+ 'When omitted, the numeric content is announced as-is by screen readers.',
164
+ ].join(' '),
165
+ table: {
166
+ type: { summary: 'string' },
167
+ defaultValue: { summary: 'undefined' },
168
+ },
169
+ },
170
+ className: {
171
+ control: false,
172
+ description: 'Additional CSS class names appended to the badge element. Use sparingly — prefer size and colour props.',
173
+ table: {
174
+ type: { summary: 'string' },
175
+ defaultValue: { summary: 'undefined' },
176
+ },
177
+ },
19
178
  },
20
179
  };
21
180
  export default meta;
181
+ // ---------------------------------------------------------------------------
182
+ // Helper: attach a per-story description to docs
183
+ // ---------------------------------------------------------------------------
22
184
  const withDescription = (story, description) => ({
23
185
  ...story,
24
186
  parameters: {
@@ -31,21 +193,458 @@ const withDescription = (story, description) => ({
31
193
  },
32
194
  },
33
195
  });
34
- export const Count = withDescription({
196
+ // ---------------------------------------------------------------------------
197
+ // Template components for composition stories
198
+ // (Named components avoid react-hooks lint issues — the react-hooks ESLint
199
+ // plugin is NOT configured in this project, so do NOT add eslint-disable.)
200
+ // ---------------------------------------------------------------------------
201
+ const WithIconTemplate = () => (_jsx("div", { style: {
202
+ padding: 'var(--spacing-xlarge)',
203
+ display: 'flex',
204
+ alignItems: 'center',
205
+ gap: 'var(--spacing-large)',
206
+ }, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }, children: [_jsxs("div", { style: { position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }, children: [_jsx(Icon, { name: "mail", size: 16, screenReaderText: "Notifications" }), _jsx(Badge, { children: "3" })] }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Notifications" })] }) }));
207
+ const InlineWithTextTemplate = () => (_jsxs("div", { style: {
208
+ padding: 'var(--spacing-xlarge)',
209
+ display: 'flex',
210
+ flexDirection: 'column',
211
+ gap: 'var(--spacing-large)',
212
+ }, children: [_jsxs("div", { style: {
213
+ display: 'flex',
214
+ alignItems: 'center',
215
+ gap: 'var(--spacing-large)',
216
+ }, children: [_jsx("span", { className: "ds-text", style: { color: 'var(--color-grey-600)' }, children: "Behaviour events" }), _jsx(Badge, { children: "14" })] }), _jsxs("div", { style: {
217
+ display: 'flex',
218
+ alignItems: 'center',
219
+ gap: 'var(--spacing-large)',
220
+ }, children: [_jsx("span", { className: "ds-text", style: { color: 'var(--color-grey-600)' }, children: "Pending exclusions" }), _jsx(Badge, { colour: "salmon", children: "3" })] }), _jsxs("div", { style: {
221
+ display: 'flex',
222
+ alignItems: 'center',
223
+ gap: 'var(--spacing-large)',
224
+ }, children: [_jsx("span", { className: "ds-text", style: { color: 'var(--color-grey-600)' }, children: "Attendance alerts" }), _jsx(Badge, { colour: "orange", children: "27" })] })] }));
225
+ const AllSizesTemplate = () => (_jsxs("div", { style: {
226
+ padding: 'var(--spacing-xlarge)',
227
+ display: 'flex',
228
+ gap: 'var(--spacing-xlarge)',
229
+ alignItems: 'flex-end',
230
+ }, children: [_jsxs("div", { style: {
231
+ display: 'flex',
232
+ flexDirection: 'column',
233
+ alignItems: 'center',
234
+ gap: 'var(--spacing-large)',
235
+ }, children: [_jsx(Badge, { size: "sm", children: "7" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "sm \u2014 1.125rem" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "--badge-small-size" })] }), _jsxs("div", { style: {
236
+ display: 'flex',
237
+ flexDirection: 'column',
238
+ alignItems: 'center',
239
+ gap: 'var(--spacing-large)',
240
+ }, children: [_jsx(Badge, { size: "md", children: "7" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "md \u2014 1.25rem" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "--badge-medium-size" })] }), _jsxs("div", { style: {
241
+ display: 'flex',
242
+ flexDirection: 'column',
243
+ alignItems: 'center',
244
+ gap: 'var(--spacing-large)',
245
+ }, children: [_jsx(Badge, { size: "lg", children: "7" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "lg \u2014 1.5rem" }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }, children: "--badge-large-size" })] })] }));
246
+ const AllColoursTemplate = () => (_jsxs("div", { style: {
247
+ padding: 'var(--spacing-xlarge)',
248
+ display: 'flex',
249
+ flexDirection: 'column',
250
+ gap: 'var(--spacing-large)',
251
+ }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Default (charcoal) + all 7 extended-palette colours" }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }, children: [_jsx(Badge, { children: "9" }), _jsx("span", { className: "ds-text", style: { fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }, children: "default" })] }), colours.map(c => (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }, children: [_jsx(Badge, { colour: c, children: "9" }), _jsx("span", { className: "ds-text", style: { fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }, children: c })] }, c)))] })] }));
252
+ const AllVariantsTemplate = () => (_jsx("div", { style: {
253
+ padding: 'var(--spacing-xlarge)',
254
+ display: 'flex',
255
+ flexDirection: 'column',
256
+ gap: 'var(--spacing-xlarge)',
257
+ }, children: ['sm', 'md', 'lg'].map(size => (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsxs("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: ["Size:", ' ', size] }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }, children: [_jsx(Badge, { size: size, children: "9" }), _jsx("span", { className: "ds-text", style: { fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }, children: "default" })] }), colours.map(c => (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }, children: [_jsx(Badge, { size: size, colour: c, children: "9" }), _jsx("span", { className: "ds-text", style: { fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }, children: c })] }, c)))] })] }, size))) }));
258
+ const A11yWithLabelTemplate = () => (_jsxs("div", { style: {
259
+ padding: 'var(--spacing-xlarge)',
260
+ display: 'flex',
261
+ flexDirection: 'column',
262
+ gap: 'var(--spacing-xlarge)',
263
+ }, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-semantic-destructive-600)' }, children: "Without a11yLabel \u2014 screen reader announces \"5\" (no context)" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }, children: [_jsx(Icon, { name: "mail", size: 16, screenReaderText: "Notifications" }), _jsx(Badge, { children: "5" })] }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }, children: "Screen reader: \"mail Notifications, 5\"" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-semantic-success-600)' }, children: "With a11yLabel \u2014 screen reader announces the full meaningful label" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }, children: [_jsx(Icon, { name: "mail", size: 16 }), _jsx(Badge, { a11yLabel: "5 unread messages", children: "5" })] }), _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }, children: "Screen reader: \"5 unread messages\"" })] })] }));
264
+ // ---------------------------------------------------------------------------
265
+ // Stories
266
+ // ---------------------------------------------------------------------------
267
+ export const Default = withDescription({
35
268
  args: {
36
269
  children: '3',
270
+ size: 'md',
271
+ colour: undefined,
37
272
  },
38
- }, 'Shows the default numeric Badge used for counts and status totals.');
39
- export const WithA11yLabel = withDescription({
40
- args: {
41
- children: '3',
42
- a11yLabel: '3 selected items',
43
- },
44
- }, 'Adds an accessible label when the visual content alone is not descriptive enough.');
45
- export const Sizes = withDescription({
46
- render: () => (_jsxs("div", { style: { display: 'flex', gap: '1rem', alignItems: 'center' }, children: [_jsx(Badge, { size: "sm", children: "1" }), _jsx(Badge, { size: "md", children: "2" }), _jsx(Badge, { size: "lg", children: "3" })] })),
47
- }, 'Compares the small, medium, and large Badge size variants.');
48
- export const Colours = withDescription({
49
- render: () => (_jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }, children: colours.map(c => (_jsx(Badge, { colour: c, size: "md", children: c.slice(0, 1).toUpperCase() }, c))) })),
50
- }, 'Displays each available Badge colour variant side by side.');
273
+ render: args => _jsx(Badge, { ...args }),
274
+ }, 'The interactive canvas story — every prop is wired to the **Controls** panel. Use the controls to explore sizes, colours, and the a11yLabel pattern. Tip: try `size="sm"` for navigation contexts or `colour="salmon"` for urgent counts.');
275
+ export const SingleDigit = withDescription({
276
+ args: { children: '3' },
277
+ parameters: {
278
+ docs: {
279
+ source: {
280
+ language: 'tsx',
281
+ code: `
282
+ import { Badge } from '@arbor-education/design-system.components';
283
+
284
+ function BadgeSingleDigitExample() {
285
+ return <Badge>3</Badge>;
286
+ }
287
+
288
+ export default BadgeSingleDigitExample;
289
+ `.trim(),
290
+ },
291
+ },
292
+ },
293
+ }, 'A single-digit count — the canonical badge case. Because `min-width` equals `height` via `--badge-medium-size`, single-digit badges render as perfect circles. This circular geometry is intentional: it signals "count" rather than "label" at a glance.');
294
+ export const DoubleDigit = withDescription({
295
+ args: { children: '12' },
296
+ parameters: {
297
+ docs: {
298
+ source: {
299
+ language: 'tsx',
300
+ code: `
301
+ import { Badge } from '@arbor-education/design-system.components';
302
+
303
+ function BadgeDoubleDigitExample() {
304
+ return <Badge>12</Badge>;
305
+ }
306
+
307
+ export default BadgeDoubleDigitExample;
308
+ `.trim(),
309
+ },
310
+ },
311
+ },
312
+ }, 'A double-digit count, showing how the badge expands into a pill when content exceeds the minimum width. All three sizes use `--border-radius-round` so the pill shape is consistent regardless of size.');
313
+ export const Overflow = withDescription({
314
+ args: { children: '99+' },
315
+ parameters: {
316
+ docs: {
317
+ source: {
318
+ language: 'tsx',
319
+ code: `
320
+ import { Badge } from '@arbor-education/design-system.components';
321
+
322
+ function BadgeOverflowExample() {
323
+ return <Badge>99+</Badge>;
324
+ }
325
+
326
+ export default BadgeOverflowExample;
327
+ `.trim(),
328
+ },
329
+ },
330
+ },
331
+ }, 'The standard truncation convention for counts above 99. Always display `"99+"` rather than the real number to prevent layout overflow and keep the badge visually compact. Never render a three-digit count like `"247"` — it breaks the badge geometry.');
332
+ export const Zero = withDescription({
333
+ args: { children: '0' },
334
+ parameters: {
335
+ docs: {
336
+ source: {
337
+ language: 'tsx',
338
+ code: `
339
+ import { Badge } from '@arbor-education/design-system.components';
340
+
341
+ function BadgeZeroExample() {
342
+ return <Badge>0</Badge>;
343
+ }
344
+
345
+ export default BadgeZeroExample;
346
+ `.trim(),
347
+ },
348
+ },
349
+ },
350
+ }, 'A zero count is a valid, intentional state — it signals that all notifications have been read or all items have been resolved. Do not hide the badge when the count reaches zero unless the design explicitly calls for it; the badge disappearing can be confusing.');
351
+ export const WithIcon = withDescription({
352
+ render: WithIconTemplate,
353
+ parameters: {
354
+ docs: {
355
+ source: {
356
+ language: 'tsx',
357
+ code: `
358
+ import { Badge, Icon } from '@arbor-education/design-system.components';
359
+
360
+ function WithIconTemplate() {
361
+ return (
362
+ <div
363
+ style={{
364
+ padding: 'var(--spacing-xlarge)',
365
+ display: 'flex',
366
+ alignItems: 'center',
367
+ gap: 'var(--spacing-large)',
368
+ }}
369
+ >
370
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
371
+ <div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }}>
372
+ <Icon name="mail" size={16} screenReaderText="Notifications" />
373
+ <Badge>3</Badge>
374
+ </div>
375
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
376
+ Notifications
377
+ </p>
378
+ </div>
379
+ </div>
380
+ );
381
+ }
382
+
383
+ export default WithIconTemplate;
384
+ `.trim(),
385
+ },
386
+ },
387
+ },
388
+ }, 'The most common real-world usage pattern: a mail icon paired with a count badge and a "Notifications" label. The `Icon` component uses `screenReaderText="Notifications"` for its own label. The badge here has no `a11yLabel` — the adjacent visible label "Notifications" provides the context. For icon-only contexts with no visible label, always add `a11yLabel`.');
389
+ export const InlineWithText = withDescription({
390
+ render: InlineWithTextTemplate,
391
+ parameters: {
392
+ docs: {
393
+ source: {
394
+ language: 'tsx',
395
+ code: `
396
+ import { Badge } from '@arbor-education/design-system.components';
397
+
398
+ function InlineWithTextTemplate() {
399
+ return (
400
+ <div
401
+ style={{
402
+ padding: 'var(--spacing-xlarge)',
403
+ display: 'flex',
404
+ flexDirection: 'column',
405
+ gap: 'var(--spacing-large)',
406
+ }}
407
+ >
408
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
409
+ <span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
410
+ Behaviour events
411
+ </span>
412
+ <Badge>14</Badge>
413
+ </div>
414
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
415
+ <span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
416
+ Pending exclusions
417
+ </span>
418
+ <Badge colour="salmon">3</Badge>
419
+ </div>
420
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
421
+ <span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
422
+ Attendance alerts
423
+ </span>
424
+ <Badge colour="orange">27</Badge>
425
+ </div>
426
+ </div>
427
+ );
428
+ }
429
+
430
+ export default InlineWithTextTemplate;
431
+ `.trim(),
432
+ },
433
+ },
434
+ },
435
+ }, 'Badge next to a text label in a statistics row. The badge uses `flex-shrink: 0` via `ds-badge` so it never squishes inside a flex container. All three rows use design token colours — salmon for urgent (pending exclusions), orange for elevated (attendance alerts), and the default charcoal for neutral counts.');
436
+ export const AllSizes = withDescription({
437
+ render: AllSizesTemplate,
438
+ parameters: {
439
+ docs: {
440
+ source: {
441
+ language: 'tsx',
442
+ code: `
443
+ import { Badge } from '@arbor-education/design-system.components';
444
+
445
+ function AllSizesTemplate() {
446
+ return (
447
+ <div
448
+ style={{
449
+ padding: 'var(--spacing-xlarge)',
450
+ display: 'flex',
451
+ gap: 'var(--spacing-xlarge)',
452
+ alignItems: 'flex-end',
453
+ }}
454
+ >
455
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
456
+ <Badge size="sm">7</Badge>
457
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
458
+ sm — 1.125rem
459
+ </p>
460
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
461
+ --badge-small-size
462
+ </p>
463
+ </div>
464
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
465
+ <Badge size="md">7</Badge>
466
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
467
+ md — 1.25rem
468
+ </p>
469
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
470
+ --badge-medium-size
471
+ </p>
472
+ </div>
473
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
474
+ <Badge size="lg">7</Badge>
475
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
476
+ lg — 1.5rem
477
+ </p>
478
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
479
+ --badge-large-size
480
+ </p>
481
+ </div>
482
+ </div>
483
+ );
484
+ }
485
+
486
+ export default AllSizesTemplate;
487
+ `.trim(),
488
+ },
489
+ },
490
+ },
491
+ }, 'All three sizes side by side with their token names and resolved rem values. `sm` is right for navigation items and tab bars. `md` (the default) suits most standalone badge contexts. `lg` is appropriate when badge counts need to be more prominent — for example in a dashboard summary card. Never mix sizes arbitrarily — pick the size that matches the surrounding typographic scale.');
492
+ export const AllColours = withDescription({
493
+ render: AllColoursTemplate,
494
+ parameters: {
495
+ docs: {
496
+ source: {
497
+ language: 'tsx',
498
+ code: `
499
+ import { Badge } from '@arbor-education/design-system.components';
500
+
501
+ const colours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'] as const;
502
+
503
+ function AllColoursTemplate() {
504
+ return (
505
+ <div
506
+ style={{
507
+ padding: 'var(--spacing-xlarge)',
508
+ display: 'flex',
509
+ flexDirection: 'column',
510
+ gap: 'var(--spacing-large)',
511
+ }}
512
+ >
513
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
514
+ Default (charcoal) + all 7 extended-palette colours
515
+ </p>
516
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
517
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
518
+ <Badge>9</Badge>
519
+ <span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
520
+ </div>
521
+ {colours.map(c => (
522
+ <div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
523
+ <Badge colour={c}>9</Badge>
524
+ <span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
525
+ </div>
526
+ ))}
527
+ </div>
528
+ </div>
529
+ );
530
+ }
531
+
532
+ export default AllColoursTemplate;
533
+ `.trim(),
534
+ },
535
+ },
536
+ },
537
+ }, 'The default charcoal (no `colour` prop) first, followed by all seven extended-palette colours. White text renders on salmon and green; dark text (`--color-extended-colours-{colour}-800`) renders on purple, teal, yellow, orange, and blue for WCAG contrast compliance. Remember: colour alone must not be the only signal — the numeric value is always the primary communicator.');
538
+ export const AllVariants = withDescription({
539
+ render: AllVariantsTemplate,
540
+ parameters: {
541
+ docs: {
542
+ source: {
543
+ language: 'tsx',
544
+ code: `
545
+ import { Badge } from '@arbor-education/design-system.components';
546
+
547
+ const colours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'] as const;
548
+
549
+ function AllVariantsTemplate() {
550
+ return (
551
+ <div
552
+ style={{
553
+ padding: 'var(--spacing-xlarge)',
554
+ display: 'flex',
555
+ flexDirection: 'column',
556
+ gap: 'var(--spacing-xlarge)',
557
+ }}
558
+ >
559
+ {(['sm', 'md', 'lg'] as const).map(size => (
560
+ <div key={size} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
561
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
562
+ Size: {size}
563
+ </p>
564
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
565
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
566
+ <Badge size={size}>9</Badge>
567
+ <span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
568
+ </div>
569
+ {colours.map(c => (
570
+ <div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
571
+ <Badge size={size} colour={c}>9</Badge>
572
+ <span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
573
+ </div>
574
+ ))}
575
+ </div>
576
+ </div>
577
+ ))}
578
+ </div>
579
+ );
580
+ }
581
+ export default AllVariantsTemplate;
582
+ `.trim(),
583
+ },
584
+ },
585
+ },
586
+ }, 'Full 3 × 8 grid: all three sizes crossed with all eight colour states (default charcoal + 7 palette colours). Use this story as a complete visual reference for the Badge component. Each cell uses the same count (`"9"`) to make size and colour comparisons accurate.');
587
+ export const A11yWithLabel = withDescription({
588
+ render: A11yWithLabelTemplate,
589
+ parameters: {
590
+ docs: {
591
+ source: {
592
+ language: 'tsx',
593
+ code: `
594
+ import { Badge, Icon } from '@arbor-education/design-system.components';
595
+
596
+ function A11yWithLabelTemplate() {
597
+ return (
598
+ <div
599
+ style={{
600
+ padding: 'var(--spacing-xlarge)',
601
+ display: 'flex',
602
+ flexDirection: 'column',
603
+ gap: 'var(--spacing-xlarge)',
604
+ }}
605
+ >
606
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
607
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
608
+ Without a11yLabel — screen reader announces "5" (no context)
609
+ </p>
610
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
611
+ <Icon name="mail" size={16} screenReaderText="Notifications" />
612
+ <Badge>5</Badge>
613
+ </div>
614
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
615
+ Screen reader: "mail Notifications, 5"
616
+ </p>
617
+ </div>
618
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
619
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-success-600)' }}>
620
+ With a11yLabel — screen reader announces the full meaningful label
621
+ </p>
622
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
623
+ <Icon name="mail" size={16} />
624
+ <Badge a11yLabel="5 unread messages">5</Badge>
625
+ </div>
626
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
627
+ Screen reader: "5 unread messages"
628
+ </p>
629
+ </div>
630
+ </div>
631
+ );
632
+ }
633
+ export default A11yWithLabelTemplate;
634
+ `.trim(),
635
+ },
636
+ },
637
+ },
638
+ }, [
639
+ 'Accessibility contrast: top row shows a badge **without** `a11yLabel` — a screen reader',
640
+ 'moving through the icon and badge would announce the icon text and then the bare number `"5"`,',
641
+ 'providing no context about what those 5 items are.',
642
+ '',
643
+ 'The bottom row adds `a11yLabel="5 unread messages"`. The component sets `aria-label` on the',
644
+ 'wrapper `<span>` and wraps `children` in an `aria-hidden="true"` span. The screen reader now',
645
+ 'announces `"5 unread messages"` as a single coherent label — no duplicate number, full context.',
646
+ '',
647
+ 'Use `a11yLabel` whenever the badge sits next to an icon-only element or in a context where',
648
+ 'the number alone would be ambiguous to a screen reader user.',
649
+ ].join(' '));
51
650
  //# sourceMappingURL=Badge.stories.js.map