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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
  2. package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
  3. package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
  4. package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
  5. package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
  6. package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
  7. package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
  8. package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
  9. package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
  10. package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
  11. package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
  12. package/.gather/gather.yaml +9 -0
  13. package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
  14. package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
  15. package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
  16. package/.gather/skills/analyze-design/meta.md +4 -0
  17. package/{.claude → .gather}/skills/create-page/README.md +5 -0
  18. package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
  19. package/.gather/skills/create-page/meta.md +4 -0
  20. package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
  21. package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
  22. package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
  23. package/.gather/skills/map-legacy/meta.md +4 -0
  24. package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
  25. package/.gather/skills/migrate-page/meta.md +4 -0
  26. package/.gather/skills/write-stories/README.md +157 -0
  27. package/.gather/skills/write-stories/SKILL.md +841 -0
  28. package/.gather/skills/write-stories/meta.md +4 -0
  29. package/.ralph/storybook-upgrade/knowledge.md +308 -0
  30. package/.ralph/storybook-upgrade/prd.json +777 -0
  31. package/.ralph/storybook-upgrade/progress.md +342 -0
  32. package/.storybook/DocsTemplate.tsx +122 -0
  33. package/.storybook/preview.ts +40 -0
  34. package/.stylelintignore +2 -0
  35. package/CHANGELOG.md +6 -0
  36. package/{.claude/component-library.md → component-library.md} +27 -10
  37. package/dist/components/badge/Badge.stories.d.ts +85 -6
  38. package/dist/components/badge/Badge.stories.d.ts.map +1 -1
  39. package/dist/components/badge/Badge.stories.js +626 -27
  40. package/dist/components/badge/Badge.stories.js.map +1 -1
  41. package/dist/components/banner/Banner.stories.d.ts +129 -63
  42. package/dist/components/banner/Banner.stories.d.ts.map +1 -1
  43. package/dist/components/banner/Banner.stories.js +855 -39
  44. package/dist/components/banner/Banner.stories.js.map +1 -1
  45. package/dist/components/button/Button.stories.d.ts +148 -8
  46. package/dist/components/button/Button.stories.d.ts.map +1 -1
  47. package/dist/components/button/Button.stories.js +1089 -80
  48. package/dist/components/button/Button.stories.js.map +1 -1
  49. package/dist/components/dot/Dot.stories.d.ts +46 -11
  50. package/dist/components/dot/Dot.stories.d.ts.map +1 -1
  51. package/dist/components/dot/Dot.stories.js +504 -15
  52. package/dist/components/dot/Dot.stories.js.map +1 -1
  53. package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
  54. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
  55. package/dist/components/dropdown/Dropdown.stories.js +769 -17
  56. package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
  57. package/dist/components/formField/FormField.stories.d.ts +95 -35
  58. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  59. package/dist/components/formField/FormField.stories.js +1174 -69
  60. package/dist/components/formField/FormField.stories.js.map +1 -1
  61. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
  62. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  63. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
  64. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  65. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
  66. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
  67. package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
  68. package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
  69. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
  70. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  71. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
  72. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  73. package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
  74. package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
  75. package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
  76. package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
  77. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
  78. package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
  79. package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
  80. package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
  81. package/dist/components/heading/Heading.stories.d.ts +449 -50
  82. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  83. package/dist/components/heading/Heading.stories.js +536 -60
  84. package/dist/components/heading/Heading.stories.js.map +1 -1
  85. package/dist/components/icon/Icon.stories.d.ts +81 -10
  86. package/dist/components/icon/Icon.stories.d.ts.map +1 -1
  87. package/dist/components/icon/Icon.stories.js +979 -8
  88. package/dist/components/icon/Icon.stories.js.map +1 -1
  89. package/dist/components/pill/Pill.stories.d.ts +71 -19
  90. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  91. package/dist/components/pill/Pill.stories.js +573 -14
  92. package/dist/components/pill/Pill.stories.js.map +1 -1
  93. package/dist/components/progress/Progress.stories.d.ts +75 -298
  94. package/dist/components/progress/Progress.stories.d.ts.map +1 -1
  95. package/dist/components/progress/Progress.stories.js +449 -52
  96. package/dist/components/progress/Progress.stories.js.map +1 -1
  97. package/dist/components/separator/Separator.stories.d.ts +58 -5
  98. package/dist/components/separator/Separator.stories.d.ts.map +1 -1
  99. package/dist/components/separator/Separator.stories.js +443 -4
  100. package/dist/components/separator/Separator.stories.js.map +1 -1
  101. package/dist/components/tag/Tag.stories.d.ts +116 -5
  102. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  103. package/dist/components/tag/Tag.stories.js +581 -28
  104. package/dist/components/tag/Tag.stories.js.map +1 -1
  105. package/dist/index.css +8 -1
  106. package/dist/index.css.map +1 -1
  107. package/eslint.config.mts +5 -1
  108. package/package.json +3 -3
  109. package/src/components/badge/Badge.stories.tsx +869 -42
  110. package/src/components/banner/Banner.stories.tsx +1081 -63
  111. package/src/components/button/Button.stories.tsx +1394 -99
  112. package/src/components/dot/Dot.stories.tsx +723 -32
  113. package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
  114. package/src/components/formField/FormField.stories.tsx +1522 -105
  115. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
  116. package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
  117. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
  118. package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
  119. package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
  120. package/src/components/heading/Heading.stories.tsx +752 -120
  121. package/src/components/icon/Icon.stories.tsx +1446 -12
  122. package/src/components/pill/Pill.stories.tsx +867 -21
  123. package/src/components/progress/Progress.stories.tsx +625 -58
  124. package/src/components/separator/Separator.stories.tsx +730 -8
  125. package/src/components/separator/separator.scss +12 -3
  126. package/src/components/tag/Tag.stories.tsx +755 -53
  127. package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
  128. package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
  129. package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
  130. package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
  131. package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
  132. package/.claude/figma-assessment-7154-58899.md +0 -404
  133. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
  134. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
  135. package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
  136. package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
  137. package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
  138. /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
  139. /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
  140. /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
  141. /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
  142. /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
@@ -1,26 +1,733 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
3
+ import { useState } from 'react';
4
+ import { fn } from 'storybook/test';
2
5
  import { CheckboxInput } from './CheckboxInput';
3
6
  import { CheckboxGroup } from './CheckboxGroup';
7
+ // ---------------------------------------------------------------------------
8
+ // Component description — built as joined arrays to avoid no-useless-escape
9
+ // on backtick code spans inside template literals.
10
+ // ---------------------------------------------------------------------------
11
+ const DESCRIPTION_INTRO = [
12
+ 'The **CheckboxInput** is the Arbor design system checkbox control — a labelled, accessible toggle',
13
+ 'for multi-select forms, boolean settings, and the canonical "select all" + indeterminate pattern.',
14
+ ].join(' ');
15
+ const USAGE_GUIDANCE = [
16
+ '### When to use',
17
+ '',
18
+ '- **Multi-select lists** — when zero, one, or many options can be chosen simultaneously',
19
+ ' (e.g. "Which year groups should receive this communication?")',
20
+ '- **Single boolean settings** in a form that has a Save or Apply button',
21
+ ' (e.g. "Include past students in attendance report")',
22
+ '- **"Select all" with indeterminate state** — a parent checkbox that reflects the selection',
23
+ ' state of a group of child checkboxes',
24
+ '',
25
+ '---',
26
+ '',
27
+ '### When NOT to use',
28
+ '',
29
+ '| Instead of CheckboxInput... | Use... | Why |',
30
+ '|---|---|---|',
31
+ '| Instant-apply boolean setting | [`Toggle`](?path=/docs/components-toggle--docs) | Toggle applies immediately; checkbox needs a Save action |',
32
+ '| Single-select from a list | [`RadioButtonInput`](?path=/docs/components-formfield-inputs-radiobutton--docs) | Radio communicates mutual exclusivity to screen readers |',
33
+ '| Free text entry | `TextInput` or `TextArea` | Checkboxes are binary; they cannot represent arbitrary text |',
34
+ '',
35
+ '---',
36
+ '',
37
+ '### Design guidance',
38
+ '',
39
+ '- **Click target is the entire row** — the label element wraps both the control and the label',
40
+ ' text, so clicking anywhere on the row toggles the checkbox.',
41
+ '- **Focus ring changes shape with label presence** — with a label, the focus ring is a pill',
42
+ ' (`--checkbox-radius`, 8px) wrapping the whole row. Without a label,',
43
+ ' it is a tight square (`--checkbox-input-control-radius`, 4px) around the control only.',
44
+ '- **Label text** — always phrase labels positively.',
45
+ ' Write "Include past students in attendance report" not "Do not exclude students who have left".',
46
+ '- **Disabled checkboxes** — always accompany a disabled control with a tooltip or helper text',
47
+ ' explaining why it cannot be interacted with. The user deserves to know.',
48
+ '- **Group checkboxes in a fieldset** — when presenting a list of options, wrap them in',
49
+ ' `CheckboxGroup` (which uses `Fieldset` internally) so the legend/heading is announced',
50
+ ' as the group label by screen readers.',
51
+ '- **The border is NOT a CSS border** — it is an inset `box-shadow`.',
52
+ ' Overriding the `border` property will have no visible effect.',
53
+ ].join('\n');
54
+ const DEVELOPER_NOTES = [
55
+ '### Critical gotchas',
56
+ '',
57
+ '#### 1. `checked` defaults to `false` — this component is always controlled',
58
+ 'The component destructures `checked = false`, so React treats it as a controlled input from',
59
+ 'the moment it mounts. You must always pair `checked` with an `onChange` handler.',
60
+ 'Without `onChange`, the checkbox will be visually frozen — toggling will not update it.',
61
+ '',
62
+ '```tsx',
63
+ '// BAD — no onChange; checkbox appears frozen',
64
+ '<CheckboxInput checked={isChecked} label="Include past students" />',
65
+ '',
66
+ '// GOOD — paired with onChange',
67
+ '<CheckboxInput',
68
+ ' checked={isChecked}',
69
+ ' onChange={(e) => setIsChecked(e.target.checked)}',
70
+ ' label="Include past students"',
71
+ '/>',
72
+ '```',
73
+ '',
74
+ '#### 2. `aria-checked="mixed"` is set automatically — do not spread it manually',
75
+ 'When `indeterminate={true}`, the component sets `aria-checked="mixed"` on the real input.',
76
+ 'Never pass `aria-checked` via `...rest` — the `{...rest}` spread comes after the explicit `aria-checked` in the JSX,',
77
+ 'so your value would override the component\'s logic and break the indeterminate announcement for screen readers.',
78
+ '',
79
+ '#### 3. No `forwardRef` — consumers cannot attach a `ref` to CheckboxInput',
80
+ 'The component uses an internal `ref` for the indeterminate DOM API. There is no `forwardRef`',
81
+ 'wrapper, so `ref` passed from outside will not reach the underlying `<input>` element.',
82
+ '',
83
+ '#### 4. Label absence changes focus ring shape',
84
+ 'Without a `label` prop, the container has no `.ds-checkbox-label__text` child, so the CSS',
85
+ '`:focus-within:not(:has(.ds-checkbox-label__text))` selector fires and applies the tighter',
86
+ '`--checkbox-input-control-radius` (4px square) instead of the pill `--checkbox-radius` (8px).',
87
+ '',
88
+ '---',
89
+ '',
90
+ '### Accessibility',
91
+ '',
92
+ '- **The custom visual span is `aria-hidden="true"`** — all accessibility information flows',
93
+ ' through the real (visually hidden) native `<input>`. The custom span is purely decorative.',
94
+ '- **Keyboard interaction** — Tab moves focus between checkboxes; Space toggles the focused',
95
+ ' checkbox. This is native browser behaviour and requires no custom JavaScript.',
96
+ '- **`aria-checked="mixed"`** is automatically applied when `indeterminate={true}`,',
97
+ ' correctly communicating the partial-selection state to screen readers.',
98
+ '- **Group labelling** — wrap related checkboxes in `CheckboxGroup`. The Fieldset legend',
99
+ ' is announced as the group name by screen readers, giving vital context to each option.',
100
+ '- **Disabled explanation** — the `disabled` attribute removes the element from the tab order.',
101
+ ' Always provide a tooltip or visible helper text explaining why the checkbox is disabled,',
102
+ ' so non-keyboard users also receive the explanation.',
103
+ '',
104
+ '---',
105
+ '',
106
+ '### TypeScript types',
107
+ '',
108
+ '```ts',
109
+ "import { CheckboxInput, CheckboxGroup } from '@arbor-education/design-system.components';",
110
+ '',
111
+ '// Prop type shorthand',
112
+ 'function MyField(props: CheckboxInput.Props) { ... }',
113
+ 'function MyGroup(props: CheckboxGroup.Props) { ... }',
114
+ '```',
115
+ '',
116
+ '| Type | Description |',
117
+ '|---|---|',
118
+ '| `CheckboxInput.Props` | `indeterminate`, `label`, plus all `InputHTMLAttributes<HTMLInputElement>` |',
119
+ '| `CheckboxGroup.Props` | `options: CheckboxInputProps[]`, plus all `Fieldset.Props` (`legend`, `HTMLFieldSetElement` attrs) |',
120
+ ].join('\n');
121
+ const RELATED_COMPONENTS = [
122
+ '## Related components',
123
+ '',
124
+ '[Toggle](?path=/docs/components-toggle--docs)',
125
+ '· [RadioButtonInput](?path=/docs/components-formfield-inputs-radiobutton--docs)',
126
+ '· [FormField](?path=/docs/components-formfield--docs)',
127
+ '· [Fieldset](?path=/docs/components-formfield-fieldset--docs)',
128
+ ].join('\n');
129
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
130
+ // ---------------------------------------------------------------------------
131
+ // Docs page
132
+ // ---------------------------------------------------------------------------
133
+ function CheckboxInputDocsPage() {
134
+ 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 })] }));
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Meta
138
+ // ---------------------------------------------------------------------------
4
139
  const meta = {
5
140
  title: 'Components/FormField/Inputs/Checkbox',
6
141
  component: CheckboxInput,
7
- };
8
- export const Default = {
9
142
  parameters: {
10
143
  layout: 'centered',
144
+ docs: {
145
+ page: CheckboxInputDocsPage,
146
+ },
11
147
  },
12
148
  tags: ['autodocs'],
13
149
  args: {
14
- disabled: false,
150
+ onChange: fn(),
151
+ },
152
+ argTypes: {
153
+ checked: {
154
+ control: 'boolean',
155
+ description: [
156
+ 'Whether the checkbox is checked. The component is always controlled — `checked` defaults to `false`',
157
+ 'in the component destructuring, so React treats it as a controlled input immediately.',
158
+ 'You must always pair this prop with an `onChange` handler, otherwise the checkbox will appear frozen.',
159
+ ].join(' '),
160
+ table: {
161
+ type: { summary: 'boolean' },
162
+ defaultValue: { summary: 'false' },
163
+ },
164
+ },
165
+ indeterminate: {
166
+ control: 'boolean',
167
+ description: [
168
+ 'Displays the checkbox in the indeterminate (partially-selected) state, shown as a minus icon.',
169
+ 'When `true`, the component sets `aria-checked="mixed"` automatically and applies the indeterminate',
170
+ 'CSS state via the DOM API (`inputRef.current.indeterminate = true`). This cannot be set as a raw HTML attribute.',
171
+ 'The canonical use case is a "Select all" checkbox that reflects partial child-item selection.',
172
+ ].join(' '),
173
+ table: {
174
+ type: { summary: 'boolean' },
175
+ defaultValue: { summary: 'false' },
176
+ },
177
+ },
178
+ label: {
179
+ control: 'text',
180
+ description: [
181
+ 'Visible label text rendered next to the control. When present, the label is wrapped in a',
182
+ '`.ds-checkbox-label__text` span and the click target expands to the entire row.',
183
+ 'When absent, only the control square is rendered — the focus ring also tightens from a pill',
184
+ '(`--checkbox-radius`, 8px) to a square (`--checkbox-input-control-radius`, 4px).',
185
+ 'Label text should be positively phrased: "Include past students" not "Do not exclude past students".',
186
+ ].join(' '),
187
+ table: {
188
+ type: { summary: 'string' },
189
+ },
190
+ },
191
+ disabled: {
192
+ control: 'boolean',
193
+ description: [
194
+ 'Disables the checkbox. Applies muted styling and removes it from the tab order.',
195
+ 'The value is NOT submitted in a form when disabled.',
196
+ 'Always accompany a disabled checkbox with a tooltip or helper text explaining why it is disabled.',
197
+ ].join(' '),
198
+ table: {
199
+ type: { summary: 'boolean' },
200
+ defaultValue: { summary: 'false' },
201
+ },
202
+ },
203
+ onChange: {
204
+ description: [
205
+ 'Change event handler — required for controlled usage. Fires whenever the user toggles the checkbox.',
206
+ 'Access the new value via `e.target.checked`. Without this prop, a controlled checkbox will appear frozen.',
207
+ ].join(' '),
208
+ control: false,
209
+ table: {
210
+ type: { summary: 'ChangeEventHandler<HTMLInputElement>' },
211
+ },
212
+ },
213
+ id: {
214
+ control: 'text',
215
+ description: [
216
+ 'HTML `id` attribute — spread onto the real hidden `<input>` element.',
217
+ 'Required by `CheckboxGroup` (passed through `options`) to key list items.',
218
+ 'Also useful for associating external labels or `aria-describedby` targets.',
219
+ ].join(' '),
220
+ table: {
221
+ type: { summary: 'string' },
222
+ },
223
+ },
224
+ className: {
225
+ control: 'text',
226
+ description: [
227
+ 'Additional CSS class names applied to the outer `<label>` container element',
228
+ '(the `.ds-checkbox-input__container` wrapper), not to the control square itself.',
229
+ ].join(' '),
230
+ table: {
231
+ type: { summary: 'string' },
232
+ },
233
+ },
234
+ },
235
+ };
236
+ export default meta;
237
+ // ---------------------------------------------------------------------------
238
+ // Helper: attach a per-story description to docs
239
+ // ---------------------------------------------------------------------------
240
+ const withDescription = (story, description) => ({
241
+ ...story,
242
+ parameters: {
243
+ ...story.parameters,
244
+ docs: {
245
+ ...story.parameters?.docs,
246
+ description: {
247
+ story: Array.isArray(description) ? description.join(' ') : description,
248
+ },
249
+ },
250
+ },
251
+ });
252
+ // ---------------------------------------------------------------------------
253
+ // Stateful template components
254
+ // Named components avoid hooks-in-callbacks lint issues (react-hooks plugin
255
+ // is NOT configured in this project — do not add eslint-disable comments).
256
+ // ---------------------------------------------------------------------------
257
+ const InteractiveControlledTemplate = () => {
258
+ const [attendanceSummary, setAttendanceSummary] = useState(false);
259
+ const [absenceAlerts, setAbsenceAlerts] = useState(true);
260
+ const [lateArrivals, setLateArrivals] = useState(false);
261
+ return (_jsxs("div", { style: { maxWidth: '28rem', padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: "Notification preferences \u2014 all three are independently controlled via local state." }), _jsx(CheckboxInput, { id: "attendance-summary", checked: attendanceSummary, onChange: e => setAttendanceSummary(e.target.checked), label: "Include past students in attendance report" }), _jsx(CheckboxInput, { id: "absence-alerts", checked: absenceAlerts, onChange: e => setAbsenceAlerts(e.target.checked), label: "Send absence alert to parents" }), _jsx(CheckboxInput, { id: "late-arrivals", checked: lateArrivals, onChange: e => setLateArrivals(e.target.checked), label: "Notify form tutor of late arrivals" }), _jsxs("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: ["State: attendance summary", ' ', attendanceSummary ? 'on' : 'off', ", absence alerts", ' ', absenceAlerts ? 'on' : 'off', ", late arrival notifications", ' ', lateArrivals ? 'on' : 'off', "."] })] }));
262
+ };
263
+ const PUPIL_OPTIONS = [
264
+ { id: 'pupil-maya', label: 'Maya Osei-Bonsu' },
265
+ { id: 'pupil-rahul', label: 'Rahul Kapoor' },
266
+ { id: 'pupil-freya', label: 'Freya Lindqvist' },
267
+ ];
268
+ const SelectAllWithIndeterminateTemplate = () => {
269
+ const [selected, setSelected] = useState(new Set(['pupil-maya']));
270
+ const allChecked = selected.size === PUPIL_OPTIONS.length;
271
+ const someChecked = selected.size > 0 && !allChecked;
272
+ const handleSelectAll = (e) => {
273
+ if (e.target.checked) {
274
+ setSelected(new Set(PUPIL_OPTIONS.map(o => o.id)));
275
+ }
276
+ else {
277
+ setSelected(new Set());
278
+ }
279
+ };
280
+ const handleOption = (id) => (e) => {
281
+ setSelected((prev) => {
282
+ const next = new Set(prev);
283
+ if (e.target.checked) {
284
+ next.add(id);
285
+ }
286
+ else {
287
+ next.delete(id);
288
+ }
289
+ return next;
290
+ });
291
+ };
292
+ return (_jsxs("div", { style: { maxWidth: '28rem', padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: "Generate end-of-term report for:" }), _jsx(CheckboxInput, { id: "select-all-pupils", checked: allChecked, indeterminate: someChecked, onChange: handleSelectAll, label: "All pupils in Year 9 Elm" }), _jsx("div", { style: { paddingLeft: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }, children: PUPIL_OPTIONS.map(option => (_jsx(CheckboxInput, { id: option.id, checked: selected.has(option.id), onChange: handleOption(option.id), label: option.label }, option.id))) }), _jsxs("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: [selected.size, ' ', "of", PUPIL_OPTIONS.length, ' ', "pupils selected."] })] }));
293
+ };
294
+ const CheckboxGroupStoryTemplate = () => (_jsx("div", { style: { maxWidth: '28rem', padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxGroup, { legend: "Absence alert recipients", options: [
295
+ { id: 'recipient-parent', label: 'Parent / guardian', checked: true, onChange: fn() },
296
+ { id: 'recipient-tutor', label: 'Form tutor', checked: false, onChange: fn() },
297
+ { id: 'recipient-head', label: 'Head of year', checked: false, onChange: fn() },
298
+ ] }) }));
299
+ const CheckboxGroupDisabledTemplate = () => (_jsx("div", { style: { maxWidth: '28rem', padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxGroup, { legend: "Report export options (locked for current academic year)", disabled: true, options: [
300
+ { id: 'export-pdf', label: 'Generate end-of-term report', checked: true, onChange: fn() },
301
+ { id: 'export-csv', label: 'Export attendance data as CSV', checked: false, onChange: fn() },
302
+ { id: 'export-email', label: 'Email summary to head of year', checked: false, onChange: fn() },
303
+ ] }) }));
304
+ // ---------------------------------------------------------------------------
305
+ // Stories
306
+ // ---------------------------------------------------------------------------
307
+ export const Default = {
308
+ args: {
15
309
  checked: false,
16
310
  indeterminate: false,
17
- label: 'Label text',
311
+ label: 'Include past students in attendance report',
312
+ disabled: false,
18
313
  },
314
+ render: args => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { ...args }) })),
19
315
  };
20
- export const CheckboxGroupStory = {
21
- render: () => {
22
- return (_jsx(CheckboxGroup, { legend: "Checkbox group", options: [{ id: 'option1', label: 'Option 1' }, { id: 'option2', label: 'Option 2' }, { id: 'option3', label: 'Option 3' }] }));
316
+ export const Checked = withDescription({
317
+ render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { id: "checked-example", checked: true, onChange: fn(), label: "Send absence alert to parents" }) })),
318
+ parameters: {
319
+ docs: {
320
+ source: {
321
+ language: 'tsx',
322
+ code: `
323
+ import { CheckboxInput } from '@arbor-education/design-system.components';
324
+
325
+ function CheckedExample() {
326
+ return (
327
+ <CheckboxInput
328
+ id="absence-alerts"
329
+ checked
330
+ onChange={(e) => console.log(e.target.checked)}
331
+ label="Send absence alert to parents"
332
+ />
333
+ );
334
+ }
335
+
336
+ export default CheckedExample;
337
+ `.trim(),
338
+ },
339
+ },
23
340
  },
24
- };
25
- export default meta;
341
+ }, 'The checked state — a blue filled control with a white check icon. The `checked` prop is always controlled; pair it with an `onChange` handler so the user can toggle it.');
342
+ export const Indeterminate = withDescription({
343
+ render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { id: "indeterminate-example", checked: false, indeterminate: true, onChange: fn(), label: "All pupils in Year 9 Elm" }) })),
344
+ parameters: {
345
+ docs: {
346
+ source: {
347
+ language: 'tsx',
348
+ code: `
349
+ import { CheckboxInput } from '@arbor-education/design-system.components';
350
+
351
+ function IndeterminateExample() {
352
+ // Indeterminate is the "some but not all" state — typically used on a
353
+ // "Select all" parent checkbox when child items are partially selected.
354
+ return (
355
+ <CheckboxInput
356
+ id="select-all"
357
+ checked={false}
358
+ indeterminate
359
+ onChange={(e) => console.log('Select all toggled:', e.target.checked)}
360
+ label="All pupils in Year 9 Elm"
361
+ />
362
+ );
363
+ }
364
+
365
+ export default IndeterminateExample;
366
+ `.trim(),
367
+ },
368
+ },
369
+ },
370
+ }, [
371
+ 'The indeterminate state — a blue filled control with a minus icon, signalling partial selection.',
372
+ '`indeterminate` is set via the DOM API (`inputRef.current.indeterminate = true`) inside a `useEffect`.',
373
+ 'You just pass `indeterminate={true}` as a prop — the component handles the DOM wiring.',
374
+ 'The component also automatically sets `aria-checked="mixed"` so screen readers correctly',
375
+ 'announce the partial-selection state. See the **SelectAllWithIndeterminate** story for the',
376
+ 'full interactive "select all" pattern.',
377
+ ].join(' '));
378
+ export const DisabledUnchecked = withDescription({
379
+ render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { id: "disabled-unchecked", checked: false, disabled: true, onChange: fn(), label: "Lock timetable for current academic year" }) })),
380
+ parameters: {
381
+ docs: {
382
+ source: {
383
+ language: 'tsx',
384
+ code: `
385
+ import { CheckboxInput } from '@arbor-education/design-system.components';
386
+
387
+ function DisabledUncheckedExample() {
388
+ // Disabled — pair with a Tooltip explaining why so users understand the constraint.
389
+ return (
390
+ <CheckboxInput
391
+ id="lock-timetable"
392
+ checked={false}
393
+ disabled
394
+ onChange={() => {}}
395
+ label="Lock timetable for current academic year"
396
+ />
397
+ );
398
+ }
399
+
400
+ export default DisabledUncheckedExample;
401
+ `.trim(),
402
+ },
403
+ },
404
+ },
405
+ }, [
406
+ 'A disabled, unchecked checkbox. The control renders with muted styling and the cursor changes to',
407
+ '`not-allowed`. The field is removed from the tab order and its value is not submitted in a form.',
408
+ 'Always accompany a disabled checkbox with a tooltip or helper text that explains why it is',
409
+ 'disabled — for example: "This option is unavailable until the academic year has been published."',
410
+ ].join(' '));
411
+ export const DisabledChecked = withDescription({
412
+ render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { id: "disabled-checked", checked: true, disabled: true, onChange: fn(), label: "Lock timetable for current academic year" }) })),
413
+ parameters: {
414
+ docs: {
415
+ source: {
416
+ language: 'tsx',
417
+ code: `
418
+ import { CheckboxInput } from '@arbor-education/design-system.components';
419
+
420
+ function DisabledCheckedExample() {
421
+ return (
422
+ <CheckboxInput
423
+ id="lock-timetable"
424
+ checked
425
+ disabled
426
+ onChange={() => {}}
427
+ label="Lock timetable for current academic year"
428
+ />
429
+ );
430
+ }
431
+
432
+ export default DisabledCheckedExample;
433
+ `.trim(),
434
+ },
435
+ },
436
+ },
437
+ }, 'A disabled, already-checked checkbox. This is appropriate for read-only settings that are currently active but cannot be changed — for example, a system-enforced policy. Always provide a tooltip or helper text explaining why the option cannot be toggled.');
438
+ export const DisabledIndeterminate = withDescription({
439
+ render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsx(CheckboxInput, { id: "disabled-indeterminate", checked: false, indeterminate: true, disabled: true, onChange: fn(), label: "All pupils in Year 9 Elm" }) })),
440
+ parameters: {
441
+ docs: {
442
+ source: {
443
+ language: 'tsx',
444
+ code: `
445
+ import { CheckboxInput } from '@arbor-education/design-system.components';
446
+
447
+ function DisabledIndeterminateExample() {
448
+ return (
449
+ <CheckboxInput
450
+ id="select-all"
451
+ checked={false}
452
+ indeterminate
453
+ disabled
454
+ onChange={() => {}}
455
+ label="All pupils in Year 9 Elm"
456
+ />
457
+ );
458
+ }
459
+
460
+ export default DisabledIndeterminateExample;
461
+ `.trim(),
462
+ },
463
+ },
464
+ },
465
+ }, 'A disabled indeterminate checkbox — the partial-selection state rendered in muted styling. The minus icon uses `--checkbox-input-control-indeterminate-disabled-color-icon`, a distinct token from the enabled indeterminate icon colour, so the disabled state is visually differentiated.');
466
+ export const WithoutLabel = withDescription({
467
+ render: () => (_jsxs("div", { style: { padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)', alignItems: 'flex-start' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: "Unchecked (no label \u2014 tight square focus ring):" }), _jsx(CheckboxInput, { id: "no-label-unchecked", checked: false, onChange: fn(), "aria-label": "Include in report" }), _jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: "Checked (no label):" }), _jsx(CheckboxInput, { id: "no-label-checked", checked: true, onChange: fn(), "aria-label": "Include in report" })] })),
468
+ parameters: {
469
+ docs: {
470
+ source: {
471
+ language: 'tsx',
472
+ code: `
473
+ import { CheckboxInput } from '@arbor-education/design-system.components';
474
+
475
+ function WithoutLabelExample() {
476
+ // When there is no visible label, always provide aria-label for screen readers.
477
+ // The focus ring becomes a tight square (--checkbox-input-control-radius: 4px)
478
+ // instead of the wider pill (--checkbox-radius: 8px).
479
+ return (
480
+ <CheckboxInput
481
+ id="row-select"
482
+ checked={isSelected}
483
+ onChange={(e) => setIsSelected(e.target.checked)}
484
+ aria-label="Select this student row"
485
+ />
486
+ );
487
+ }
488
+
489
+ export default WithoutLabelExample;
490
+ `.trim(),
491
+ },
492
+ },
493
+ },
494
+ }, [
495
+ 'CheckboxInput without a `label` prop — renders just the control square with no adjacent text.',
496
+ 'This is typical in data table row-selection columns where the column header provides the context.',
497
+ 'When omitting `label`, always pass `aria-label` or `aria-labelledby` so screen reader users',
498
+ 'understand what the checkbox controls.',
499
+ 'The focus ring also changes: without `.ds-checkbox-label__text` present, the CSS selector',
500
+ '`:focus-within:not(:has(.ds-checkbox-label__text))` applies `--checkbox-input-control-radius`',
501
+ '(4px square) instead of the pill `--checkbox-radius` (8px).',
502
+ ].join(' '));
503
+ export const AllStates = withDescription({
504
+ render: () => (_jsxs("div", { style: { maxWidth: '32rem', padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)' }, children: "Enabled states:" }), _jsx(CheckboxInput, { id: "all-unchecked", checked: false, onChange: fn(), label: "Unchecked \u2014 default state" }), _jsx(CheckboxInput, { id: "all-checked", checked: true, onChange: fn(), label: "Checked \u2014 option selected" }), _jsx(CheckboxInput, { id: "all-indeterminate", checked: false, indeterminate: true, onChange: fn(), label: "Indeterminate \u2014 partial selection" }), _jsx("p", { style: { margin: 0, color: 'var(--color-grey-600)', fontSize: 'var(--font-size-2-13)', marginTop: 'var(--spacing-small)' }, children: "Disabled states:" }), _jsx(CheckboxInput, { id: "all-disabled-unchecked", checked: false, disabled: true, onChange: fn(), label: "Disabled unchecked" }), _jsx(CheckboxInput, { id: "all-disabled-checked", checked: true, disabled: true, onChange: fn(), label: "Disabled checked" }), _jsx(CheckboxInput, { id: "all-disabled-indeterminate", checked: false, indeterminate: true, disabled: true, onChange: fn(), label: "Disabled indeterminate" })] })),
505
+ parameters: {
506
+ docs: {
507
+ source: {
508
+ language: 'tsx',
509
+ code: `
510
+ import { CheckboxInput } from '@arbor-education/design-system.components';
511
+
512
+ function AllStatesExample() {
513
+ // Static display of all six visual states.
514
+ // In a real application every CheckboxInput needs an onChange handler.
515
+ return (
516
+ <>
517
+ {/* Enabled */}
518
+ <CheckboxInput id="s1" checked={false} onChange={() => {}} label="Unchecked" />
519
+ <CheckboxInput id="s2" checked onChange={() => {}} label="Checked" />
520
+ <CheckboxInput id="s3" checked={false} indeterminate onChange={() => {}} label="Indeterminate" />
521
+ {/* Disabled */}
522
+ <CheckboxInput id="s4" checked={false} disabled onChange={() => {}} label="Disabled unchecked" />
523
+ <CheckboxInput id="s5" checked disabled onChange={() => {}} label="Disabled checked" />
524
+ <CheckboxInput id="s6" checked={false} indeterminate disabled onChange={() => {}} label="Disabled indeterminate" />
525
+ </>
526
+ );
527
+ }
528
+
529
+ export default AllStatesExample;
530
+ `.trim(),
531
+ },
532
+ },
533
+ },
534
+ }, 'A side-by-side reference showing all six visual states: unchecked, checked, indeterminate, and their three disabled counterparts. The icon colour token changes between enabled and disabled variants — checked-disabled uses `--checkbox-input-control-checked-disabled-color-icon` while indeterminate-disabled uses `--checkbox-input-control-indeterminate-disabled-color-icon`.');
535
+ export const InteractiveControlled = withDescription({
536
+ render: () => _jsx(InteractiveControlledTemplate, {}),
537
+ parameters: {
538
+ docs: {
539
+ source: {
540
+ language: 'tsx',
541
+ code: `
542
+ import { useState } from 'react';
543
+ import { CheckboxInput } from '@arbor-education/design-system.components';
544
+
545
+ function NotificationPreferencesExample() {
546
+ const [attendanceSummary, setAttendanceSummary] = useState(false);
547
+ const [absenceAlerts, setAbsenceAlerts] = useState(true);
548
+ const [lateArrivals, setLateArrivals] = useState(false);
549
+
550
+ return (
551
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }}>
552
+ <CheckboxInput
553
+ id="attendance-summary"
554
+ checked={attendanceSummary}
555
+ onChange={(e) => setAttendanceSummary(e.target.checked)}
556
+ label="Include past students in attendance report"
557
+ />
558
+ <CheckboxInput
559
+ id="absence-alerts"
560
+ checked={absenceAlerts}
561
+ onChange={(e) => setAbsenceAlerts(e.target.checked)}
562
+ label="Send absence alert to parents"
563
+ />
564
+ <CheckboxInput
565
+ id="late-arrivals"
566
+ checked={lateArrivals}
567
+ onChange={(e) => setLateArrivals(e.target.checked)}
568
+ label="Notify form tutor of late arrivals"
569
+ />
570
+ </div>
571
+ );
572
+ }
573
+
574
+ export default NotificationPreferencesExample;
575
+ `.trim(),
576
+ },
577
+ },
578
+ },
579
+ }, [
580
+ 'Three independently controlled checkboxes wired to local `useState`. Each checkbox manages its own boolean state.',
581
+ 'This is the fundamental controlled-checkbox pattern in React: `checked` reflects state, `onChange`',
582
+ 'reads `e.target.checked` and calls the setter. Try toggling each option — the status text below updates in real time.',
583
+ ].join(' '));
584
+ export const SelectAllWithIndeterminate = withDescription({
585
+ render: () => _jsx(SelectAllWithIndeterminateTemplate, {}),
586
+ parameters: {
587
+ docs: {
588
+ source: {
589
+ language: 'tsx',
590
+ code: `
591
+ import { useState } from 'react';
592
+ import { CheckboxInput } from '@arbor-education/design-system.components';
593
+
594
+ const PUPILS = [
595
+ { id: 'pupil-maya', label: 'Maya Osei-Bonsu' },
596
+ { id: 'pupil-rahul', label: 'Rahul Kapoor' },
597
+ { id: 'pupil-freya', label: 'Freya Lindqvist' },
598
+ ];
599
+
600
+ function SelectAllExample() {
601
+ const [selected, setSelected] = useState(new Set(['pupil-maya']));
602
+
603
+ const allChecked = selected.size === PUPILS.length;
604
+ const someChecked = selected.size > 0 && !allChecked;
605
+
606
+ const handleSelectAll = (e) => {
607
+ setSelected(e.target.checked ? new Set(PUPILS.map((p) => p.id)) : new Set());
608
+ };
609
+
610
+ const handlePupil = (id) => (e) => {
611
+ setSelected((prev) => {
612
+ const next = new Set(prev);
613
+ e.target.checked ? next.add(id) : next.delete(id);
614
+ return next;
615
+ });
616
+ };
617
+
618
+ return (
619
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }}>
620
+ {/* Parent "Select all" checkbox */}
621
+ <CheckboxInput
622
+ id="select-all"
623
+ checked={allChecked}
624
+ indeterminate={someChecked}
625
+ onChange={handleSelectAll}
626
+ label="All pupils in Year 9 Elm"
627
+ />
628
+ {/* Child checkboxes */}
629
+ <div style={{ paddingLeft: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-medium)' }}>
630
+ {PUPILS.map((pupil) => (
631
+ <CheckboxInput
632
+ key={pupil.id}
633
+ id={pupil.id}
634
+ checked={selected.has(pupil.id)}
635
+ onChange={handlePupil(pupil.id)}
636
+ label={pupil.label}
637
+ />
638
+ ))}
639
+ </div>
640
+ </div>
641
+ );
642
+ }
643
+
644
+ export default SelectAllExample;
645
+ `.trim(),
646
+ },
647
+ },
648
+ },
649
+ }, [
650
+ 'The canonical indeterminate pattern: a "Select all" parent checkbox whose state is derived from its children.',
651
+ '`allChecked` is `true` when every item is in the set; `someChecked` drives `indeterminate`',
652
+ 'when at least one (but not all) items are selected.',
653
+ 'Try checking and unchecking individual pupils — the parent checkbox transitions between',
654
+ 'unchecked → indeterminate → checked automatically.',
655
+ 'Clicking the parent when indeterminate selects all; clicking again deselects all.',
656
+ ].join(' '));
657
+ export const CheckboxGroupStory = withDescription({
658
+ name: 'CheckboxGroup',
659
+ render: () => _jsx(CheckboxGroupStoryTemplate, {}),
660
+ parameters: {
661
+ docs: {
662
+ source: {
663
+ language: 'tsx',
664
+ code: `
665
+ import { CheckboxGroup } from '@arbor-education/design-system.components';
666
+
667
+ function AbsenceAlertRecipientsExample() {
668
+ return (
669
+ <CheckboxGroup
670
+ legend="Absence alert recipients"
671
+ options={[
672
+ { id: 'recipient-parent', label: 'Parent / guardian', checked: true, onChange: () => {} },
673
+ { id: 'recipient-tutor', label: 'Form tutor', checked: false, onChange: () => {} },
674
+ { id: 'recipient-head', label: 'Head of year', checked: false, onChange: () => {} },
675
+ ]}
676
+ />
677
+ );
678
+ }
679
+
680
+ export default AbsenceAlertRecipientsExample;
681
+ `.trim(),
682
+ },
683
+ },
684
+ },
685
+ }, [
686
+ '`CheckboxGroup` composes `Fieldset` and a list of `CheckboxInput` instances. Pass your options as an',
687
+ 'array of `CheckboxInput.Props` objects to the `options` prop — each object is spread onto a',
688
+ '`CheckboxInput`, so every `CheckboxInputProps` is valid here (including `checked`, `onChange`,',
689
+ '`disabled`, `indeterminate`, and `label`).',
690
+ 'The `legend` prop renders a `<legend>` inside the `<fieldset>` — screen readers announce it as',
691
+ 'the group label before each option, giving essential context.',
692
+ 'The vertical gap between options is controlled by the token',
693
+ '`--checkbox-or-radio-button-group-spacing-gap-vertical` inside Fieldset — do not add your own gap wrapper.',
694
+ ].join(' '));
695
+ export const CheckboxGroupDisabled = withDescription({
696
+ name: 'CheckboxGroup — Fieldset-level disabled',
697
+ render: () => _jsx(CheckboxGroupDisabledTemplate, {}),
698
+ parameters: {
699
+ docs: {
700
+ source: {
701
+ language: 'tsx',
702
+ code: `
703
+ import { CheckboxGroup } from '@arbor-education/design-system.components';
704
+
705
+ function LockedReportOptionsExample() {
706
+ // Pass disabled directly on CheckboxGroup — it flows through Fieldset's
707
+ // HTMLFieldSetElement props and disables ALL child checkboxes at once.
708
+ return (
709
+ <CheckboxGroup
710
+ legend="Report export options (locked for current academic year)"
711
+ disabled
712
+ options={[
713
+ { id: 'export-pdf', label: 'Generate end-of-term report', checked: true, onChange: () => {} },
714
+ { id: 'export-csv', label: 'Export attendance data as CSV', checked: false, onChange: () => {} },
715
+ { id: 'export-email', label: 'Email summary to head of year', checked: false, onChange: () => {} },
716
+ ]}
717
+ />
718
+ );
719
+ }
720
+
721
+ export default LockedReportOptionsExample;
722
+ `.trim(),
723
+ },
724
+ },
725
+ },
726
+ }, [
727
+ 'Passing `disabled` to `CheckboxGroup` flows through the underlying `<fieldset>` element,',
728
+ 'which disables ALL child `<input>` elements at once — native HTML behaviour, zero extra JavaScript.',
729
+ 'This is the correct pattern for locking an entire group (e.g. a settings panel that is read-only',
730
+ 'until the academic year is published). Individual options can still be disabled via their own',
731
+ '`disabled` prop inside `options` if you need partial disabling.',
732
+ ].join(' '));
26
733
  //# sourceMappingURL=CheckboxInput.stories.js.map