@arbor-education/design-system.components 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6 -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/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/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/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/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/tag/Tag.stories.tsx +755 -53
- 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,21 +1,256 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { fn } from 'storybook/test';
|
|
3
5
|
import { Dot } from '../dot/Dot';
|
|
4
6
|
import { Icon } from '../icon/Icon';
|
|
5
7
|
import { Tag } from './Tag';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Docs page content
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const DESCRIPTION_INTRO = [
|
|
12
|
+
'Tag is a compact, non-interactive label used to describe metadata, categories, or statuses — think "Year 7", "SEN",',
|
|
13
|
+
'"Pupil Premium", or "Active". Tags support 8 colour variants, composable leading/trailing slots, and a selection',
|
|
14
|
+
'state. The remove button exists exclusively for composite chip patterns (like Combobox selection pills); standalone',
|
|
15
|
+
'tags in metadata lists are never interactive.',
|
|
16
|
+
].join('\n');
|
|
17
|
+
const USAGE_GUIDANCE = [
|
|
18
|
+
'### When to use',
|
|
19
|
+
'',
|
|
20
|
+
'- **Student attributes** — year group, form group, special educational needs, funding flags: `Year 7`, `SEN`, `Pupil Premium`',
|
|
21
|
+
'- **Subject / curriculum labels** — `Maths`, `English`, `Autumn term`',
|
|
22
|
+
'- **Status descriptors** — `Active`, `On track`, `At risk`, `Assessed`',
|
|
23
|
+
'- **Category clustering** — a group of Tags on a student profile card describing their characteristics',
|
|
24
|
+
'',
|
|
25
|
+
'---',
|
|
26
|
+
'',
|
|
27
|
+
'### When NOT to use',
|
|
28
|
+
'',
|
|
29
|
+
'| Scenario | Use instead |',
|
|
30
|
+
'|---|---|',
|
|
31
|
+
'| Filtering results by a category | [`Pill`](?path=/docs/components-pill--docs) |',
|
|
32
|
+
'| Displaying a numeric count | [`Badge`](?path=/docs/components-badge--docs) |',
|
|
33
|
+
'| Status dot without any text | [`Dot`](?path=/docs/components-dot--docs) |',
|
|
34
|
+
'| Clickable action that does something | [`Button`](?path=/docs/components-button--docs) |',
|
|
35
|
+
'',
|
|
36
|
+
'```tsx',
|
|
37
|
+
'// BAD — Tags are non-interactive. Adding onClick to a Tag creates confusion with Pills.',
|
|
38
|
+
'<Tag onClick={handleClick}>Maths</Tag>',
|
|
39
|
+
'',
|
|
40
|
+
'// GOOD — use Pill for filter behaviour',
|
|
41
|
+
'<Pill onClick={handleClick} text="Maths" />',
|
|
42
|
+
'```',
|
|
43
|
+
'',
|
|
44
|
+
'---',
|
|
45
|
+
'',
|
|
46
|
+
'### Design guidance',
|
|
47
|
+
'',
|
|
48
|
+
'- **Max 3 words per label** (Confluence design spec). Labels truncate with ellipsis when a max-width is hit.',
|
|
49
|
+
' Keep labels short and specific.',
|
|
50
|
+
'- **Establish a semantic colour mapping in your app** — the design system does NOT map semantic warning/destructive',
|
|
51
|
+
' tokens to Tag colours. Tags describe *what something is*, not its urgency. Define your own mapping and',
|
|
52
|
+
' apply it consistently (e.g. `salmon` = "Pupil Premium", `purple` = "SEN").',
|
|
53
|
+
'- **Limit distinct colours visible at once** — Confluence guidance: colour variety hurts scannability.',
|
|
54
|
+
' Use neutral for most tags; reach for colour only when it adds genuine semantic value.',
|
|
55
|
+
'- **Dot prefix** — use `slotStart={<Dot colour="purple" />}` for the canonical dot-prefix pattern.',
|
|
56
|
+
' This replaces the old `dotColour` prop (removed in v2). `Dot` is `aria-hidden` when no `label` is passed.',
|
|
57
|
+
'- **Component tokens** — background colours are controlled by `--tag-category-{colour}-color-background`',
|
|
58
|
+
' (one of 8 pale -050 washes). Height, radius, and spacing via `--tag-height`, `--tag-radius`,',
|
|
59
|
+
' `--tag-spacing-horizontal`, and `--tag-spacing-gap-horizontal`. Selected state colours via',
|
|
60
|
+
' `--tag-selected-color-background` and `--tag-selected-color-border`.',
|
|
61
|
+
].join('\n');
|
|
62
|
+
const DEVELOPER_NOTES = [
|
|
63
|
+
'### Critical patterns',
|
|
64
|
+
'',
|
|
65
|
+
'- **`selected` overrides `color`** — when `selected={true}`, the background and border come from',
|
|
66
|
+
' `--tag-selected-color-*` tokens regardless of the `color` prop. Selection is a higher-priority signal',
|
|
67
|
+
' than categorical colour. Text reverts to neutral regardless of the base colour.',
|
|
68
|
+
'- **`onRemove` is for composite chip contexts only** — Combobox selection pills, multi-value inputs.',
|
|
69
|
+
' Never render a removable Tag in a plain metadata list; Confluence design spec is explicit that',
|
|
70
|
+
' regular tags are non-interactive.',
|
|
71
|
+
'- **`removeButtonTabIndex={-1}` in roving-tabindex parents** — when the parent (e.g. Combobox) manages',
|
|
72
|
+
' keyboard focus via a roving tabindex, pass `removeButtonTabIndex={-1}` so users do not tab through',
|
|
73
|
+
' every individual X button. The parent moves focus programmatically instead.',
|
|
74
|
+
'- **`removeLabel` must always be overridden** — the default `"Remove"` is meaningless in isolation.',
|
|
75
|
+
' Always pass something like `removeLabel="Remove Year 7"` so screen reader users understand what',
|
|
76
|
+
' will be removed.',
|
|
77
|
+
'',
|
|
78
|
+
'---',
|
|
79
|
+
'',
|
|
80
|
+
'### Accessibility',
|
|
81
|
+
'',
|
|
82
|
+
'- **Non-removable Tag** — renders as `<span>` with no `role` and no `tabIndex`. This is correct:',
|
|
83
|
+
' non-interactive elements should not be focusable.',
|
|
84
|
+
'- **Removable Tag** — the remove `<button>` carries an `aria-label` (default `"Remove"`).',
|
|
85
|
+
' **Always override this** with something meaningful: `removeLabel="Remove Year 7"`.',
|
|
86
|
+
'- **`e.stopPropagation()`** — the remove click stops bubbling so composite parents (Combobox,',
|
|
87
|
+
' custom chip inputs) do not receive the click as a selection event.',
|
|
88
|
+
'- **Inside Combobox / composite parent** — pass `removeButtonTabIndex={-1}` and let the parent',
|
|
89
|
+
' manage focus. Without this, tab navigation through a group of Tags visits every X button',
|
|
90
|
+
' individually, which is jarring and verbose for keyboard users.',
|
|
91
|
+
'',
|
|
92
|
+
'---',
|
|
93
|
+
'',
|
|
94
|
+
'### TypeScript types',
|
|
95
|
+
'',
|
|
96
|
+
'```ts',
|
|
97
|
+
"import { Tag } from '@arbor-education/design-system.components';",
|
|
98
|
+
'',
|
|
99
|
+
'function MyTag(props: Tag.Props) { ... }',
|
|
100
|
+
'```',
|
|
101
|
+
'',
|
|
102
|
+
'| Type | Description |',
|
|
103
|
+
'|---|---|',
|
|
104
|
+
'| `Tag.Props` | Full props interface |',
|
|
105
|
+
"| `Tag.Color` | `'neutral' \\| 'orange' \\| 'blue' \\| 'green' \\| 'purple' \\| 'teal' \\| 'salmon' \\| 'yellow'` |",
|
|
106
|
+
].join('\n');
|
|
107
|
+
const RELATED_COMPONENTS = [
|
|
108
|
+
'## Related components',
|
|
109
|
+
'',
|
|
110
|
+
'[Badge](?path=/docs/components-badge--docs) · [Pill](?path=/docs/components-pill--docs) · [Dot](?path=/docs/components-dot--docs) · [Combobox](?path=/docs/components-combobox--docs) · [Icon](?path=/docs/components-icon--docs)',
|
|
111
|
+
].join('\n');
|
|
112
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
113
|
+
function TagDocsPage() {
|
|
114
|
+
return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(DocHeading, { children: "Interactive example" }), _jsx(Markdown, { children: PROPS_INTRO }), _jsx(DocPrimary, {}), _jsx(Controls, {}), _jsx(DocHeading, { children: "Usage guidance" }), _jsx(Markdown, { children: USAGE_GUIDANCE }), _jsx(DocHeading, { children: "Developer notes" }), _jsx(Markdown, { children: DEVELOPER_NOTES }), _jsx(DocHeading, { children: "Examples" }), _jsx(Stories, { title: "" }), _jsx(Markdown, { children: RELATED_COMPONENTS })] }));
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Data
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
const allColours = ['neutral', 'orange', 'blue', 'green', 'purple', 'teal', 'salmon', 'yellow'];
|
|
120
|
+
// Realistic Arbor content mapped to each colour for AllColours story
|
|
121
|
+
const colourLabels = {
|
|
122
|
+
neutral: 'Year 7',
|
|
123
|
+
orange: 'At risk',
|
|
124
|
+
blue: 'Maths',
|
|
125
|
+
green: 'On track',
|
|
126
|
+
purple: 'SEN',
|
|
127
|
+
teal: 'EAL',
|
|
128
|
+
salmon: 'Pupil Premium',
|
|
129
|
+
yellow: 'Active',
|
|
130
|
+
};
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Meta
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
6
134
|
const meta = {
|
|
7
|
-
tags: ['autodocs'],
|
|
8
135
|
title: 'Components/Tag',
|
|
9
136
|
component: Tag,
|
|
137
|
+
tags: ['autodocs'],
|
|
10
138
|
parameters: {
|
|
11
139
|
docs: {
|
|
12
|
-
|
|
13
|
-
|
|
140
|
+
page: TagDocsPage,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
argTypes: {
|
|
144
|
+
children: {
|
|
145
|
+
control: 'text',
|
|
146
|
+
description: [
|
|
147
|
+
'The label text rendered inside the Tag. Keep to **3 words or fewer** (Confluence spec) —',
|
|
148
|
+
'longer labels truncate with ellipsis when a max-width is applied.',
|
|
149
|
+
'Examples: `"Year 7"`, `"SEN"`, `"Pupil Premium"`, `"At risk"`, `"Autumn term"`.',
|
|
150
|
+
].join(' '),
|
|
151
|
+
table: {
|
|
152
|
+
type: { summary: 'React.ReactNode' },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
color: {
|
|
156
|
+
control: 'select',
|
|
157
|
+
options: allColours,
|
|
158
|
+
description: [
|
|
159
|
+
'The colour variant applied to the Tag background and border.',
|
|
160
|
+
'8 options: `neutral` (default), `orange`, `blue`, `green`, `purple`, `teal`, `salmon`, `yellow`.',
|
|
161
|
+
'Background colours are controlled by `--tag-category-{colour}-color-background` tokens (pale -050 washes).',
|
|
162
|
+
'Establish a semantic mapping in your app and apply it consistently — e.g. `salmon` = Pupil Premium,',
|
|
163
|
+
'`purple` = SEN. **Note:** when `selected` is `true`, this prop is visually overridden by the selected tokens.',
|
|
164
|
+
].join(' '),
|
|
165
|
+
table: {
|
|
166
|
+
type: { summary: "'neutral' | 'orange' | 'blue' | 'green' | 'purple' | 'teal' | 'salmon' | 'yellow'" },
|
|
167
|
+
defaultValue: { summary: "'neutral'" },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
selected: {
|
|
171
|
+
control: 'boolean',
|
|
172
|
+
description: [
|
|
173
|
+
'When `true`, overrides the `color` prop: background and border come from `--tag-selected-color-background`',
|
|
174
|
+
'and `--tag-selected-color-border`, and text reverts to neutral.',
|
|
175
|
+
'Selection is a higher-priority signal than categorical colour — use `selected` to indicate',
|
|
176
|
+
'that this tag represents the currently active category or chosen filter value.',
|
|
177
|
+
].join(' '),
|
|
178
|
+
table: {
|
|
179
|
+
type: { summary: 'boolean' },
|
|
180
|
+
defaultValue: { summary: 'false' },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
slotStart: {
|
|
184
|
+
control: false,
|
|
185
|
+
description: [
|
|
186
|
+
'Leading slot rendered before the label. Accepts any `ReactNode` — canonical use is',
|
|
187
|
+
'`slotStart={<Dot colour="purple" />}` for the dot-prefix pattern (replaces the old `dotColour` prop).',
|
|
188
|
+
'Also accepts `<Icon name="user" size={12} />` for an icon prefix.',
|
|
189
|
+
'`Dot` is `aria-hidden` when no `label` prop is passed.',
|
|
190
|
+
].join(' '),
|
|
191
|
+
table: {
|
|
192
|
+
type: { summary: 'React.ReactNode' },
|
|
193
|
+
defaultValue: { summary: 'undefined' },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
slotEnd: {
|
|
197
|
+
control: false,
|
|
198
|
+
description: [
|
|
199
|
+
'Trailing slot rendered after the label (and before the remove button when `onRemove` is set).',
|
|
200
|
+
'Accepts any `ReactNode` — common uses: a chevron icon for expandable tags, a count badge, a status dot.',
|
|
201
|
+
'Example: `slotEnd={<Icon name="chevron-down" size={12} />}`.',
|
|
202
|
+
].join(' '),
|
|
203
|
+
table: {
|
|
204
|
+
type: { summary: 'React.ReactNode' },
|
|
205
|
+
defaultValue: { summary: 'undefined' },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
onRemove: {
|
|
209
|
+
control: false,
|
|
210
|
+
description: [
|
|
211
|
+
'Callback fired when the remove button is clicked. When provided, renders a `<button>` with an X icon',
|
|
212
|
+
'at the end of the Tag. **For composite chip contexts only** (Combobox selection pills, multi-value inputs).',
|
|
213
|
+
'Do not use on standalone Tags in metadata lists — Confluence spec says regular Tags are non-interactive.',
|
|
214
|
+
'The click has `e.stopPropagation()` applied to prevent bubbling into composite parents.',
|
|
215
|
+
].join(' '),
|
|
216
|
+
table: {
|
|
217
|
+
type: { summary: '() => void' },
|
|
218
|
+
defaultValue: { summary: 'undefined' },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
removeLabel: {
|
|
222
|
+
control: 'text',
|
|
223
|
+
description: [
|
|
224
|
+
'`aria-label` applied to the remove button. Default is `"Remove"` — **always override this**',
|
|
225
|
+
'with something meaningful, e.g. `removeLabel="Remove Year 7"` or `removeLabel="Remove Alice Johnson"`.',
|
|
226
|
+
'The default alone is meaningless to screen reader users who cannot see which tag they are removing.',
|
|
227
|
+
].join(' '),
|
|
228
|
+
table: {
|
|
229
|
+
type: { summary: 'string' },
|
|
230
|
+
defaultValue: { summary: '"Remove"' },
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
removeButtonTabIndex: {
|
|
234
|
+
control: 'select',
|
|
235
|
+
options: [0, -1],
|
|
236
|
+
description: [
|
|
237
|
+
'Tab index on the remove button.',
|
|
238
|
+
'`0` (default) — remove button is in the natural tab order. Correct for standalone removable tags.',
|
|
239
|
+
'`-1` — remove button is excluded from tab order; the parent manages focus via a roving tabindex.',
|
|
240
|
+
'Pass `-1` when the Tag lives inside a Combobox or other composite parent that manages keyboard',
|
|
241
|
+
'navigation, to avoid jarring tab-through of every individual X button.',
|
|
242
|
+
].join(' '),
|
|
243
|
+
table: {
|
|
244
|
+
type: { summary: '0 | -1' },
|
|
245
|
+
defaultValue: { summary: '0' },
|
|
14
246
|
},
|
|
15
247
|
},
|
|
16
248
|
},
|
|
17
249
|
};
|
|
18
250
|
export default meta;
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Helper: attach a per-story description to docs
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
19
254
|
const withDescription = (story, description) => ({
|
|
20
255
|
...story,
|
|
21
256
|
parameters: {
|
|
@@ -28,34 +263,245 @@ const withDescription = (story, description) => ({
|
|
|
28
263
|
},
|
|
29
264
|
},
|
|
30
265
|
});
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// Template components for composition / stateful stories
|
|
268
|
+
// (Named components avoid react-hooks lint issues — the react-hooks ESLint
|
|
269
|
+
// plugin is NOT configured in this project, so do NOT add eslint-disable.)
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
const AllColoursTemplate = () => (_jsx("div", { style: {
|
|
272
|
+
padding: 'var(--spacing-xlarge)',
|
|
273
|
+
display: 'flex',
|
|
274
|
+
flexWrap: 'wrap',
|
|
275
|
+
gap: 'var(--spacing-small)',
|
|
276
|
+
alignItems: 'center',
|
|
277
|
+
}, children: allColours.map(color => (_jsx(Tag, { color: color, children: colourLabels[color] }, color))) }));
|
|
278
|
+
const RemovableInteractiveTemplate = () => {
|
|
279
|
+
const [tags, setTags] = useState(['Year 7', 'SEN', 'Pupil Premium', 'Active']);
|
|
280
|
+
return (_jsx("div", { style: {
|
|
281
|
+
padding: 'var(--spacing-xlarge)',
|
|
282
|
+
display: 'flex',
|
|
283
|
+
flexWrap: 'wrap',
|
|
284
|
+
gap: 'var(--spacing-small)',
|
|
285
|
+
alignItems: 'center',
|
|
286
|
+
}, children: tags.length === 0
|
|
287
|
+
? (_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "All tags dismissed." }))
|
|
288
|
+
: (tags.map(tag => (_jsx(Tag, { onRemove: () => setTags(prev => prev.filter(t => t !== tag)), removeLabel: `Remove ${tag}`, children: tag }, tag)))) }));
|
|
289
|
+
};
|
|
290
|
+
const LongLabelTruncationTemplate = () => (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)' }, children: [_jsx("p", { className: "ds-text", style: { marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }, children: "Container constrained to 12rem \u2014 label truncates with ellipsis:" }), _jsx("div", { style: { maxWidth: '12rem' }, children: _jsx(Tag, { children: "Currently being assessed for additional learning needs" }) })] }));
|
|
291
|
+
const ContentGuidelinesTemplate = () => (_jsxs("div", { style: {
|
|
292
|
+
padding: 'var(--spacing-xlarge)',
|
|
293
|
+
display: 'flex',
|
|
294
|
+
flexDirection: 'column',
|
|
295
|
+
gap: 'var(--spacing-large)',
|
|
296
|
+
}, children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Good (3 words or fewer)" }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)', alignItems: 'center' }, children: [_jsx(Tag, { children: "Maths" }), _jsx(Tag, { children: "Year 7" }), _jsx(Tag, { children: "Autumn term" }), _jsx(Tag, { children: "Assessed" }), _jsx(Tag, { children: "SEN" })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }, children: [_jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "Too long (4 or more words)" }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)', alignItems: 'center' }, children: [_jsx(Tag, { children: "Currently being assessed" }), _jsx(Tag, { children: "Due for review next week" }), _jsx(Tag, { children: "Permission required from guardian" })] })] })] }));
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Stories
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
31
300
|
export const Default = withDescription({
|
|
32
|
-
args: {
|
|
33
|
-
|
|
301
|
+
args: {
|
|
302
|
+
children: 'Year 7',
|
|
303
|
+
color: 'neutral',
|
|
304
|
+
selected: false,
|
|
305
|
+
removeLabel: 'Remove',
|
|
306
|
+
removeButtonTabIndex: 0,
|
|
307
|
+
},
|
|
308
|
+
render: args => _jsx(Tag, { ...args }),
|
|
309
|
+
}, 'The interactive canvas story — every prop is wired to the **Controls** panel below. Use the controls to explore colours, selected state, and the remove button pattern. The `slotStart`, `slotEnd`, and `onRemove` props accept `ReactNode`/function values that cannot be set via controls; see the dedicated stories below for those patterns.');
|
|
34
310
|
export const WithColour = withDescription({
|
|
35
|
-
args: {
|
|
36
|
-
|
|
311
|
+
args: {
|
|
312
|
+
children: 'Mathematics',
|
|
313
|
+
color: 'blue',
|
|
314
|
+
},
|
|
315
|
+
parameters: {
|
|
316
|
+
docs: {
|
|
317
|
+
source: {
|
|
318
|
+
language: 'tsx',
|
|
319
|
+
code: `
|
|
320
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
321
|
+
|
|
322
|
+
function WithColourExample() {
|
|
323
|
+
return <Tag color="blue">Mathematics</Tag>;
|
|
324
|
+
}
|
|
325
|
+
export default WithColourExample;
|
|
326
|
+
`.trim(),
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
}, 'Applies the `blue` colour variant. Each of the 8 colour options uses a distinct pale wash background from the `--tag-category-{colour}-color-background` token family. Establish a consistent semantic colour mapping in your app — for example `blue` = subject labels, `salmon` = Pupil Premium — and apply it everywhere.');
|
|
331
|
+
export const AllColours = withDescription({
|
|
332
|
+
render: AllColoursTemplate,
|
|
333
|
+
parameters: {
|
|
334
|
+
docs: {
|
|
335
|
+
source: {
|
|
336
|
+
language: 'tsx',
|
|
337
|
+
code: `
|
|
338
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
339
|
+
|
|
340
|
+
const colours = ['neutral', 'orange', 'blue', 'green', 'purple', 'teal', 'salmon', 'yellow'] as const;
|
|
341
|
+
const labels: Record<typeof colours[number], string> = {
|
|
342
|
+
neutral: 'Year 7',
|
|
343
|
+
orange: 'At risk',
|
|
344
|
+
blue: 'Maths',
|
|
345
|
+
green: 'On track',
|
|
346
|
+
purple: 'SEN',
|
|
347
|
+
teal: 'EAL',
|
|
348
|
+
salmon: 'Pupil Premium',
|
|
349
|
+
yellow: 'Active',
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
function AllColoursExample() {
|
|
353
|
+
return (
|
|
354
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
|
|
355
|
+
{colours.map(color => (
|
|
356
|
+
<Tag key={color} color={color}>
|
|
357
|
+
{labels[color]}
|
|
358
|
+
</Tag>
|
|
359
|
+
))}
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
export default AllColoursExample;
|
|
364
|
+
`.trim(),
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
}, 'All 8 colour variants side by side with realistic Arbor content for each colour. Confluence guidance: limit the number of distinct colours visible at once — colour variety hurts scannability. Use `neutral` as the default and reach for colour only when it adds genuine semantic value to the label.');
|
|
37
369
|
export const Selected = withDescription({
|
|
38
|
-
args: {
|
|
39
|
-
|
|
370
|
+
args: {
|
|
371
|
+
children: 'Form 7B',
|
|
372
|
+
color: 'blue',
|
|
373
|
+
selected: true,
|
|
374
|
+
},
|
|
375
|
+
parameters: {
|
|
376
|
+
docs: {
|
|
377
|
+
source: {
|
|
378
|
+
language: 'tsx',
|
|
379
|
+
code: `
|
|
380
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
381
|
+
|
|
382
|
+
function SelectedExample() {
|
|
383
|
+
return <Tag color="blue" selected>Form 7B</Tag>;
|
|
384
|
+
}
|
|
385
|
+
export default SelectedExample;
|
|
386
|
+
`.trim(),
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
}, 'The `selected` prop overrides `color` — background and border come from `--tag-selected-color-background` and `--tag-selected-color-border`, and text reverts to neutral regardless of the `color` prop. Selection is a higher-priority signal than categorical colour; use it to indicate the currently active tag in a selectable cluster.');
|
|
40
391
|
export const WithDot = withDescription({
|
|
41
392
|
args: {
|
|
42
|
-
children: '
|
|
43
|
-
slotStart: _jsx(Dot, { colour: "
|
|
393
|
+
children: 'Pupil Premium',
|
|
394
|
+
slotStart: _jsx(Dot, { colour: "salmon" }),
|
|
44
395
|
},
|
|
45
|
-
|
|
396
|
+
parameters: {
|
|
397
|
+
docs: {
|
|
398
|
+
source: {
|
|
399
|
+
language: 'tsx',
|
|
400
|
+
code: `
|
|
401
|
+
import { Dot, Tag } from '@arbor-education/design-system.components';
|
|
402
|
+
|
|
403
|
+
function WithDotExample() {
|
|
404
|
+
return <Tag slotStart={<Dot colour="salmon" />}>Pupil Premium</Tag>;
|
|
405
|
+
}
|
|
406
|
+
export default WithDotExample;
|
|
407
|
+
`.trim(),
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
}, 'The canonical dot-prefix pattern using `slotStart={<Dot colour="salmon" />}`. This replaces the old `dotColour` prop removed in v2. `Dot` renders as a decorative `<span aria-hidden="true">` when no `label` prop is passed — it adds no screen reader noise while providing a strong visual colour signal alongside the text.');
|
|
46
412
|
export const WithIcon = withDescription({
|
|
47
413
|
args: {
|
|
48
414
|
children: 'Alice Johnson',
|
|
49
415
|
slotStart: _jsx(Icon, { name: "user", size: 12 }),
|
|
50
416
|
},
|
|
51
|
-
|
|
417
|
+
parameters: {
|
|
418
|
+
docs: {
|
|
419
|
+
source: {
|
|
420
|
+
language: 'tsx',
|
|
421
|
+
code: `
|
|
422
|
+
import { Icon, Tag } from '@arbor-education/design-system.components';
|
|
423
|
+
|
|
424
|
+
function WithIconExample() {
|
|
425
|
+
return <Tag slotStart={<Icon name="user" size={12} />}>Alice Johnson</Tag>;
|
|
426
|
+
}
|
|
427
|
+
export default WithIconExample;
|
|
428
|
+
`.trim(),
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
}, 'An icon prefix via `slotStart`. Size `12` matches the Tag label font size for optical balance. Icons in `slotStart` are presentational — add `screenReaderText` to `Icon` only if the icon conveys information not already present in the Tag label.');
|
|
433
|
+
export const WithSlotEnd = withDescription({
|
|
434
|
+
args: {
|
|
435
|
+
children: 'Autumn term',
|
|
436
|
+
slotEnd: _jsx(Icon, { name: "chevron-down", size: 12 }),
|
|
437
|
+
},
|
|
438
|
+
parameters: {
|
|
439
|
+
docs: {
|
|
440
|
+
source: {
|
|
441
|
+
language: 'tsx',
|
|
442
|
+
code: `
|
|
443
|
+
import { Icon, Tag } from '@arbor-education/design-system.components';
|
|
444
|
+
|
|
445
|
+
function WithSlotEndExample() {
|
|
446
|
+
return <Tag slotEnd={<Icon name="chevron-down" size={12} />}>Autumn term</Tag>;
|
|
447
|
+
}
|
|
448
|
+
export default WithSlotEndExample;
|
|
449
|
+
`.trim(),
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
}, 'A trailing slot via `slotEnd`. Common uses: a chevron icon for expandable Tag menus, a count indicator, or a secondary status dot. The slot renders between the label and the remove button (when present), so the remove button is always the last interactive element in the Tab sequence.');
|
|
454
|
+
export const WithBothSlots = withDescription({
|
|
455
|
+
args: {
|
|
456
|
+
children: 'Form 7B',
|
|
457
|
+
slotStart: _jsx(Dot, { colour: "teal" }),
|
|
458
|
+
slotEnd: _jsx(Icon, { name: "chevron-down", size: 12 }),
|
|
459
|
+
},
|
|
460
|
+
parameters: {
|
|
461
|
+
docs: {
|
|
462
|
+
source: {
|
|
463
|
+
language: 'tsx',
|
|
464
|
+
code: `
|
|
465
|
+
import { Dot, Icon, Tag } from '@arbor-education/design-system.components';
|
|
466
|
+
|
|
467
|
+
function WithBothSlotsExample() {
|
|
468
|
+
return (
|
|
469
|
+
<Tag
|
|
470
|
+
slotStart={<Dot colour="teal" />}
|
|
471
|
+
slotEnd={<Icon name="chevron-down" size={12} />}
|
|
472
|
+
>
|
|
473
|
+
Form 7B
|
|
474
|
+
</Tag>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
export default WithBothSlotsExample;
|
|
478
|
+
`.trim(),
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
}, 'Both `slotStart` and `slotEnd` present simultaneously. The layout order is: `slotStart` | label | `slotEnd` | remove button (if `onRemove` is set). Slots are independently optional — compose freely based on the information density your context requires.');
|
|
52
483
|
export const Removable = withDescription({
|
|
53
484
|
args: {
|
|
54
|
-
children: '
|
|
485
|
+
children: 'Year 7',
|
|
55
486
|
onRemove: fn(),
|
|
56
|
-
removeLabel: 'Remove
|
|
487
|
+
removeLabel: 'Remove Year 7',
|
|
488
|
+
},
|
|
489
|
+
parameters: {
|
|
490
|
+
docs: {
|
|
491
|
+
source: {
|
|
492
|
+
language: 'tsx',
|
|
493
|
+
code: `
|
|
494
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
495
|
+
|
|
496
|
+
function RemovableExample() {
|
|
497
|
+
return <Tag onRemove={() => {}} removeLabel="Remove Year 7">Year 7</Tag>;
|
|
498
|
+
}
|
|
499
|
+
export default RemovableExample;
|
|
500
|
+
`.trim(),
|
|
501
|
+
},
|
|
502
|
+
},
|
|
57
503
|
},
|
|
58
|
-
}, '
|
|
504
|
+
}, '`onRemove` renders a remove `<button>` with an X icon. **Use only in composite chip contexts** (Combobox selection pills, multi-value inputs) — Confluence spec says standalone Tags in metadata lists are non-interactive. The `removeLabel` prop sets the `aria-label` on the button; always override the default `"Remove"` with something specific like `"Remove Year 7"` so screen reader users know exactly what will be removed.');
|
|
59
505
|
export const RemovableWithIcon = withDescription({
|
|
60
506
|
args: {
|
|
61
507
|
children: 'Alice Johnson',
|
|
@@ -63,15 +509,122 @@ export const RemovableWithIcon = withDescription({
|
|
|
63
509
|
onRemove: fn(),
|
|
64
510
|
removeLabel: 'Remove Alice Johnson',
|
|
65
511
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
512
|
+
parameters: {
|
|
513
|
+
docs: {
|
|
514
|
+
source: {
|
|
515
|
+
language: 'tsx',
|
|
516
|
+
code: `
|
|
517
|
+
import { Icon, Tag } from '@arbor-education/design-system.components';
|
|
518
|
+
|
|
519
|
+
function RemovableWithIconExample() {
|
|
520
|
+
return (
|
|
521
|
+
<Tag
|
|
522
|
+
slotStart={<Icon name="user" size={12} />}
|
|
523
|
+
onRemove={() => {}}
|
|
524
|
+
removeLabel="Remove Alice Johnson"
|
|
525
|
+
>
|
|
526
|
+
Alice Johnson
|
|
527
|
+
</Tag>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
export default RemovableWithIconExample;
|
|
531
|
+
`.trim(),
|
|
532
|
+
},
|
|
533
|
+
},
|
|
71
534
|
},
|
|
72
|
-
}, '
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
535
|
+
}, 'Leading icon combined with a remove button — the chip-style composition pattern. The layout order is: icon slot | label | remove button. This pattern is common in people-picker Combobox components where selected individuals are shown as dismissible chips.');
|
|
536
|
+
export const RemovableInteractive = withDescription({
|
|
537
|
+
render: RemovableInteractiveTemplate,
|
|
538
|
+
parameters: {
|
|
539
|
+
docs: {
|
|
540
|
+
source: {
|
|
541
|
+
language: 'tsx',
|
|
542
|
+
code: `
|
|
543
|
+
import { useState } from 'react';
|
|
544
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
545
|
+
|
|
546
|
+
function RemovableTags() {
|
|
547
|
+
const [tags, setTags] = useState(['Year 7', 'SEN', 'Pupil Premium', 'Active']);
|
|
548
|
+
|
|
549
|
+
return (
|
|
550
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
|
|
551
|
+
{tags.length === 0 ? (
|
|
552
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>All tags dismissed.</p>
|
|
553
|
+
) : (
|
|
554
|
+
tags.map(tag => (
|
|
555
|
+
<Tag
|
|
556
|
+
key={tag}
|
|
557
|
+
onRemove={() => setTags(prev => prev.filter(t => t !== tag))}
|
|
558
|
+
removeLabel={\`Remove \${tag}\`}
|
|
559
|
+
>
|
|
560
|
+
{tag}
|
|
561
|
+
</Tag>
|
|
562
|
+
))
|
|
563
|
+
)}
|
|
564
|
+
</div>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
export default RemovableTags;
|
|
568
|
+
`.trim(),
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
}, 'A live interactive example: click any X button to dismiss that Tag. When all tags are dismissed, a "All tags dismissed." message appears. This demonstrates the stateful chip pattern used inside Combobox and multi-select inputs. Each `removeLabel` is specific to the tag content — never leave it as the default `"Remove"`.');
|
|
573
|
+
export const LongLabelTruncation = withDescription({
|
|
574
|
+
render: LongLabelTruncationTemplate,
|
|
575
|
+
parameters: {
|
|
576
|
+
docs: {
|
|
577
|
+
source: {
|
|
578
|
+
language: 'tsx',
|
|
579
|
+
code: `
|
|
580
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
581
|
+
|
|
582
|
+
function LongLabelTruncationExample() {
|
|
583
|
+
return (
|
|
584
|
+
<div style={{ maxWidth: '12rem' }}>
|
|
585
|
+
<Tag>Currently being assessed for additional learning needs</Tag>
|
|
586
|
+
</div>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
export default LongLabelTruncationExample;
|
|
590
|
+
`.trim(),
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
}, 'Tag labels truncate with ellipsis when the available width is exceeded. This story demonstrates the behaviour in a container constrained to `12rem`. The ellipsis reinforces the Confluence design spec: keep Tag labels to **3 words or fewer**. If you find a label needing truncation, shorten it.');
|
|
595
|
+
export const ContentGuidelines = withDescription({
|
|
596
|
+
render: ContentGuidelinesTemplate,
|
|
597
|
+
parameters: {
|
|
598
|
+
docs: {
|
|
599
|
+
source: {
|
|
600
|
+
language: 'tsx',
|
|
601
|
+
code: `
|
|
602
|
+
import { Tag } from '@arbor-education/design-system.components';
|
|
603
|
+
|
|
604
|
+
function ContentGuidelinesExample() {
|
|
605
|
+
return (
|
|
606
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)', padding: 'var(--spacing-xlarge)' }}>
|
|
607
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
|
|
608
|
+
{/* Good — 3 words or fewer (Confluence spec) */}
|
|
609
|
+
<Tag>Maths</Tag>
|
|
610
|
+
<Tag>Year 7</Tag>
|
|
611
|
+
<Tag>Autumn term</Tag>
|
|
612
|
+
<Tag>Assessed</Tag>
|
|
613
|
+
<Tag>SEN</Tag>
|
|
614
|
+
</div>
|
|
615
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
|
|
616
|
+
{/* Too long — 4 or more words. Use a tooltip or description instead. */}
|
|
617
|
+
<Tag>Currently being assessed</Tag>
|
|
618
|
+
<Tag>Due for review next week</Tag>
|
|
619
|
+
<Tag>Permission required from guardian</Tag>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
export default ContentGuidelinesExample;
|
|
625
|
+
`.trim(),
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
}, 'A side-by-side comparison of good (3 words or fewer) and too-long (4+ words) labels. The Confluence design spec is clear: Tags must be brief. Labels like "Due for review next week" belong in a tooltip or description, not a Tag. If you need more than 3 words to describe the attribute, reconsider the information architecture.');
|
|
77
630
|
//# sourceMappingURL=Tag.stories.js.map
|