@arbor-education/design-system.components 0.12.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.
- package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
- package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
- package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
- package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
- package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
- package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
- package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
- package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
- package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
- package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
- package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
- package/.gather/gather.yaml +9 -0
- package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
- package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
- package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
- package/.gather/skills/analyze-design/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/README.md +5 -0
- package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
- package/.gather/skills/create-page/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
- package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
- package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
- package/.gather/skills/map-legacy/meta.md +4 -0
- package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
- package/.gather/skills/migrate-page/meta.md +4 -0
- package/.gather/skills/write-stories/README.md +157 -0
- package/.gather/skills/write-stories/SKILL.md +841 -0
- package/.gather/skills/write-stories/meta.md +4 -0
- package/.ralph/storybook-upgrade/knowledge.md +308 -0
- package/.ralph/storybook-upgrade/prd.json +777 -0
- package/.ralph/storybook-upgrade/progress.md +342 -0
- package/.storybook/DocsTemplate.tsx +122 -0
- package/.storybook/preview.ts +40 -0
- package/.stylelintignore +2 -0
- package/CHANGELOG.md +12 -0
- package/{.claude/component-library.md → component-library.md} +27 -10
- package/dist/components/badge/Badge.stories.d.ts +85 -6
- package/dist/components/badge/Badge.stories.d.ts.map +1 -1
- package/dist/components/badge/Badge.stories.js +626 -27
- package/dist/components/badge/Badge.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts +129 -63
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +855 -39
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/button/Button.stories.d.ts +148 -8
- package/dist/components/button/Button.stories.d.ts.map +1 -1
- package/dist/components/button/Button.stories.js +1089 -80
- package/dist/components/button/Button.stories.js.map +1 -1
- package/dist/components/datePicker/DatePicker.d.ts +1 -0
- package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.js +2 -2
- package/dist/components/datePicker/DatePicker.js.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.d.ts +1 -0
- package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
- package/dist/components/dot/Dot.stories.d.ts +46 -11
- package/dist/components/dot/Dot.stories.d.ts.map +1 -1
- package/dist/components/dot/Dot.stories.js +504 -15
- package/dist/components/dot/Dot.stories.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +769 -17
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +95 -35
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +1174 -69
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
- package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
- package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
- package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
- package/dist/components/heading/Heading.stories.d.ts +449 -50
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +536 -60
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/icon/Icon.stories.d.ts +81 -10
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +979 -8
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/pill/Pill.stories.d.ts +71 -19
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +573 -14
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +75 -298
- package/dist/components/progress/Progress.stories.d.ts.map +1 -1
- package/dist/components/progress/Progress.stories.js +449 -52
- package/dist/components/progress/Progress.stories.js.map +1 -1
- package/dist/components/separator/Separator.stories.d.ts +58 -5
- package/dist/components/separator/Separator.stories.d.ts.map +1 -1
- package/dist/components/separator/Separator.stories.js +443 -4
- package/dist/components/separator/Separator.stories.js.map +1 -1
- package/dist/components/table/Table.d.ts +7 -0
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +9 -0
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +1 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +87 -0
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +49 -1
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/cellEditors/DateCellEditor.d.ts +3 -0
- package/dist/components/table/cellEditors/DateCellEditor.d.ts.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.js +13 -0
- package/dist/components/table/cellEditors/DateCellEditor.js.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.test.d.ts +2 -0
- package/dist/components/table/cellEditors/DateCellEditor.test.d.ts.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.test.js +81 -0
- package/dist/components/table/cellEditors/DateCellEditor.test.js.map +1 -0
- package/dist/components/tag/Tag.stories.d.ts +116 -5
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +581 -28
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/index.css +8 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/eslint.config.mts +5 -1
- package/package.json +3 -3
- package/src/components/badge/Badge.stories.tsx +869 -42
- package/src/components/banner/Banner.stories.tsx +1081 -63
- package/src/components/button/Button.stories.tsx +1394 -99
- package/src/components/datePicker/DatePicker.tsx +3 -0
- package/src/components/dot/Dot.stories.tsx +723 -32
- package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
- package/src/components/formField/FormField.stories.tsx +1522 -105
- package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
- package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
- package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
- package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
- package/src/components/heading/Heading.stories.tsx +752 -120
- package/src/components/icon/Icon.stories.tsx +1446 -12
- package/src/components/pill/Pill.stories.tsx +867 -21
- package/src/components/progress/Progress.stories.tsx +625 -58
- package/src/components/separator/Separator.stories.tsx +730 -8
- package/src/components/separator/separator.scss +12 -3
- package/src/components/table/Table.stories.tsx +102 -0
- package/src/components/table/Table.test.tsx +82 -3
- package/src/components/table/Table.tsx +9 -0
- package/src/components/table/cellEditors/DateCellEditor.test.tsx +109 -0
- package/src/components/table/cellEditors/DateCellEditor.tsx +27 -0
- package/src/components/tag/Tag.stories.tsx +755 -53
- package/src/index.ts +0 -5
- package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
- package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
- package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
- package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
- package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
- package/.claude/figma-assessment-7154-58899.md +0 -404
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
- package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
- package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
- /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
- /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
- /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
|
@@ -1,30 +1,229 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
Controls,
|
|
4
|
+
Heading as DocHeading,
|
|
5
|
+
Markdown,
|
|
6
|
+
Primary as DocPrimary,
|
|
7
|
+
Stories,
|
|
8
|
+
Subtitle,
|
|
9
|
+
Title,
|
|
10
|
+
} from '@storybook/addon-docs/blocks';
|
|
11
|
+
import { Icon } from 'Components/icon/Icon';
|
|
2
12
|
import { Badge, type BadgeColour } from './Badge';
|
|
3
13
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Docs page content
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const DESCRIPTION_INTRO = [
|
|
19
|
+
'Badge is a compact numeric count indicator that sits alongside icons, labels, or controls to communicate',
|
|
20
|
+
'quantities at a glance — unread messages, behaviour event totals, pending form responses.',
|
|
21
|
+
].join('\n');
|
|
22
|
+
|
|
23
|
+
const USAGE_GUIDANCE = [
|
|
24
|
+
'### When to use',
|
|
25
|
+
'',
|
|
26
|
+
'- **Unread notification counts** — messages, alerts, or action items awaiting review',
|
|
27
|
+
'- **Status totals** — number of students flagged, responses submitted, events logged',
|
|
28
|
+
'- **Count-adjacent icons** — a bell icon with a Badge communicates "3 unread notifications"',
|
|
29
|
+
'- **Tab bar counts** — urgent items in a specific tab need a visual quantity signal',
|
|
30
|
+
'',
|
|
31
|
+
'---',
|
|
32
|
+
'',
|
|
33
|
+
'### When NOT to use',
|
|
34
|
+
'',
|
|
35
|
+
'Badge is for **numbers only**. It is not a label, filter toggle, or status dot.',
|
|
36
|
+
'',
|
|
37
|
+
'| Component | Use for |',
|
|
38
|
+
'|---|---|',
|
|
39
|
+
'| `Badge` | Numeric counts: `3`, `12`, `99+` |',
|
|
40
|
+
'| [`Tag`](?path=/docs/components-tag--docs) | Category labels: `English`, `Year 9`, `GCSE` |',
|
|
41
|
+
'| [`Pill`](?path=/docs/components-pill--docs) | Filter toggles: `Active`, `Archived`, `Flagged` |',
|
|
42
|
+
'| [`Dot`](?path=/docs/components-dot--docs) | Status indicator (no number): presence, colour-coded state |',
|
|
43
|
+
'',
|
|
44
|
+
'```tsx',
|
|
45
|
+
'// BAD — numbers require different emphasis and spacing than category labels',
|
|
46
|
+
'<Badge colour="purple">English</Badge>',
|
|
47
|
+
'',
|
|
48
|
+
'// GOOD — use Tag for category labels',
|
|
49
|
+
'<Tag color="purple">English</Tag>',
|
|
50
|
+
'```',
|
|
51
|
+
'',
|
|
52
|
+
'---',
|
|
53
|
+
'',
|
|
54
|
+
'### Design guidance',
|
|
55
|
+
'',
|
|
56
|
+
'- **Three sizes** — `sm` (`--badge-small-size`, 1.125rem / 18px), `md` (`--badge-medium-size`,',
|
|
57
|
+
' 1.25rem / 20px, default), `lg` (`--badge-large-size`, 1.5rem / 24px). All three use',
|
|
58
|
+
' `--border-radius-round` (a fully circular radius) so single-digit badges appear as perfect circles',
|
|
59
|
+
' and multi-digit badges expand into pills.',
|
|
60
|
+
'- **Font weight** — `--badge-font-weight` resolves to `--font-weight-semi-bold` (600) across all sizes.',
|
|
61
|
+
'- **Default colour** — no `colour` prop renders with `--color-grey-800` background and',
|
|
62
|
+
' `--color-mono-white` text (charcoal). All seven extended-palette colours are also available.',
|
|
63
|
+
'- **Colour contrast pairings** — white text on salmon, green, and the default charcoal.',
|
|
64
|
+
' Dark text (`--color-extended-colours-{colour}-800`) on purple, teal, yellow, orange, and blue.',
|
|
65
|
+
'- **`99+` convention** — when a count exceeds 99, display `"99+"` rather than the real number to',
|
|
66
|
+
' keep the badge compact and avoid layout overflow.',
|
|
67
|
+
].join('\n');
|
|
68
|
+
|
|
69
|
+
const DEVELOPER_NOTES = [
|
|
70
|
+
'### Accessibility',
|
|
71
|
+
'',
|
|
72
|
+
'- **Keyboard** — Badge is a purely presentational `<span>`. It has no interactive behaviour and is',
|
|
73
|
+
' not focusable.',
|
|
74
|
+
'- **Screen readers** — by default, the badge content is read as inline text. If the badge sits',
|
|
75
|
+
' next to an icon or label that already provides context, the count alone may be confusing',
|
|
76
|
+
' ("3" without context means nothing).',
|
|
77
|
+
'- **`a11yLabel` pattern** — pass a full human-readable string like `"3 unread messages"`. The',
|
|
78
|
+
' component sets `aria-label` on the wrapper `<span>` and wraps `children` in an',
|
|
79
|
+
' `aria-hidden="true"` span to suppress the duplicate numeric announcement. The screen reader',
|
|
80
|
+
' then announces the full label instead of just the number.',
|
|
81
|
+
'- **Do not rely on colour alone** — the numeric value must be visible. Never convey meaning',
|
|
82
|
+
' exclusively through badge colour without a label or supporting text.',
|
|
83
|
+
'',
|
|
84
|
+
'---',
|
|
85
|
+
'',
|
|
86
|
+
'### TypeScript types',
|
|
87
|
+
'',
|
|
88
|
+
'```ts',
|
|
89
|
+
"import { Badge } from '@arbor-education/design-system.components';",
|
|
90
|
+
'',
|
|
91
|
+
'function MyBadge(props: Badge.Props) { ... }',
|
|
92
|
+
'```',
|
|
93
|
+
'',
|
|
94
|
+
'| Type | Description |',
|
|
95
|
+
'|---|---|',
|
|
96
|
+
'| `Badge.Props` | Full props interface |',
|
|
97
|
+
"| `Badge.Size` | `'sm' \\| 'md' \\| 'lg'` |",
|
|
98
|
+
"| `Badge.Colour` | `'purple' \\| 'salmon' \\| 'teal' \\| 'yellow' \\| 'green' \\| 'orange' \\| 'blue'` (same scale as `Dot.Colour`) |",
|
|
99
|
+
].join('\n');
|
|
100
|
+
|
|
101
|
+
const RELATED_COMPONENTS = [
|
|
102
|
+
'## Related components',
|
|
103
|
+
'',
|
|
104
|
+
'[Tag](?path=/docs/components-tag--docs) · [Pill](?path=/docs/components-pill--docs) · [Dot](?path=/docs/components-dot--docs) · [Icon](?path=/docs/components-icon--docs)',
|
|
105
|
+
].join('\n');
|
|
106
|
+
|
|
107
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
108
|
+
|
|
109
|
+
function BadgeDocsPage() {
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Title />
|
|
113
|
+
<Subtitle />
|
|
114
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
115
|
+
<DocHeading>Interactive example</DocHeading>
|
|
116
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
117
|
+
<DocPrimary />
|
|
118
|
+
<Controls />
|
|
119
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
120
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
121
|
+
<DocHeading>Developer notes</DocHeading>
|
|
122
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
123
|
+
<DocHeading>Examples</DocHeading>
|
|
124
|
+
<Stories title="" />
|
|
125
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Data
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
const colours: BadgeColour[] = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'];
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Meta
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
const meta = {
|
|
16
141
|
title: 'Components/Badge',
|
|
17
142
|
component: Badge,
|
|
143
|
+
parameters: {
|
|
144
|
+
layout: 'centered',
|
|
145
|
+
docs: {
|
|
146
|
+
page: BadgeDocsPage,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
tags: ['autodocs'],
|
|
18
150
|
argTypes: {
|
|
19
|
-
|
|
20
|
-
|
|
151
|
+
children: {
|
|
152
|
+
control: 'text',
|
|
153
|
+
description: [
|
|
154
|
+
'The count to display inside the badge. Should always be a number or short numeric string.',
|
|
155
|
+
'For counts above 99, use `"99+"` to keep the badge compact.',
|
|
156
|
+
'Badge is for numbers only — do not pass category labels or text strings.',
|
|
157
|
+
].join(' '),
|
|
158
|
+
table: {
|
|
159
|
+
type: { summary: 'React.ReactNode' },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
size: {
|
|
163
|
+
control: 'select',
|
|
164
|
+
options: ['sm', 'md', 'lg'],
|
|
165
|
+
description: [
|
|
166
|
+
'Controls the height, min-width, padding, font-size, and border-radius of the badge as a single unit.',
|
|
167
|
+
'`sm` — 1.125rem / 18px via `--badge-small-size`.',
|
|
168
|
+
'`md` — 1.25rem / 20px via `--badge-medium-size` (default).',
|
|
169
|
+
'`lg` — 1.5rem / 24px via `--badge-large-size`.',
|
|
170
|
+
'All sizes use `--border-radius-round` so single-digit badges appear as circles and multi-digit badges expand into pills.',
|
|
171
|
+
].join(' '),
|
|
172
|
+
table: {
|
|
173
|
+
type: { summary: "'sm' | 'md' | 'lg'" },
|
|
174
|
+
defaultValue: { summary: "'md'" },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
colour: {
|
|
178
|
+
control: 'select',
|
|
179
|
+
options: [undefined, ...colours],
|
|
180
|
+
description: [
|
|
181
|
+
'Extended-palette colour applied to the badge background.',
|
|
182
|
+
'When omitted (default), the badge renders with `--color-grey-800` (charcoal) background and white text.',
|
|
183
|
+
'White text pairings: salmon, green, and the default charcoal.',
|
|
184
|
+
'Dark text pairings (`--color-extended-colours-{colour}-800`): purple, teal, yellow, orange, blue.',
|
|
185
|
+
'Do not rely on colour alone to communicate meaning — the numeric value must always be visible.',
|
|
186
|
+
].join(' '),
|
|
187
|
+
table: {
|
|
188
|
+
type: { summary: "'purple' | 'salmon' | 'teal' | 'yellow' | 'green' | 'orange' | 'blue' | undefined" },
|
|
189
|
+
defaultValue: { summary: 'undefined (charcoal)' },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
a11yLabel: {
|
|
193
|
+
control: 'text',
|
|
194
|
+
description: [
|
|
195
|
+
'Accessible label for the badge. Sets `aria-label` on the wrapper `<span>` and wraps',
|
|
196
|
+
'`children` in an `aria-hidden="true"` span to suppress the duplicate numeric announcement.',
|
|
197
|
+
'Use when the badge sits next to an icon or element that provides no visible label — e.g.',
|
|
198
|
+
'`a11yLabel="5 unread messages"` next to a bell icon.',
|
|
199
|
+
'When omitted, the numeric content is announced as-is by screen readers.',
|
|
200
|
+
].join(' '),
|
|
201
|
+
table: {
|
|
202
|
+
type: { summary: 'string' },
|
|
203
|
+
defaultValue: { summary: 'undefined' },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
className: {
|
|
207
|
+
control: false,
|
|
208
|
+
description: 'Additional CSS class names appended to the badge element. Use sparingly — prefer size and colour props.',
|
|
209
|
+
table: {
|
|
210
|
+
type: { summary: 'string' },
|
|
211
|
+
defaultValue: { summary: 'undefined' },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
21
214
|
},
|
|
22
|
-
}
|
|
215
|
+
} satisfies Meta<typeof Badge>;
|
|
23
216
|
|
|
24
217
|
export default meta;
|
|
25
|
-
|
|
218
|
+
// Use StoryObj<typeof Badge> (not typeof meta) so that render-only stories are
|
|
219
|
+
// not forced to provide args for the required `children` prop — the template
|
|
220
|
+
// components supply their own Badge instances directly.
|
|
26
221
|
type Story = StoryObj<typeof Badge>;
|
|
27
222
|
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Helper: attach a per-story description to docs
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
28
227
|
const withDescription = (story: Story, description: string): Story => ({
|
|
29
228
|
...story,
|
|
30
229
|
parameters: {
|
|
@@ -38,37 +237,665 @@ const withDescription = (story: Story, description: string): Story => ({
|
|
|
38
237
|
},
|
|
39
238
|
});
|
|
40
239
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Template components for composition stories
|
|
242
|
+
// (Named components avoid react-hooks lint issues — the react-hooks ESLint
|
|
243
|
+
// plugin is NOT configured in this project, so do NOT add eslint-disable.)
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
const WithIconTemplate = () => (
|
|
247
|
+
<div
|
|
248
|
+
style={{
|
|
249
|
+
padding: 'var(--spacing-xlarge)',
|
|
250
|
+
display: 'flex',
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
gap: 'var(--spacing-large)',
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
256
|
+
<div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }}>
|
|
257
|
+
<Icon name="mail" size={16} screenReaderText="Notifications" />
|
|
258
|
+
<Badge>3</Badge>
|
|
259
|
+
</div>
|
|
260
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
261
|
+
Notifications
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const InlineWithTextTemplate = () => (
|
|
268
|
+
<div
|
|
269
|
+
style={{
|
|
270
|
+
padding: 'var(--spacing-xlarge)',
|
|
271
|
+
display: 'flex',
|
|
272
|
+
flexDirection: 'column',
|
|
273
|
+
gap: 'var(--spacing-large)',
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<div
|
|
277
|
+
style={{
|
|
278
|
+
display: 'flex',
|
|
279
|
+
alignItems: 'center',
|
|
280
|
+
gap: 'var(--spacing-large)',
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
284
|
+
Behaviour events
|
|
285
|
+
</span>
|
|
286
|
+
<Badge>14</Badge>
|
|
287
|
+
</div>
|
|
288
|
+
<div
|
|
289
|
+
style={{
|
|
290
|
+
display: 'flex',
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
gap: 'var(--spacing-large)',
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
296
|
+
Pending exclusions
|
|
297
|
+
</span>
|
|
298
|
+
<Badge colour="salmon">3</Badge>
|
|
299
|
+
</div>
|
|
300
|
+
<div
|
|
301
|
+
style={{
|
|
302
|
+
display: 'flex',
|
|
303
|
+
alignItems: 'center',
|
|
304
|
+
gap: 'var(--spacing-large)',
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
308
|
+
Attendance alerts
|
|
309
|
+
</span>
|
|
310
|
+
<Badge colour="orange">27</Badge>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const AllSizesTemplate = () => (
|
|
316
|
+
<div
|
|
317
|
+
style={{
|
|
318
|
+
padding: 'var(--spacing-xlarge)',
|
|
319
|
+
display: 'flex',
|
|
320
|
+
gap: 'var(--spacing-xlarge)',
|
|
321
|
+
alignItems: 'flex-end',
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
<div
|
|
325
|
+
style={{
|
|
326
|
+
display: 'flex',
|
|
327
|
+
flexDirection: 'column',
|
|
328
|
+
alignItems: 'center',
|
|
329
|
+
gap: 'var(--spacing-large)',
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
<Badge size="sm">7</Badge>
|
|
333
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
334
|
+
sm — 1.125rem
|
|
335
|
+
</p>
|
|
336
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
337
|
+
--badge-small-size
|
|
338
|
+
</p>
|
|
339
|
+
</div>
|
|
340
|
+
<div
|
|
341
|
+
style={{
|
|
342
|
+
display: 'flex',
|
|
343
|
+
flexDirection: 'column',
|
|
344
|
+
alignItems: 'center',
|
|
345
|
+
gap: 'var(--spacing-large)',
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
<Badge size="md">7</Badge>
|
|
349
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
350
|
+
md — 1.25rem
|
|
351
|
+
</p>
|
|
352
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
353
|
+
--badge-medium-size
|
|
354
|
+
</p>
|
|
355
|
+
</div>
|
|
356
|
+
<div
|
|
357
|
+
style={{
|
|
358
|
+
display: 'flex',
|
|
359
|
+
flexDirection: 'column',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
gap: 'var(--spacing-large)',
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
<Badge size="lg">7</Badge>
|
|
365
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
366
|
+
lg — 1.5rem
|
|
367
|
+
</p>
|
|
368
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
369
|
+
--badge-large-size
|
|
370
|
+
</p>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const AllColoursTemplate = () => (
|
|
376
|
+
<div
|
|
377
|
+
style={{
|
|
378
|
+
padding: 'var(--spacing-xlarge)',
|
|
379
|
+
display: 'flex',
|
|
380
|
+
flexDirection: 'column',
|
|
381
|
+
gap: 'var(--spacing-large)',
|
|
382
|
+
}}
|
|
383
|
+
>
|
|
384
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
385
|
+
Default (charcoal) + all 7 extended-palette colours
|
|
386
|
+
</p>
|
|
387
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
|
|
388
|
+
{/* Default charcoal first */}
|
|
389
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
390
|
+
<Badge>9</Badge>
|
|
391
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
|
|
392
|
+
</div>
|
|
393
|
+
{colours.map(c => (
|
|
394
|
+
<div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
395
|
+
<Badge colour={c}>9</Badge>
|
|
396
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
|
|
397
|
+
</div>
|
|
398
|
+
))}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const AllVariantsTemplate = () => (
|
|
404
|
+
<div
|
|
405
|
+
style={{
|
|
406
|
+
padding: 'var(--spacing-xlarge)',
|
|
407
|
+
display: 'flex',
|
|
408
|
+
flexDirection: 'column',
|
|
409
|
+
gap: 'var(--spacing-xlarge)',
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
{(['sm', 'md', 'lg'] as const).map(size => (
|
|
413
|
+
<div key={size} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
414
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
415
|
+
Size:
|
|
416
|
+
{' '}
|
|
417
|
+
{size}
|
|
418
|
+
</p>
|
|
419
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
|
|
420
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
421
|
+
<Badge size={size}>9</Badge>
|
|
422
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
|
|
423
|
+
</div>
|
|
424
|
+
{colours.map(c => (
|
|
425
|
+
<div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
426
|
+
<Badge size={size} colour={c}>9</Badge>
|
|
427
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
|
|
428
|
+
</div>
|
|
429
|
+
))}
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
))}
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const A11yWithLabelTemplate = () => (
|
|
437
|
+
<div
|
|
438
|
+
style={{
|
|
439
|
+
padding: 'var(--spacing-xlarge)',
|
|
440
|
+
display: 'flex',
|
|
441
|
+
flexDirection: 'column',
|
|
442
|
+
gap: 'var(--spacing-xlarge)',
|
|
443
|
+
}}
|
|
444
|
+
>
|
|
445
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
446
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
|
|
447
|
+
Without a11yLabel — screen reader announces "5" (no context)
|
|
448
|
+
</p>
|
|
449
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
450
|
+
<Icon name="mail" size={16} screenReaderText="Notifications" />
|
|
451
|
+
<Badge>5</Badge>
|
|
452
|
+
</div>
|
|
453
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
454
|
+
Screen reader: "mail Notifications, 5"
|
|
455
|
+
</p>
|
|
456
|
+
</div>
|
|
457
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
458
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-success-600)' }}>
|
|
459
|
+
With a11yLabel — screen reader announces the full meaningful label
|
|
460
|
+
</p>
|
|
461
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
462
|
+
<Icon name="mail" size={16} />
|
|
463
|
+
<Badge a11yLabel="5 unread messages">5</Badge>
|
|
464
|
+
</div>
|
|
465
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
466
|
+
Screen reader: "5 unread messages"
|
|
467
|
+
</p>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// Stories
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
|
|
476
|
+
export const Default: Story = withDescription(
|
|
477
|
+
{
|
|
478
|
+
args: {
|
|
479
|
+
children: '3',
|
|
480
|
+
size: 'md',
|
|
481
|
+
colour: undefined,
|
|
482
|
+
},
|
|
483
|
+
render: args => <Badge {...args} />,
|
|
484
|
+
},
|
|
485
|
+
'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.',
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
export const SingleDigit: Story = withDescription(
|
|
489
|
+
{
|
|
490
|
+
args: { children: '3' },
|
|
491
|
+
parameters: {
|
|
492
|
+
docs: {
|
|
493
|
+
source: {
|
|
494
|
+
language: 'tsx',
|
|
495
|
+
code: `
|
|
496
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
497
|
+
|
|
498
|
+
function BadgeSingleDigitExample() {
|
|
499
|
+
return <Badge>3</Badge>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export default BadgeSingleDigitExample;
|
|
503
|
+
`.trim(),
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
'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.',
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
export const DoubleDigit: Story = withDescription(
|
|
512
|
+
{
|
|
513
|
+
args: { children: '12' },
|
|
514
|
+
parameters: {
|
|
515
|
+
docs: {
|
|
516
|
+
source: {
|
|
517
|
+
language: 'tsx',
|
|
518
|
+
code: `
|
|
519
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
520
|
+
|
|
521
|
+
function BadgeDoubleDigitExample() {
|
|
522
|
+
return <Badge>12</Badge>;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export default BadgeDoubleDigitExample;
|
|
526
|
+
`.trim(),
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
'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.',
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
export const Overflow: Story = withDescription(
|
|
535
|
+
{
|
|
536
|
+
args: { children: '99+' },
|
|
537
|
+
parameters: {
|
|
538
|
+
docs: {
|
|
539
|
+
source: {
|
|
540
|
+
language: 'tsx',
|
|
541
|
+
code: `
|
|
542
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
543
|
+
|
|
544
|
+
function BadgeOverflowExample() {
|
|
545
|
+
return <Badge>99+</Badge>;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export default BadgeOverflowExample;
|
|
549
|
+
`.trim(),
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
'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.',
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
export const Zero: Story = withDescription(
|
|
558
|
+
{
|
|
559
|
+
args: { children: '0' },
|
|
560
|
+
parameters: {
|
|
561
|
+
docs: {
|
|
562
|
+
source: {
|
|
563
|
+
language: 'tsx',
|
|
564
|
+
code: `
|
|
565
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
566
|
+
|
|
567
|
+
function BadgeZeroExample() {
|
|
568
|
+
return <Badge>0</Badge>;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export default BadgeZeroExample;
|
|
572
|
+
`.trim(),
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
},
|
|
44
576
|
},
|
|
45
|
-
|
|
577
|
+
'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.',
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
export const WithIcon: Story = withDescription(
|
|
581
|
+
{
|
|
582
|
+
render: WithIconTemplate,
|
|
583
|
+
parameters: {
|
|
584
|
+
docs: {
|
|
585
|
+
source: {
|
|
586
|
+
language: 'tsx',
|
|
587
|
+
code: `
|
|
588
|
+
import { Badge, Icon } from '@arbor-education/design-system.components';
|
|
589
|
+
|
|
590
|
+
function WithIconTemplate() {
|
|
591
|
+
return (
|
|
592
|
+
<div
|
|
593
|
+
style={{
|
|
594
|
+
padding: 'var(--spacing-xlarge)',
|
|
595
|
+
display: 'flex',
|
|
596
|
+
alignItems: 'center',
|
|
597
|
+
gap: 'var(--spacing-large)',
|
|
598
|
+
}}
|
|
599
|
+
>
|
|
600
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
601
|
+
<div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }}>
|
|
602
|
+
<Icon name="mail" size={16} screenReaderText="Notifications" />
|
|
603
|
+
<Badge>3</Badge>
|
|
604
|
+
</div>
|
|
605
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
606
|
+
Notifications
|
|
607
|
+
</p>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
);
|
|
611
|
+
}
|
|
46
612
|
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
613
|
+
export default WithIconTemplate;
|
|
614
|
+
`.trim(),
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
},
|
|
51
618
|
},
|
|
52
|
-
|
|
619
|
+
'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`.',
|
|
620
|
+
);
|
|
53
621
|
|
|
54
|
-
export const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
622
|
+
export const InlineWithText: Story = withDescription(
|
|
623
|
+
{
|
|
624
|
+
render: InlineWithTextTemplate,
|
|
625
|
+
parameters: {
|
|
626
|
+
docs: {
|
|
627
|
+
source: {
|
|
628
|
+
language: 'tsx',
|
|
629
|
+
code: `
|
|
630
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
631
|
+
|
|
632
|
+
function InlineWithTextTemplate() {
|
|
633
|
+
return (
|
|
634
|
+
<div
|
|
635
|
+
style={{
|
|
636
|
+
padding: 'var(--spacing-xlarge)',
|
|
637
|
+
display: 'flex',
|
|
638
|
+
flexDirection: 'column',
|
|
639
|
+
gap: 'var(--spacing-large)',
|
|
640
|
+
}}
|
|
641
|
+
>
|
|
642
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
643
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
644
|
+
Behaviour events
|
|
645
|
+
</span>
|
|
646
|
+
<Badge>14</Badge>
|
|
647
|
+
</div>
|
|
648
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
649
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
650
|
+
Pending exclusions
|
|
651
|
+
</span>
|
|
652
|
+
<Badge colour="salmon">3</Badge>
|
|
653
|
+
</div>
|
|
654
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
655
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
656
|
+
Attendance alerts
|
|
657
|
+
</span>
|
|
658
|
+
<Badge colour="orange">27</Badge>
|
|
659
|
+
</div>
|
|
60
660
|
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
661
|
+
);
|
|
662
|
+
}
|
|
63
663
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
664
|
+
export default InlineWithTextTemplate;
|
|
665
|
+
`.trim(),
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
'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.',
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
export const AllSizes: Story = withDescription(
|
|
674
|
+
{
|
|
675
|
+
render: AllSizesTemplate,
|
|
676
|
+
parameters: {
|
|
677
|
+
docs: {
|
|
678
|
+
source: {
|
|
679
|
+
language: 'tsx',
|
|
680
|
+
code: `
|
|
681
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
682
|
+
|
|
683
|
+
function AllSizesTemplate() {
|
|
684
|
+
return (
|
|
685
|
+
<div
|
|
686
|
+
style={{
|
|
687
|
+
padding: 'var(--spacing-xlarge)',
|
|
688
|
+
display: 'flex',
|
|
689
|
+
gap: 'var(--spacing-xlarge)',
|
|
690
|
+
alignItems: 'flex-end',
|
|
691
|
+
}}
|
|
692
|
+
>
|
|
693
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
694
|
+
<Badge size="sm">7</Badge>
|
|
695
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
696
|
+
sm — 1.125rem
|
|
697
|
+
</p>
|
|
698
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
699
|
+
--badge-small-size
|
|
700
|
+
</p>
|
|
701
|
+
</div>
|
|
702
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
703
|
+
<Badge size="md">7</Badge>
|
|
704
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
705
|
+
md — 1.25rem
|
|
706
|
+
</p>
|
|
707
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
708
|
+
--badge-medium-size
|
|
709
|
+
</p>
|
|
710
|
+
</div>
|
|
711
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
712
|
+
<Badge size="lg">7</Badge>
|
|
713
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
714
|
+
lg — 1.5rem
|
|
715
|
+
</p>
|
|
716
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-1-11)' }}>
|
|
717
|
+
--badge-large-size
|
|
718
|
+
</p>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export default AllSizesTemplate;
|
|
725
|
+
`.trim(),
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
'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.',
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
export const AllColours: Story = withDescription(
|
|
734
|
+
{
|
|
735
|
+
render: AllColoursTemplate,
|
|
736
|
+
parameters: {
|
|
737
|
+
docs: {
|
|
738
|
+
source: {
|
|
739
|
+
language: 'tsx',
|
|
740
|
+
code: `
|
|
741
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
742
|
+
|
|
743
|
+
const colours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'] as const;
|
|
744
|
+
|
|
745
|
+
function AllColoursTemplate() {
|
|
746
|
+
return (
|
|
747
|
+
<div
|
|
748
|
+
style={{
|
|
749
|
+
padding: 'var(--spacing-xlarge)',
|
|
750
|
+
display: 'flex',
|
|
751
|
+
flexDirection: 'column',
|
|
752
|
+
gap: 'var(--spacing-large)',
|
|
753
|
+
}}
|
|
754
|
+
>
|
|
755
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
756
|
+
Default (charcoal) + all 7 extended-palette colours
|
|
757
|
+
</p>
|
|
758
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
|
|
759
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
760
|
+
<Badge>9</Badge>
|
|
761
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
|
|
762
|
+
</div>
|
|
763
|
+
{colours.map(c => (
|
|
764
|
+
<div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
765
|
+
<Badge colour={c}>9</Badge>
|
|
766
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
|
|
767
|
+
</div>
|
|
768
|
+
))}
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export default AllColoursTemplate;
|
|
775
|
+
`.trim(),
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
'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.',
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
export const AllVariants: Story = withDescription(
|
|
784
|
+
{
|
|
785
|
+
render: AllVariantsTemplate,
|
|
786
|
+
parameters: {
|
|
787
|
+
docs: {
|
|
788
|
+
source: {
|
|
789
|
+
language: 'tsx',
|
|
790
|
+
code: `
|
|
791
|
+
import { Badge } from '@arbor-education/design-system.components';
|
|
792
|
+
|
|
793
|
+
const colours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'] as const;
|
|
794
|
+
|
|
795
|
+
function AllVariantsTemplate() {
|
|
796
|
+
return (
|
|
797
|
+
<div
|
|
798
|
+
style={{
|
|
799
|
+
padding: 'var(--spacing-xlarge)',
|
|
800
|
+
display: 'flex',
|
|
801
|
+
flexDirection: 'column',
|
|
802
|
+
gap: 'var(--spacing-xlarge)',
|
|
803
|
+
}}
|
|
804
|
+
>
|
|
805
|
+
{(['sm', 'md', 'lg'] as const).map(size => (
|
|
806
|
+
<div key={size} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
807
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
808
|
+
Size: {size}
|
|
809
|
+
</p>
|
|
810
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-large)', alignItems: 'center' }}>
|
|
811
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
812
|
+
<Badge size={size}>9</Badge>
|
|
813
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>default</span>
|
|
814
|
+
</div>
|
|
815
|
+
{colours.map(c => (
|
|
816
|
+
<div key={c} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
817
|
+
<Badge size={size} colour={c}>9</Badge>
|
|
818
|
+
<span className="ds-text" style={{ fontSize: 'var(--font-size-1-11)', color: 'var(--color-grey-600)' }}>{c}</span>
|
|
819
|
+
</div>
|
|
820
|
+
))}
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
71
823
|
))}
|
|
72
824
|
</div>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
export default AllVariantsTemplate;
|
|
828
|
+
`.trim(),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
'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.',
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
export const A11yWithLabel: Story = withDescription(
|
|
837
|
+
{
|
|
838
|
+
render: A11yWithLabelTemplate,
|
|
839
|
+
parameters: {
|
|
840
|
+
docs: {
|
|
841
|
+
source: {
|
|
842
|
+
language: 'tsx',
|
|
843
|
+
code: `
|
|
844
|
+
import { Badge, Icon } from '@arbor-education/design-system.components';
|
|
845
|
+
|
|
846
|
+
function A11yWithLabelTemplate() {
|
|
847
|
+
return (
|
|
848
|
+
<div
|
|
849
|
+
style={{
|
|
850
|
+
padding: 'var(--spacing-xlarge)',
|
|
851
|
+
display: 'flex',
|
|
852
|
+
flexDirection: 'column',
|
|
853
|
+
gap: 'var(--spacing-xlarge)',
|
|
854
|
+
}}
|
|
855
|
+
>
|
|
856
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
857
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
|
|
858
|
+
Without a11yLabel — screen reader announces "5" (no context)
|
|
859
|
+
</p>
|
|
860
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
861
|
+
<Icon name="mail" size={16} screenReaderText="Notifications" />
|
|
862
|
+
<Badge>5</Badge>
|
|
863
|
+
</div>
|
|
864
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
865
|
+
Screen reader: "mail Notifications, 5"
|
|
866
|
+
</p>
|
|
867
|
+
</div>
|
|
868
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
869
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-success-600)' }}>
|
|
870
|
+
With a11yLabel — screen reader announces the full meaningful label
|
|
871
|
+
</p>
|
|
872
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
873
|
+
<Icon name="mail" size={16} />
|
|
874
|
+
<Badge a11yLabel="5 unread messages">5</Badge>
|
|
875
|
+
</div>
|
|
876
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
877
|
+
Screen reader: "5 unread messages"
|
|
878
|
+
</p>
|
|
879
|
+
</div>
|
|
880
|
+
</div>
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
export default A11yWithLabelTemplate;
|
|
884
|
+
`.trim(),
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
[
|
|
890
|
+
'Accessibility contrast: top row shows a badge **without** `a11yLabel` — a screen reader',
|
|
891
|
+
'moving through the icon and badge would announce the icon text and then the bare number `"5"`,',
|
|
892
|
+
'providing no context about what those 5 items are.',
|
|
893
|
+
'',
|
|
894
|
+
'The bottom row adds `a11yLabel="5 unread messages"`. The component sets `aria-label` on the',
|
|
895
|
+
'wrapper `<span>` and wraps `children` in an `aria-hidden="true"` span. The screen reader now',
|
|
896
|
+
'announces `"5 unread messages"` as a single coherent label — no duplicate number, full context.',
|
|
897
|
+
'',
|
|
898
|
+
'Use `a11yLabel` whenever the badge sits next to an icon-only element or in a context where',
|
|
899
|
+
'the number alone would be ambiguous to a screen reader user.',
|
|
900
|
+
].join(' '),
|
|
901
|
+
);
|