@arbor-education/design-system.components 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +14 -0
- package/{.claude/component-library.md → component-library.md} +27 -10
- package/dist/components/articleCard/ArticleCard.d.ts +30 -0
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.js +24 -0
- package/dist/components/articleCard/ArticleCard.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts +18 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.js +112 -0
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts +2 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.js +49 -0
- package/dist/components/articleCard/ArticleCard.test.js.map +1 -0
- 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/card/Card.d.ts +41 -12
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +46 -17
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +9 -84
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +15 -73
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/card/Card.test.js +50 -152
- package/dist/components/card/Card.test.js.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/formField/inputs/time/TimeInput.d.ts +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +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/icoText/IcoText.d.ts +37 -0
- package/dist/components/icoText/IcoText.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.js +29 -0
- package/dist/components/icoText/IcoText.js.map +1 -0
- package/dist/components/icoText/IcoText.stories.d.ts +34 -0
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.stories.js +24 -0
- package/dist/components/icoText/IcoText.stories.js.map +1 -0
- package/dist/components/icoText/IcoText.test.d.ts +2 -0
- package/dist/components/icoText/IcoText.test.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.test.js +27 -0
- package/dist/components/icoText/IcoText.test.js.map +1 -0
- 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/kpiCard/KPICard.d.ts +13 -0
- package/dist/components/kpiCard/KPICard.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.js +8 -0
- package/dist/components/kpiCard/KPICard.js.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts +9 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.js +18 -0
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -0
- package/dist/components/kpiCard/KPICard.test.d.ts +2 -0
- package/dist/components/kpiCard/KPICard.test.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.test.js +37 -0
- package/dist/components/kpiCard/KPICard.test.js.map +1 -0
- package/dist/components/kvpList/KVPList.d.ts +34 -0
- package/dist/components/kvpList/KVPList.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.js +20 -0
- package/dist/components/kvpList/KVPList.js.map +1 -0
- package/dist/components/kvpList/KVPList.stories.d.ts +27 -0
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.stories.js +18 -0
- package/dist/components/kvpList/KVPList.stories.js.map +1 -0
- package/dist/components/kvpList/KVPList.test.d.ts +2 -0
- package/dist/components/kvpList/KVPList.test.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.test.js +29 -0
- package/dist/components/kvpList/KVPList.test.js.map +1 -0
- 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/singleUser/SingleUser.d.ts +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- 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 +194 -23
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/eslint.config.mts +5 -1
- package/package.json +3 -3
- package/src/components/articleCard/ArticleCard.stories.tsx +132 -0
- package/src/components/articleCard/ArticleCard.test.tsx +121 -0
- package/src/components/articleCard/ArticleCard.tsx +100 -0
- package/src/components/articleCard/articleCard.scss +39 -0
- 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/card/Card.stories.tsx +35 -79
- package/src/components/card/Card.test.tsx +72 -190
- package/src/components/card/Card.tsx +117 -58
- package/src/components/card/card.scss +18 -31
- 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/icoText/IcoText.stories.tsx +47 -0
- package/src/components/icoText/IcoText.test.tsx +41 -0
- package/src/components/icoText/IcoText.tsx +93 -0
- package/src/components/icoText/icoText.scss +34 -0
- package/src/components/icon/Icon.stories.tsx +1446 -12
- package/src/components/kpiCard/KPICard.stories.tsx +47 -0
- package/src/components/kpiCard/KPICard.test.tsx +60 -0
- package/src/components/kpiCard/KPICard.tsx +45 -0
- package/src/components/kpiCard/kpiCard.scss +35 -0
- package/src/components/kvpList/KVPList.stories.tsx +51 -0
- package/src/components/kvpList/KVPList.test.tsx +66 -0
- package/src/components/kvpList/KVPList.tsx +109 -0
- package/src/components/kvpList/kvpList.scss +64 -0
- 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/tag/Tag.stories.tsx +755 -53
- package/src/index.scss +4 -0
- package/src/index.ts +13 -4
- package/src/tokens.scss +6 -0
- package/tokens/json/Arbor.json +30 -0
- 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,41 +1,732 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import {
|
|
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 { Tag } from 'Components/tag/Tag';
|
|
12
|
+
import { Dot, type DotColour } from './Dot';
|
|
3
13
|
|
|
4
|
-
|
|
5
|
-
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Docs page content
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const DESCRIPTION_INTRO = [
|
|
19
|
+
'Dot is the smallest coloured indicator in the Arbor design system — a fixed 10 × 10 pixel',
|
|
20
|
+
'circle that communicates status, category, or grouping through colour. It provides the visual',
|
|
21
|
+
'primitive; the consuming application owns the semantic meaning.',
|
|
22
|
+
].join('\n');
|
|
23
|
+
|
|
24
|
+
const USAGE_GUIDANCE = [
|
|
25
|
+
'### When to use',
|
|
26
|
+
'',
|
|
27
|
+
'- **Inside `Tag`\'s `slotStart`** — the canonical dot-prefix pattern for category tags',
|
|
28
|
+
' (`"Year 7"`, `"SEN"`, `"Active"`)',
|
|
29
|
+
'- **Alongside text in lists or tables** — paired with a student name, status label, or row',
|
|
30
|
+
' description so that colour is never the sole conveyor of meaning (WCAG 1.4.1)',
|
|
31
|
+
'- **Chart or map legends** — where an adjacent label names the colour grouping',
|
|
32
|
+
'',
|
|
33
|
+
'---',
|
|
34
|
+
'',
|
|
35
|
+
'### When NOT to use',
|
|
36
|
+
'',
|
|
37
|
+
'| Instead of Dot, use | When you need |',
|
|
38
|
+
'|---|---|',
|
|
39
|
+
'| [`Badge`](?path=/docs/components-badge--docs) | A numeric count: `3`, `12`, `99+` |',
|
|
40
|
+
'| [`Icon`](?path=/docs/components-icon--docs) | A recognisable glyph that conveys meaning by shape |',
|
|
41
|
+
'| [`Tag`](?path=/docs/components-tag--docs) | A text label for metadata or category |',
|
|
42
|
+
'| [`Pill`](?path=/docs/components-pill--docs) | An interactive filter toggle |',
|
|
43
|
+
'',
|
|
44
|
+
'```tsx',
|
|
45
|
+
'// BAD — Icon name="dot" is Lucide\'s 16 px dot glyph, not the design-system circle',
|
|
46
|
+
'<Icon name="dot" />',
|
|
47
|
+
'',
|
|
48
|
+
'// GOOD — Dot is always 10 × 10 and perfectly round',
|
|
49
|
+
'<Dot colour="green" />',
|
|
50
|
+
'```',
|
|
51
|
+
'',
|
|
52
|
+
'---',
|
|
53
|
+
'',
|
|
54
|
+
'### Arbor de-facto colour semantics',
|
|
55
|
+
'',
|
|
56
|
+
'The Arbor platform has established informal conventions for Dot colours. These are **not**',
|
|
57
|
+
'enforced at the design system level — your application owns the semantic mapping — but',
|
|
58
|
+
'applying them consistently improves recognition across the product.',
|
|
59
|
+
'',
|
|
60
|
+
'| Colour | Arbor convention |',
|
|
61
|
+
'|---|---|',
|
|
62
|
+
'| `green` | On track, positive attendance, active |',
|
|
63
|
+
'| `salmon` | Pupil Premium, attention needed |',
|
|
64
|
+
'| `orange` | At risk, warning |',
|
|
65
|
+
'| `purple` | SEN |',
|
|
66
|
+
'| `teal` | EAL |',
|
|
67
|
+
'| `blue` | Subject or category grouping |',
|
|
68
|
+
'| `yellow` | Active, neutral attention |',
|
|
69
|
+
'',
|
|
70
|
+
'Limit the number of distinct colours visible at once — per the Pills & Tags & Badges',
|
|
71
|
+
'Confluence guidance, fewer colours used consistently carry more meaning than a full rainbow.',
|
|
72
|
+
].join('\n');
|
|
73
|
+
|
|
74
|
+
const DEVELOPER_NOTES = [
|
|
75
|
+
'### Closed API',
|
|
76
|
+
'',
|
|
77
|
+
'Dot is deliberately closed: `colour` and `label` are the only props. There is no `size`',
|
|
78
|
+
'(always 10 × 10), no `className` or `style` override, and no `onClick`. If you need a',
|
|
79
|
+
'different size or a clickable indicator, compose a different component.',
|
|
80
|
+
'',
|
|
81
|
+
'---',
|
|
82
|
+
'',
|
|
83
|
+
'### Accessibility',
|
|
84
|
+
'',
|
|
85
|
+
'Dot has two modes, controlled by the optional `label` prop:',
|
|
86
|
+
'',
|
|
87
|
+
'| Mode | When | Markup |',
|
|
88
|
+
'|---|---|---|',
|
|
89
|
+
'| **Decorative** (default) | Adjacent text carries the meaning | `aria-hidden="true"` — invisible to assistive technology |',
|
|
90
|
+
'| **Meaningful** | Dot is standalone with no adjacent label | `aria-label={label}` — announced by screen readers |',
|
|
91
|
+
'',
|
|
92
|
+
'The rule is simple:',
|
|
93
|
+
'',
|
|
94
|
+
'- If visible text next to the dot tells users what the colour means — omit `label`.',
|
|
95
|
+
'- If the dot is the only signal — provide `label`.',
|
|
96
|
+
'',
|
|
97
|
+
'**WCAG 1.4.1 Use of Colour**: colour alone must never be the only means of conveying',
|
|
98
|
+
'information. Either provide a `label`, or pair the dot with adjacent text that carries the',
|
|
99
|
+
'semantic weight.',
|
|
100
|
+
'',
|
|
101
|
+
'---',
|
|
102
|
+
'',
|
|
103
|
+
'### TypeScript types',
|
|
104
|
+
'',
|
|
105
|
+
'```ts',
|
|
106
|
+
"import { Dot } from '@arbor-education/design-system.components';",
|
|
107
|
+
'',
|
|
108
|
+
'function MyDot(props: Dot.Props) { ... }',
|
|
109
|
+
'```',
|
|
110
|
+
'',
|
|
111
|
+
'| Type | Description |',
|
|
112
|
+
'|---|---|',
|
|
113
|
+
'| `Dot.Props` | Full props interface |',
|
|
114
|
+
"| `Dot.Colour` | `'purple' \\| 'salmon' \\| 'teal' \\| 'yellow' \\| 'green' \\| 'orange' \\| 'blue'` |",
|
|
115
|
+
].join('\n');
|
|
116
|
+
|
|
117
|
+
const RELATED_COMPONENTS = [
|
|
118
|
+
'## Related components',
|
|
119
|
+
'',
|
|
120
|
+
'[Tag](?path=/docs/components-tag--docs) · [Badge](?path=/docs/components-badge--docs) · [Icon](?path=/docs/components-icon--docs) · [Pill](?path=/docs/components-pill--docs)',
|
|
121
|
+
].join('\n');
|
|
122
|
+
|
|
123
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
124
|
+
|
|
125
|
+
function DotDocsPage() {
|
|
126
|
+
return (
|
|
127
|
+
<>
|
|
128
|
+
<Title />
|
|
129
|
+
<Subtitle />
|
|
130
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
131
|
+
<DocHeading>Interactive example</DocHeading>
|
|
132
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
133
|
+
<DocPrimary />
|
|
134
|
+
<Controls />
|
|
135
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
136
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
137
|
+
<DocHeading>Developer notes</DocHeading>
|
|
138
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
139
|
+
<DocHeading>Examples</DocHeading>
|
|
140
|
+
<Stories title="" />
|
|
141
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
142
|
+
</>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Data
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
const allColours: DotColour[] = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'];
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Meta
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
const meta = {
|
|
6
157
|
title: 'Components/Dot',
|
|
7
158
|
component: Dot,
|
|
8
|
-
|
|
159
|
+
parameters: {
|
|
160
|
+
layout: 'centered',
|
|
161
|
+
docs: {
|
|
162
|
+
page: DotDocsPage,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
tags: ['autodocs'],
|
|
166
|
+
argTypes: {
|
|
167
|
+
colour: {
|
|
168
|
+
control: 'select',
|
|
169
|
+
options: allColours,
|
|
170
|
+
description: [
|
|
171
|
+
'The background colour of the circle. **Required** — there is no default.',
|
|
172
|
+
'Establish a consistent semantic mapping in your application (e.g. green = on track,',
|
|
173
|
+
'salmon = Pupil Premium) and apply it uniformly.',
|
|
174
|
+
].join(' '),
|
|
175
|
+
table: {
|
|
176
|
+
type: { summary: "'purple' | 'salmon' | 'teal' | 'yellow' | 'green' | 'orange' | 'blue'" },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
label: {
|
|
180
|
+
control: 'text',
|
|
181
|
+
description: [
|
|
182
|
+
'Accessible label for the dot. When omitted (default), the dot is decorative and',
|
|
183
|
+
'invisible to assistive technology — correct when adjacent visible text carries the',
|
|
184
|
+
'meaning. When provided, the screen reader announces the label. Use this prop whenever',
|
|
185
|
+
'the dot is the sole carrier of information (no adjacent text).',
|
|
186
|
+
].join(' '),
|
|
187
|
+
table: {
|
|
188
|
+
type: { summary: 'string' },
|
|
189
|
+
defaultValue: { summary: 'undefined (decorative — aria-hidden)' },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
} satisfies Meta<typeof Dot>;
|
|
9
194
|
|
|
10
195
|
export default meta;
|
|
11
|
-
|
|
196
|
+
// Use StoryObj<typeof Dot> (not typeof meta) so that render-only stories are
|
|
197
|
+
// not forced to provide args for the required `colour` prop — template
|
|
198
|
+
// components supply their own Dot instances directly.
|
|
12
199
|
type Story = StoryObj<typeof Dot>;
|
|
13
200
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Helper: attach a per-story description to docs
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
const withDescription = (story: Story, description: string): Story => ({
|
|
206
|
+
...story,
|
|
207
|
+
parameters: {
|
|
208
|
+
...story.parameters,
|
|
209
|
+
docs: {
|
|
210
|
+
...story.parameters?.docs,
|
|
211
|
+
description: {
|
|
212
|
+
story: description,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Template components for composition stories
|
|
220
|
+
// (Named components avoid react-hooks lint issues — the react-hooks ESLint
|
|
221
|
+
// plugin is NOT configured in this project, so do NOT add eslint-disable.)
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
const AllColoursTemplate = () => (
|
|
225
|
+
<div
|
|
226
|
+
style={{
|
|
227
|
+
padding: 'var(--spacing-xlarge)',
|
|
228
|
+
background: 'var(--color-grey-050)',
|
|
229
|
+
borderRadius: 'var(--border-radius-small)',
|
|
230
|
+
display: 'flex',
|
|
231
|
+
flexWrap: 'wrap',
|
|
232
|
+
gap: 'var(--spacing-small)',
|
|
233
|
+
alignItems: 'center',
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
{allColours.map(colour => (
|
|
237
|
+
<div
|
|
238
|
+
key={colour}
|
|
239
|
+
style={{
|
|
240
|
+
display: 'flex',
|
|
241
|
+
flexDirection: 'column',
|
|
242
|
+
alignItems: 'center',
|
|
243
|
+
gap: 'var(--spacing-small)',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<Dot colour={colour} label={colour} />
|
|
247
|
+
<span
|
|
248
|
+
className="ds-text"
|
|
249
|
+
style={{ color: 'var(--color-grey-600)' }}
|
|
250
|
+
>
|
|
251
|
+
{colour}
|
|
252
|
+
</span>
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const DecorativeVsMeaningfulTemplate = () => (
|
|
259
|
+
<div
|
|
260
|
+
style={{
|
|
261
|
+
padding: 'var(--spacing-xlarge)',
|
|
262
|
+
display: 'flex',
|
|
263
|
+
gap: 'var(--spacing-xlarge)',
|
|
264
|
+
flexWrap: 'wrap',
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{/* LEFT: Decorative dot — adjacent text carries meaning */}
|
|
268
|
+
<div
|
|
269
|
+
style={{
|
|
270
|
+
display: 'flex',
|
|
271
|
+
flexDirection: 'column',
|
|
272
|
+
gap: 'var(--spacing-large)',
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
<p
|
|
276
|
+
className="ds-text"
|
|
277
|
+
style={{ margin: 0, color: 'var(--color-grey-600)' }}
|
|
278
|
+
>
|
|
279
|
+
Decorative (aria-hidden)
|
|
280
|
+
</p>
|
|
281
|
+
<div
|
|
282
|
+
style={{
|
|
283
|
+
display: 'flex',
|
|
284
|
+
alignItems: 'center',
|
|
285
|
+
gap: 'var(--spacing-small)',
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
{/* No label prop — dot is decorative because "Active" text is present */}
|
|
289
|
+
<Dot colour="green" />
|
|
290
|
+
<span className="ds-text">Active</span>
|
|
291
|
+
</div>
|
|
292
|
+
<p
|
|
293
|
+
className="ds-text"
|
|
294
|
+
style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}
|
|
295
|
+
>
|
|
296
|
+
Screen reader: "Active"
|
|
297
|
+
</p>
|
|
298
|
+
<p
|
|
299
|
+
className="ds-text"
|
|
300
|
+
style={{ margin: 0, color: 'var(--color-grey-600)' }}
|
|
301
|
+
>
|
|
302
|
+
The dot renders with no label prop. Adjacent text
|
|
303
|
+
"Active" carries the full meaning — the dot is
|
|
304
|
+
a decorative reinforcement only.
|
|
305
|
+
</p>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* RIGHT: Meaningful dot — standalone, no adjacent text */}
|
|
309
|
+
<div
|
|
310
|
+
style={{
|
|
311
|
+
display: 'flex',
|
|
312
|
+
flexDirection: 'column',
|
|
313
|
+
gap: 'var(--spacing-large)',
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<p
|
|
317
|
+
className="ds-text"
|
|
318
|
+
style={{ margin: 0, color: 'var(--color-grey-600)' }}
|
|
319
|
+
>
|
|
320
|
+
Meaningful (aria-label)
|
|
321
|
+
</p>
|
|
322
|
+
<div
|
|
323
|
+
style={{
|
|
324
|
+
display: 'flex',
|
|
325
|
+
alignItems: 'center',
|
|
326
|
+
gap: 'var(--spacing-small)',
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
{/* label prop set — dot is meaningful, no adjacent text */}
|
|
330
|
+
<Dot colour="green" label="Active" />
|
|
331
|
+
</div>
|
|
332
|
+
<p
|
|
333
|
+
className="ds-text"
|
|
334
|
+
style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}
|
|
335
|
+
>
|
|
336
|
+
Screen reader: "Active"
|
|
337
|
+
</p>
|
|
338
|
+
<p
|
|
339
|
+
className="ds-text"
|
|
340
|
+
style={{ margin: 0, color: 'var(--color-grey-600)' }}
|
|
341
|
+
>
|
|
342
|
+
The dot renders with
|
|
343
|
+
{' '}
|
|
344
|
+
<code>label="Active"</code>
|
|
345
|
+
. No adjacent text —
|
|
346
|
+
so the dot itself must carry the semantic meaning via
|
|
347
|
+
aria-label.
|
|
348
|
+
</p>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const InTagContextTemplate = () => (
|
|
354
|
+
<div
|
|
355
|
+
style={{
|
|
356
|
+
padding: 'var(--spacing-xlarge)',
|
|
357
|
+
display: 'flex',
|
|
358
|
+
gap: 'var(--spacing-small)',
|
|
359
|
+
flexWrap: 'wrap',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
{/*
|
|
364
|
+
Dot is decorative in all four Tags below — the Tag's text children
|
|
365
|
+
carry the semantic meaning. flex-shrink: 0 on ds-dot means the circle
|
|
366
|
+
never collapses inside Tag's internal flex layout.
|
|
367
|
+
*/}
|
|
368
|
+
<Tag color="blue" slotStart={<Dot colour="blue" />}>Year 7</Tag>
|
|
369
|
+
<Tag color="green" slotStart={<Dot colour="green" />}>Active</Tag>
|
|
370
|
+
<Tag color="purple" slotStart={<Dot colour="purple" />}>SEN</Tag>
|
|
371
|
+
<Tag color="salmon" slotStart={<Dot colour="salmon" />}>At risk</Tag>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const InStatusListTemplate = () => (
|
|
376
|
+
<div
|
|
377
|
+
style={{
|
|
378
|
+
padding: 'var(--spacing-xlarge)',
|
|
379
|
+
display: 'flex',
|
|
380
|
+
flexDirection: 'column',
|
|
381
|
+
gap: 'var(--spacing-large)',
|
|
382
|
+
}}
|
|
383
|
+
>
|
|
384
|
+
<p
|
|
385
|
+
className="ds-text"
|
|
386
|
+
style={{ margin: 0, color: 'var(--color-grey-600)' }}
|
|
387
|
+
>
|
|
388
|
+
Today's attendance
|
|
389
|
+
</p>
|
|
390
|
+
<div
|
|
391
|
+
style={{
|
|
392
|
+
display: 'flex',
|
|
393
|
+
flexDirection: 'column',
|
|
394
|
+
gap: 'var(--spacing-small)',
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
397
|
+
{/*
|
|
398
|
+
Most students share the same "Present" (green) dot.
|
|
399
|
+
Only flagged entries use different colours. This demonstrates
|
|
400
|
+
the "establish a semantic mapping and apply it consistently"
|
|
401
|
+
guidance — a meaningful pattern, not a decorative rainbow.
|
|
402
|
+
|
|
403
|
+
All dots here are decorative (no label prop) because the
|
|
404
|
+
adjacent student name + status text carries the meaning.
|
|
405
|
+
*/}
|
|
406
|
+
{[
|
|
407
|
+
{ name: 'Ahmed Al-Rashidi', status: 'Present', colour: 'green' as DotColour },
|
|
408
|
+
{ name: 'Maria Santos', status: 'Present', colour: 'green' as DotColour },
|
|
409
|
+
{ name: 'James Okonkwo', status: 'Late', colour: 'yellow' as DotColour },
|
|
410
|
+
{ name: 'Chloe Patel', status: 'Medical', colour: 'teal' as DotColour },
|
|
411
|
+
{ name: 'Oliver Wright', status: 'Absent', colour: 'salmon' as DotColour },
|
|
412
|
+
].map(({ name, status, colour }) => (
|
|
413
|
+
<div
|
|
414
|
+
key={name}
|
|
415
|
+
style={{
|
|
416
|
+
display: 'flex',
|
|
417
|
+
alignItems: 'center',
|
|
418
|
+
gap: 'var(--spacing-small)',
|
|
419
|
+
}}
|
|
420
|
+
>
|
|
421
|
+
<Dot colour={colour} />
|
|
422
|
+
<span className="ds-text">{name}</span>
|
|
423
|
+
<span
|
|
424
|
+
className="ds-text"
|
|
425
|
+
style={{ color: 'var(--color-grey-600)' }}
|
|
426
|
+
>
|
|
427
|
+
—
|
|
428
|
+
{' '}
|
|
429
|
+
{status}
|
|
430
|
+
</span>
|
|
431
|
+
</div>
|
|
432
|
+
))}
|
|
39
433
|
</div>
|
|
40
|
-
|
|
41
|
-
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// ---------------------------------------------------------------------------
|
|
438
|
+
// Stories
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
export const Default: Story = withDescription(
|
|
442
|
+
{
|
|
443
|
+
args: {
|
|
444
|
+
colour: 'purple',
|
|
445
|
+
},
|
|
446
|
+
render: args => <Dot {...args} />,
|
|
447
|
+
},
|
|
448
|
+
'The interactive canvas story — every prop is wired to the **Controls** panel. Use the controls to explore all 7 colours and toggle the `label` prop to switch between decorative (`aria-hidden`) and meaningful (`aria-label`) modes. Dot is decorative by default: no `label` prop means assistive technology ignores it entirely.',
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
export const AllColours: Story = withDescription(
|
|
452
|
+
{
|
|
453
|
+
parameters: {
|
|
454
|
+
controls: { disable: true },
|
|
455
|
+
docs: {
|
|
456
|
+
source: {
|
|
457
|
+
language: 'tsx',
|
|
458
|
+
code: `
|
|
459
|
+
import { Dot } from '@arbor-education/design-system.components';
|
|
460
|
+
|
|
461
|
+
const allColours = ['purple', 'salmon', 'teal', 'yellow', 'green', 'orange', 'blue'] as const;
|
|
462
|
+
|
|
463
|
+
function AllColoursTemplate() {
|
|
464
|
+
return (
|
|
465
|
+
<div
|
|
466
|
+
style={{
|
|
467
|
+
padding: 'var(--spacing-xlarge)',
|
|
468
|
+
background: 'var(--color-grey-050)',
|
|
469
|
+
borderRadius: 'var(--border-radius-small)',
|
|
470
|
+
display: 'flex',
|
|
471
|
+
flexWrap: 'wrap',
|
|
472
|
+
gap: 'var(--spacing-small)',
|
|
473
|
+
alignItems: 'center',
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
{allColours.map(colour => (
|
|
477
|
+
<div
|
|
478
|
+
key={colour}
|
|
479
|
+
style={{
|
|
480
|
+
display: 'flex',
|
|
481
|
+
flexDirection: 'column',
|
|
482
|
+
alignItems: 'center',
|
|
483
|
+
gap: 'var(--spacing-small)',
|
|
484
|
+
}}
|
|
485
|
+
>
|
|
486
|
+
<Dot colour={colour} label={colour} />
|
|
487
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
488
|
+
{colour}
|
|
489
|
+
</span>
|
|
490
|
+
</div>
|
|
491
|
+
))}
|
|
492
|
+
</div>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
export default AllColoursTemplate;
|
|
496
|
+
`.trim(),
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
render: AllColoursTemplate,
|
|
501
|
+
},
|
|
502
|
+
[
|
|
503
|
+
'All 7 `DotColour` values displayed together. The subtle grey wash (`--color-grey-050`)',
|
|
504
|
+
'background ensures 10 × 10 circles are visible against white canvas. Each dot uses',
|
|
505
|
+
'`label={colour}` here so screen readers can navigate the story sensibly — in real',
|
|
506
|
+
'usage, decorative dots paired with visible text would omit the `label` prop.',
|
|
507
|
+
'',
|
|
508
|
+
'**Note:** `DotColour` shares its 7 values with `BadgeColour` — a single colour string',
|
|
509
|
+
'works for both components without any casting.',
|
|
510
|
+
].join(' '),
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
export const WithAccessibleLabel: Story = withDescription(
|
|
514
|
+
{
|
|
515
|
+
args: {
|
|
516
|
+
colour: 'purple',
|
|
517
|
+
label: 'Purple — SEN category',
|
|
518
|
+
},
|
|
519
|
+
render: args => <Dot {...args} />,
|
|
520
|
+
parameters: {
|
|
521
|
+
docs: {
|
|
522
|
+
source: {
|
|
523
|
+
language: 'tsx',
|
|
524
|
+
code: `
|
|
525
|
+
import { Dot } from '@arbor-education/design-system.components';
|
|
526
|
+
|
|
527
|
+
function WithAccessibleLabelExample() {
|
|
528
|
+
return <Dot colour="purple" label="Purple — SEN category" />;
|
|
529
|
+
}
|
|
530
|
+
export default WithAccessibleLabelExample;
|
|
531
|
+
`.trim(),
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
[
|
|
537
|
+
'A dot with a `label` prop — rendered with `aria-label="Purple — SEN category"` and no',
|
|
538
|
+
'`aria-hidden`. A screen reader navigating to this element announces the label directly.',
|
|
539
|
+
'Use this pattern whenever the dot is the sole carrier of information — for example, a',
|
|
540
|
+
'standalone status indicator in a column with no adjacent text. When visible text is',
|
|
541
|
+
'present beside the dot, omit `label` instead (see **DecorativeVsMeaningful**).',
|
|
542
|
+
].join(' '),
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
export const DecorativeVsMeaningful: Story = withDescription(
|
|
546
|
+
{
|
|
547
|
+
parameters: {
|
|
548
|
+
controls: { disable: true },
|
|
549
|
+
docs: {
|
|
550
|
+
source: {
|
|
551
|
+
language: 'tsx',
|
|
552
|
+
code: `
|
|
553
|
+
import { Dot } from '@arbor-education/design-system.components';
|
|
554
|
+
|
|
555
|
+
function DecorativeVsMeaningfulTemplate() {
|
|
556
|
+
return (
|
|
557
|
+
<div
|
|
558
|
+
style={{
|
|
559
|
+
padding: 'var(--spacing-xlarge)',
|
|
560
|
+
display: 'flex',
|
|
561
|
+
gap: 'var(--spacing-xlarge)',
|
|
562
|
+
flexWrap: 'wrap',
|
|
563
|
+
}}
|
|
564
|
+
>
|
|
565
|
+
{/* Decorative dot — adjacent text carries meaning */}
|
|
566
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
567
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
568
|
+
Decorative (aria-hidden)
|
|
569
|
+
</p>
|
|
570
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
571
|
+
{/* No label prop — dot is decorative because "Active" text is present */}
|
|
572
|
+
<Dot colour="green" />
|
|
573
|
+
<span className="ds-text">Active</span>
|
|
574
|
+
</div>
|
|
575
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
576
|
+
Screen reader: "Active"
|
|
577
|
+
</p>
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
{/* Meaningful dot — standalone, no adjacent text */}
|
|
581
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
582
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
583
|
+
Meaningful (aria-label)
|
|
584
|
+
</p>
|
|
585
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}>
|
|
586
|
+
{/* label prop set — dot is meaningful, no adjacent text */}
|
|
587
|
+
<Dot colour="green" label="Active" />
|
|
588
|
+
</div>
|
|
589
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
|
|
590
|
+
Screen reader: "Active"
|
|
591
|
+
</p>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
export default DecorativeVsMeaningfulTemplate;
|
|
597
|
+
`.trim(),
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
render: DecorativeVsMeaningfulTemplate,
|
|
602
|
+
},
|
|
603
|
+
[
|
|
604
|
+
'The core accessibility teaching story. **Left column**: a decorative dot paired with',
|
|
605
|
+
'visible text ("Active") — no `label` prop, `aria-hidden="true"`, screen reader',
|
|
606
|
+
'announces only the text. **Right column**: a standalone meaningful dot — `label="Active"`,',
|
|
607
|
+
'screen reader announces the label.',
|
|
608
|
+
'',
|
|
609
|
+
'**WCAG 1.4.1 Use of Colour**: colour alone must never be the only means of conveying',
|
|
610
|
+
'information. The rule is: if visible text next to the dot tells users what the colour means,',
|
|
611
|
+
'omit `label`. If the dot is the only signal, provide `label`.',
|
|
612
|
+
].join(' '),
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
export const InTagContext: Story = withDescription(
|
|
616
|
+
{
|
|
617
|
+
parameters: {
|
|
618
|
+
controls: { disable: true },
|
|
619
|
+
docs: {
|
|
620
|
+
source: {
|
|
621
|
+
language: 'tsx',
|
|
622
|
+
code: `
|
|
623
|
+
import { Dot, Tag } from '@arbor-education/design-system.components';
|
|
624
|
+
|
|
625
|
+
function InTagContextTemplate() {
|
|
626
|
+
return (
|
|
627
|
+
<div
|
|
628
|
+
style={{
|
|
629
|
+
padding: 'var(--spacing-xlarge)',
|
|
630
|
+
display: 'flex',
|
|
631
|
+
gap: 'var(--spacing-small)',
|
|
632
|
+
flexWrap: 'wrap',
|
|
633
|
+
alignItems: 'center',
|
|
634
|
+
}}
|
|
635
|
+
>
|
|
636
|
+
<Tag color="blue" slotStart={<Dot colour="blue" />}>Year 7</Tag>
|
|
637
|
+
<Tag color="green" slotStart={<Dot colour="green" />}>Active</Tag>
|
|
638
|
+
<Tag color="purple" slotStart={<Dot colour="purple" />}>SEN</Tag>
|
|
639
|
+
<Tag color="salmon" slotStart={<Dot colour="salmon" />}>At risk</Tag>
|
|
640
|
+
</div>
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
export default InTagContextTemplate;
|
|
644
|
+
`.trim(),
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
render: InTagContextTemplate,
|
|
649
|
+
},
|
|
650
|
+
[
|
|
651
|
+
'The canonical Arbor usage pattern: `<Tag slotStart={<Dot colour="..." />}>Label</Tag>`.',
|
|
652
|
+
'All four dots are **decorative** — no `label` prop — because `Tag`\'s text children',
|
|
653
|
+
'("Year 7", "Active", "SEN", "At risk") already carry the full semantic meaning.',
|
|
654
|
+
'',
|
|
655
|
+
'`flex-shrink: 0` is built into `ds-dot`, so the circle never collapses inside',
|
|
656
|
+
'`Tag`\'s internal flex layout, even in very narrow containers.',
|
|
657
|
+
'',
|
|
658
|
+
'**Colour note:** `Tag` and `Dot` use separate colour props (`TagColor` vs `DotColour`)',
|
|
659
|
+
'but share the same 7 extended-palette values. Always match both to the same colour',
|
|
660
|
+
'for visual consistency.',
|
|
661
|
+
].join(' '),
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
export const InStatusList: Story = withDescription(
|
|
665
|
+
{
|
|
666
|
+
parameters: {
|
|
667
|
+
controls: { disable: true },
|
|
668
|
+
docs: {
|
|
669
|
+
source: {
|
|
670
|
+
language: 'tsx',
|
|
671
|
+
code: `
|
|
672
|
+
import { Dot } from '@arbor-education/design-system.components';
|
|
673
|
+
|
|
674
|
+
function InStatusListTemplate() {
|
|
675
|
+
const students = [
|
|
676
|
+
{ name: 'Ahmed Al-Rashidi', status: 'Present', colour: 'green' as const },
|
|
677
|
+
{ name: 'Maria Santos', status: 'Present', colour: 'green' as const },
|
|
678
|
+
{ name: 'James Okonkwo', status: 'Late', colour: 'yellow' as const },
|
|
679
|
+
{ name: 'Chloe Patel', status: 'Medical', colour: 'teal' as const },
|
|
680
|
+
{ name: 'Oliver Wright', status: 'Absent', colour: 'salmon' as const },
|
|
681
|
+
];
|
|
682
|
+
|
|
683
|
+
return (
|
|
684
|
+
<div
|
|
685
|
+
style={{
|
|
686
|
+
padding: 'var(--spacing-xlarge)',
|
|
687
|
+
display: 'flex',
|
|
688
|
+
flexDirection: 'column',
|
|
689
|
+
gap: 'var(--spacing-large)',
|
|
690
|
+
}}
|
|
691
|
+
>
|
|
692
|
+
<p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
693
|
+
Today's attendance
|
|
694
|
+
</p>
|
|
695
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }}>
|
|
696
|
+
{students.map(({ name, status, colour }) => (
|
|
697
|
+
<div
|
|
698
|
+
key={name}
|
|
699
|
+
style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}
|
|
700
|
+
>
|
|
701
|
+
<Dot colour={colour} />
|
|
702
|
+
<span className="ds-text">{name}</span>
|
|
703
|
+
<span className="ds-text" style={{ color: 'var(--color-grey-600)' }}>
|
|
704
|
+
— {status}
|
|
705
|
+
</span>
|
|
706
|
+
</div>
|
|
707
|
+
))}
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
export default InStatusListTemplate;
|
|
713
|
+
`.trim(),
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
render: InStatusListTemplate,
|
|
718
|
+
},
|
|
719
|
+
[
|
|
720
|
+
'A realistic Arbor attendance list: student name, status text, and a Dot for each row.',
|
|
721
|
+
'Most students share the same `green` dot ("Present") — only flagged entries use',
|
|
722
|
+
'different colours (`yellow` for Late, `teal` for Medical, `salmon` for Absent).',
|
|
723
|
+
'',
|
|
724
|
+
'This demonstrates the key design principle: **establish a semantic colour mapping and',
|
|
725
|
+
'apply it consistently**. A meaningful pattern — where most rows are one colour and',
|
|
726
|
+
'exceptions stand out — carries far more information than a decorative rainbow.',
|
|
727
|
+
'',
|
|
728
|
+
'All dots here are **decorative** (no `label` prop) because the adjacent name and',
|
|
729
|
+
'status text ("Ahmed Al-Rashidi — Present") carries the full meaning for every row.',
|
|
730
|
+
'WCAG 1.4.1 is satisfied by the visible text, not the colour alone.',
|
|
731
|
+
].join(' '),
|
|
732
|
+
);
|