@arbor-education/design-system.components 0.25.4 → 0.25.6

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 (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/fileUpload/FileUpload.d.ts +21 -0
  3. package/dist/components/fileUpload/FileUpload.d.ts.map +1 -0
  4. package/dist/components/fileUpload/FileUpload.js +23 -0
  5. package/dist/components/fileUpload/FileUpload.js.map +1 -0
  6. package/dist/components/fileUpload/FileUpload.stories.d.ts +71 -0
  7. package/dist/components/fileUpload/FileUpload.stories.d.ts.map +1 -0
  8. package/dist/components/fileUpload/FileUpload.stories.js +736 -0
  9. package/dist/components/fileUpload/FileUpload.stories.js.map +1 -0
  10. package/dist/components/fileUpload/FileUpload.test.d.ts +2 -0
  11. package/dist/components/fileUpload/FileUpload.test.d.ts.map +1 -0
  12. package/dist/components/fileUpload/FileUpload.test.js +99 -0
  13. package/dist/components/fileUpload/FileUpload.test.js.map +1 -0
  14. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +1 -1
  15. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  16. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
  17. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +14 -15
  18. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
  19. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js +11 -0
  20. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -1
  21. package/dist/components/grid/Grid.d.ts +14 -0
  22. package/dist/components/grid/Grid.d.ts.map +1 -0
  23. package/dist/components/grid/Grid.js +11 -0
  24. package/dist/components/grid/Grid.js.map +1 -0
  25. package/dist/components/grid/Grid.stories.d.ts +52 -0
  26. package/dist/components/grid/Grid.stories.d.ts.map +1 -0
  27. package/dist/components/grid/Grid.stories.js +675 -0
  28. package/dist/components/grid/Grid.stories.js.map +1 -0
  29. package/dist/components/grid/Grid.test.d.ts +2 -0
  30. package/dist/components/grid/Grid.test.d.ts.map +1 -0
  31. package/dist/components/grid/Grid.test.js +59 -0
  32. package/dist/components/grid/Grid.test.js.map +1 -0
  33. package/dist/components/grid/GridColumn.d.ts +11 -0
  34. package/dist/components/grid/GridColumn.d.ts.map +1 -0
  35. package/dist/components/grid/GridColumn.js +7 -0
  36. package/dist/components/grid/GridColumn.js.map +1 -0
  37. package/dist/components/grid/GridRow.d.ts +10 -0
  38. package/dist/components/grid/GridRow.d.ts.map +1 -0
  39. package/dist/components/grid/GridRow.js +7 -0
  40. package/dist/components/grid/GridRow.js.map +1 -0
  41. package/dist/components/icon/allowedIcons.d.ts +1 -0
  42. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  43. package/dist/components/icon/allowedIcons.js +2 -1
  44. package/dist/components/icon/allowedIcons.js.map +1 -1
  45. package/dist/index.css +252 -0
  46. package/dist/index.css.map +1 -1
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -0
  50. package/dist/index.js.map +1 -1
  51. package/package.json +3 -1
  52. package/src/components/button/button.scss +2 -0
  53. package/src/components/fileUpload/FileUpload.stories.tsx +920 -0
  54. package/src/components/fileUpload/FileUpload.test.tsx +172 -0
  55. package/src/components/fileUpload/FileUpload.tsx +144 -0
  56. package/src/components/fileUpload/fileUpload.scss +74 -0
  57. package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +15 -16
  58. package/src/components/formField/inputs/selectDropdown/SelectDropdown.test.tsx +19 -0
  59. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +1 -1
  60. package/src/components/grid/Grid.stories.tsx +1130 -0
  61. package/src/components/grid/Grid.test.tsx +67 -0
  62. package/src/components/grid/Grid.tsx +34 -0
  63. package/src/components/grid/GridColumn.tsx +31 -0
  64. package/src/components/grid/GridRow.tsx +29 -0
  65. package/src/components/icon/allowedIcons.tsx +2 -0
  66. package/src/components/progress/progress.scss +1 -0
  67. package/src/docs/Contributing.mdx +16 -0
  68. package/src/index.scss +2 -0
  69. package/src/index.ts +2 -0
@@ -0,0 +1,1130 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import {
4
+ Controls,
5
+ Heading as DocHeading,
6
+ Markdown,
7
+ Primary as DocPrimary,
8
+ Stories,
9
+ Subtitle,
10
+ Title,
11
+ } from '@storybook/addon-docs/blocks';
12
+ import { Grid } from './Grid.js';
13
+ import { Avatar } from 'Components/avatar/Avatar';
14
+ import { Badge } from 'Components/badge/Badge';
15
+ import { Button } from 'Components/button/Button';
16
+ import { DataViewCard } from 'Components/dataViewCard/DataViewCard';
17
+ import { Heading } from 'Components/heading/Heading';
18
+ import { KPICard } from 'Components/kpiCard/KPICard';
19
+ import { KVPList } from 'Components/kvpList/KVPList';
20
+ import { Section } from 'Components/section/Section';
21
+ import { Tag } from 'Components/tag/Tag';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Docs page content
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const DESCRIPTION_INTRO = [
28
+ 'Grid is a compound layout component that implements the',
29
+ '[GOV.UK Frontend grid system](https://frontend.design-system.service.gov.uk/styles/layout/).',
30
+ 'It provides a structured, column-based page layout that matches the GOV.UK design',
31
+ 'patterns used across Arbor\'s school management interface.',
32
+ '',
33
+ 'The component exposes three building blocks through a single compound API:',
34
+ '',
35
+ '| Part | Class rendered | Description |',
36
+ '|---|---|---|',
37
+ '| `<Grid>` | `govuk-main-wrapper` | Page-level wrapper for all layout content |',
38
+ '| `<Grid.Row>` | `govuk-grid-row` | Horizontal row — contains one or more columns |',
39
+ '| `<Grid.Column>` | `govuk-grid-column-{width}` | A single column within a row |',
40
+ '',
41
+ '### Important: `Grid.Row` and `Grid.Column` are only available via the compound API',
42
+ '',
43
+ '`GridRow` and `GridColumn` are **not** individually exported from the package.',
44
+ 'You must always use the compound form:',
45
+ '',
46
+ '```tsx',
47
+ "import { Grid } from '@arbor-education/design-system.components';",
48
+ '',
49
+ '// Correct',
50
+ '<Grid.Row>',
51
+ ' <Grid.Column columnWidth="two-thirds">...</Grid.Column>',
52
+ '</Grid.Row>',
53
+ '',
54
+ '// Wrong — these imports do not exist in the package',
55
+ "import { GridRow, GridColumn } from '@arbor-education/design-system.components';",
56
+ '```',
57
+ '',
58
+ '### GOV.UK grid styles are bundled',
59
+ '',
60
+ 'The `govuk-grid-row` and `govuk-grid-column-*` styles are compiled into the library\'s',
61
+ 'CSS bundle — import `@arbor-education/design-system.components/dist/index.css` and',
62
+ 'columns will lay out correctly with no additional stylesheet required.',
63
+ '',
64
+ 'The `govuk-main-wrapper` class applied to the `Grid` root is **not** part of the bundled',
65
+ 'grid object — it is a semantic class name only. If your page relies on GOV.UK\'s own',
66
+ '`govuk-main-wrapper` padding, load the GOV.UK Frontend stylesheet separately.',
67
+ ].join('\n');
68
+
69
+ const PROPS_INTRO = 'The interactive canvas below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
70
+
71
+ const USAGE_GUIDANCE = [
72
+ '### When to use Grid',
73
+ '',
74
+ '- **Building GOV.UK-pattern page layouts** — sidebar + main content, full-width sections,',
75
+ ' or equal-width columns that follow the service manual\'s grid',
76
+ '- **Creating standardised column-based structure** across Arbor\'s school management pages',
77
+ ' so layouts stay consistent and accessible',
78
+ '- **School MIS page layouts** such as a student profile page (narrow summary sidebar + wide',
79
+ ' main content) or a dashboard with four equal-width metric cards',
80
+ '',
81
+ '---',
82
+ '',
83
+ '### When NOT to use Grid',
84
+ '',
85
+ '| Situation | Use instead |',
86
+ '|---|---|',
87
+ '| Generic flexbox or grid inside a card or small component | Plain `div` with inline styles or a utility class |',
88
+ '| Custom breakpoints beyond GOV.UK Frontend\'s defaults | Custom CSS — GOV.UK columns use fixed breakpoints |',
89
+ '| Columns deeply nested inside an already-padded container | Avoid nesting — `govuk-main-wrapper` carries page-level padding |',
90
+ ].join('\n');
91
+
92
+ const DEVELOPER_NOTES = [
93
+ '### GOV.UK grid styles are bundled — no extra stylesheet needed for columns',
94
+ '',
95
+ '`govuk-grid-row` and `govuk-grid-column-*` styles are compiled into',
96
+ '`@arbor-education/design-system.components/dist/index.css` via the library\'s SCSS bundle.',
97
+ 'Importing the library\'s CSS is sufficient for `Grid.Row` and `Grid.Column` to lay out correctly.',
98
+ '',
99
+ '`govuk-main-wrapper` (applied to the `Grid` root) is **not** part of the bundled grid object.',
100
+ 'It is a semantic class name. If your application depends on GOV.UK Frontend\'s own',
101
+ '`govuk-main-wrapper` padding styles, load the GOV.UK Frontend stylesheet separately.',
102
+ '',
103
+ '---',
104
+ '',
105
+ '### Compound component API',
106
+ '',
107
+ 'Only `Grid` and `GridProps` are exported from the package. Access rows and columns',
108
+ 'through the compound API:',
109
+ '',
110
+ '```tsx',
111
+ "import { Grid, type GridProps } from '@arbor-education/design-system.components';",
112
+ '',
113
+ '<Grid>',
114
+ ' <Grid.Row>',
115
+ ' <Grid.Column columnWidth="one-third">Sidebar</Grid.Column>',
116
+ ' <Grid.Column columnWidth="two-thirds">Main content</Grid.Column>',
117
+ ' </Grid.Row>',
118
+ '</Grid>',
119
+ '```',
120
+ '',
121
+ '---',
122
+ '',
123
+ '### `Grid.Column` props',
124
+ '',
125
+ '| Prop | Type | Default | Description |',
126
+ '|---|---|---|---|',
127
+ '| `columnWidth` | `\'one-quarter\' \\| \'three-quarters\' \\| \'one-third\' \\| \'two-thirds\' \\| \'one-half\' \\| \'full\'` | `\'full\'` | GOV.UK grid column width class |',
128
+ '| `className` | `string` | — | Additional CSS classes |',
129
+ '| `children` | `ReactNode` | — | Column content |',
130
+ '',
131
+ '### `Grid.Row` props',
132
+ '',
133
+ '| Prop | Type | Default | Description |',
134
+ '|---|---|---|---|',
135
+ '| `className` | `string` | — | Additional CSS classes |',
136
+ '| `children` | `ReactNode` | — | One or more `Grid.Column` elements |',
137
+ '',
138
+ '---',
139
+ '',
140
+ '### Column widths must sum to a full row',
141
+ '',
142
+ 'The component does **not** enforce that column widths add up. Columns that exceed 100%',
143
+ 'will wrap to a new line; columns that total less than 100% will leave empty space.',
144
+ 'Always verify widths sum correctly:',
145
+ '',
146
+ '| Combination | Total |',
147
+ '|---|---|',
148
+ '| `one-half` + `one-half` | 100% |',
149
+ '| `one-third` + `two-thirds` | 100% |',
150
+ '| `one-quarter` + `three-quarters` | 100% |',
151
+ '| `one-quarter` × 4 | 100% |',
152
+ '| `full` | 100% |',
153
+ '',
154
+ '---',
155
+ '',
156
+ '### Do NOT add padding or margin directly to `Grid.Column`',
157
+ '',
158
+ 'GOV.UK Frontend uses gutters (padding) on grid column elements. Adding extra `padding`',
159
+ 'or `margin` to the column itself will break the gutter calculations and misalign content.',
160
+ 'Always put inner spacing on a wrapper `div` **inside** the column:',
161
+ '',
162
+ '```tsx',
163
+ '// Correct',
164
+ '<Grid.Column columnWidth="one-third">',
165
+ ' <div style={{ padding: \'var(--spacing-medium)\' }}>Sidebar content</div>',
166
+ '</Grid.Column>',
167
+ '',
168
+ '// Wrong — overrides GOV.UK gutter padding',
169
+ '<Grid.Column columnWidth="one-third" className="my-padded-column">',
170
+ ' Sidebar content',
171
+ '</Grid.Column>',
172
+ '```',
173
+ '',
174
+ '---',
175
+ '',
176
+ '### TypeScript types',
177
+ '',
178
+ '```ts',
179
+ "import { Grid, type GridProps } from '@arbor-education/design-system.components';",
180
+ '',
181
+ 'function MyLayout(props: GridProps) { ... }',
182
+ '```',
183
+ '',
184
+ '| Type | Description |',
185
+ '|---|---|',
186
+ '| `GridProps` | Props for the `<Grid>` root element |',
187
+ ].join('\n');
188
+
189
+ const RELATED_COMPONENTS = [
190
+ '## Related components',
191
+ '',
192
+ '[Row](?path=/docs/components-row--docs) · [Section](?path=/docs/components-section--docs) · [Modal](?path=/docs/components-modals-modal--docs)',
193
+ ].join('\n');
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Placeholder box — used only in schematic stories to make column boundaries
197
+ // visible. Each column in a row is given a distinct hue (cycled by position)
198
+ // so it is obvious where one column ends and the next begins. The real-page
199
+ // composition stories below do not use this — they render true page content.
200
+ // ---------------------------------------------------------------------------
201
+
202
+ const PLACEHOLDER_HUES = ['blue', 'green', 'purple', 'orange', 'teal', 'salmon', 'yellow'] as const;
203
+
204
+ const placeholderStyle = (index: number): CSSProperties => {
205
+ const hue = PLACEHOLDER_HUES[index % PLACEHOLDER_HUES.length];
206
+ return {
207
+ background: `var(--color-extended-colours-${hue}-100)`,
208
+ border: `var(--border-width-thin) solid var(--color-extended-colours-${hue}-500)`,
209
+ borderRadius: 'var(--border-radius-small)',
210
+ padding: 'var(--spacing-medium)',
211
+ color: `var(--color-extended-colours-${hue}-800)`,
212
+ fontSize: 'var(--font-size-200)',
213
+ fontWeight: 500,
214
+ };
215
+ };
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Docs page
219
+ // ---------------------------------------------------------------------------
220
+
221
+ function GridDocsPage() {
222
+ return (
223
+ <>
224
+ <Title />
225
+ <Subtitle />
226
+ <Markdown>{DESCRIPTION_INTRO}</Markdown>
227
+ <DocHeading>Interactive example</DocHeading>
228
+ <Markdown>{PROPS_INTRO}</Markdown>
229
+ <DocPrimary />
230
+ <Controls />
231
+ <DocHeading>Usage guidance</DocHeading>
232
+ <Markdown>{USAGE_GUIDANCE}</Markdown>
233
+ <DocHeading>Developer notes</DocHeading>
234
+ <Markdown>{DEVELOPER_NOTES}</Markdown>
235
+ <DocHeading>Examples</DocHeading>
236
+ <Stories title="" />
237
+ <Markdown>{RELATED_COMPONENTS}</Markdown>
238
+ </>
239
+ );
240
+ }
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Meta
244
+ // ---------------------------------------------------------------------------
245
+
246
+ const meta = {
247
+ title: 'Components/Grid',
248
+ component: Grid,
249
+ tags: ['autodocs'],
250
+ parameters: {
251
+ layout: 'padded',
252
+ docs: { page: GridDocsPage },
253
+ },
254
+ argTypes: {
255
+ className: {
256
+ control: false,
257
+ description: 'Additional CSS class names applied to the `govuk-main-wrapper` root element.',
258
+ table: {
259
+ type: { summary: 'string' },
260
+ },
261
+ },
262
+ children: {
263
+ control: false,
264
+ description: 'One or more `Grid.Row` elements that make up the page layout.',
265
+ table: {
266
+ type: { summary: 'ReactNode' },
267
+ },
268
+ },
269
+ },
270
+ } satisfies Meta<typeof Grid>;
271
+
272
+ export default meta;
273
+ type Story = StoryObj<typeof meta>;
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // Helper: attach a per-story description to docs
277
+ // ---------------------------------------------------------------------------
278
+
279
+ const withDescription = (story: Story, description: string): Story => ({
280
+ ...story,
281
+ parameters: {
282
+ ...story.parameters,
283
+ docs: {
284
+ ...story.parameters?.docs,
285
+ description: {
286
+ story: description,
287
+ },
288
+ },
289
+ },
290
+ });
291
+
292
+ // ---------------------------------------------------------------------------
293
+ // Template components
294
+ // ---------------------------------------------------------------------------
295
+
296
+ const AllWidthsTemplate = () => (
297
+ <Grid>
298
+ {(['full', 'three-quarters', 'two-thirds', 'one-half', 'one-third', 'one-quarter'] as const).map((width, index) => (
299
+ <Grid.Row key={width}>
300
+ <Grid.Column columnWidth={width}>
301
+ <div style={placeholderStyle(index)}>{width}</div>
302
+ </Grid.Column>
303
+ </Grid.Row>
304
+ ))}
305
+ </Grid>
306
+ );
307
+
308
+ const MultipleRowsTemplate = () => (
309
+ <Grid>
310
+ <Grid.Row>
311
+ <Grid.Column columnWidth="full">
312
+ <div style={placeholderStyle(0)}>Page header — full width</div>
313
+ </Grid.Column>
314
+ </Grid.Row>
315
+ <Grid.Row>
316
+ <Grid.Column columnWidth="one-third">
317
+ <div style={placeholderStyle(0)}>Navigation</div>
318
+ </Grid.Column>
319
+ <Grid.Column columnWidth="two-thirds">
320
+ <div style={placeholderStyle(1)}>Main content</div>
321
+ </Grid.Column>
322
+ </Grid.Row>
323
+ <Grid.Row>
324
+ <Grid.Column columnWidth="full">
325
+ <div style={placeholderStyle(0)}>Footer — full width</div>
326
+ </Grid.Column>
327
+ </Grid.Row>
328
+ </Grid>
329
+ );
330
+
331
+ const FourQuartersTemplate = () => (
332
+ <Grid>
333
+ <Grid.Row>
334
+ <Grid.Column columnWidth="one-quarter">
335
+ <div style={placeholderStyle(0)}>Student summary</div>
336
+ </Grid.Column>
337
+ <Grid.Column columnWidth="one-quarter">
338
+ <div style={placeholderStyle(1)}>Attendance</div>
339
+ </Grid.Column>
340
+ <Grid.Column columnWidth="one-quarter">
341
+ <div style={placeholderStyle(2)}>Assessment</div>
342
+ </Grid.Column>
343
+ <Grid.Column columnWidth="one-quarter">
344
+ <div style={placeholderStyle(3)}>Finance</div>
345
+ </Grid.Column>
346
+ </Grid.Row>
347
+ </Grid>
348
+ );
349
+
350
+ // ---------------------------------------------------------------------------
351
+ // Stories
352
+ // ---------------------------------------------------------------------------
353
+
354
+ export const Default: Story = withDescription(
355
+ {
356
+ args: {},
357
+ render: args => (
358
+ <Grid {...args}>
359
+ <Grid.Row>
360
+ <Grid.Column columnWidth="full">
361
+ <div style={placeholderStyle(0)}>Main content — full width</div>
362
+ </Grid.Column>
363
+ </Grid.Row>
364
+ </Grid>
365
+ ),
366
+ },
367
+ [
368
+ 'The simplest Grid usage: a single `Grid.Row` containing one `Grid.Column` set to `full` width.',
369
+ 'Use this as your starting point and add more columns or rows as the layout requires.',
370
+ 'The `className` prop on the Controls panel applies to the `govuk-main-wrapper` root.',
371
+ ].join(' '),
372
+ );
373
+
374
+ export const OneHalf: Story = withDescription(
375
+ {
376
+ render: () => (
377
+ <Grid>
378
+ <Grid.Row>
379
+ <Grid.Column columnWidth="one-half">
380
+ <div style={placeholderStyle(0)}>Student information</div>
381
+ </Grid.Column>
382
+ <Grid.Column columnWidth="one-half">
383
+ <div style={placeholderStyle(1)}>Attendance overview</div>
384
+ </Grid.Column>
385
+ </Grid.Row>
386
+ </Grid>
387
+ ),
388
+ parameters: {
389
+ controls: { disable: true },
390
+ docs: {
391
+ source: {
392
+ language: 'tsx',
393
+ code: `
394
+ import { Grid } from '@arbor-education/design-system.components';
395
+
396
+ function OneHalfExample() {
397
+ return (
398
+ <Grid>
399
+ <Grid.Row>
400
+ <Grid.Column columnWidth="one-half">
401
+ Student information
402
+ </Grid.Column>
403
+ <Grid.Column columnWidth="one-half">
404
+ Attendance overview
405
+ </Grid.Column>
406
+ </Grid.Row>
407
+ </Grid>
408
+ );
409
+ }
410
+ export default OneHalfExample;
411
+ `.trim(),
412
+ },
413
+ },
414
+ },
415
+ },
416
+ [
417
+ 'Two equal `one-half` columns placed side by side.',
418
+ 'Use this when two content areas carry equal visual weight, such as a student information',
419
+ 'panel alongside an attendance summary.',
420
+ ].join(' '),
421
+ );
422
+
423
+ export const OneThirdTwoThirds: Story = withDescription(
424
+ {
425
+ render: () => (
426
+ <Grid>
427
+ <Grid.Row>
428
+ <Grid.Column columnWidth="one-third">
429
+ <div style={placeholderStyle(0)}>Navigation</div>
430
+ </Grid.Column>
431
+ <Grid.Column columnWidth="two-thirds">
432
+ <div style={placeholderStyle(1)}>Main content</div>
433
+ </Grid.Column>
434
+ </Grid.Row>
435
+ </Grid>
436
+ ),
437
+ parameters: {
438
+ controls: { disable: true },
439
+ docs: {
440
+ source: {
441
+ language: 'tsx',
442
+ code: `
443
+ import { Grid } from '@arbor-education/design-system.components';
444
+
445
+ function SidebarLayoutExample() {
446
+ return (
447
+ <Grid>
448
+ <Grid.Row>
449
+ <Grid.Column columnWidth="one-third">
450
+ Navigation
451
+ </Grid.Column>
452
+ <Grid.Column columnWidth="two-thirds">
453
+ Main content
454
+ </Grid.Column>
455
+ </Grid.Row>
456
+ </Grid>
457
+ );
458
+ }
459
+ export default SidebarLayoutExample;
460
+ `.trim(),
461
+ },
462
+ },
463
+ },
464
+ },
465
+ [
466
+ 'The canonical GOV.UK sidebar + main content pattern.',
467
+ 'A narrow left-hand `one-third` column holds navigation or filters,',
468
+ 'while the `two-thirds` column carries the primary page content.',
469
+ ].join(' '),
470
+ );
471
+
472
+ export const TwoThirdsOneThird: Story = withDescription(
473
+ {
474
+ render: () => (
475
+ <Grid>
476
+ <Grid.Row>
477
+ <Grid.Column columnWidth="two-thirds">
478
+ <div style={placeholderStyle(0)}>Assessment results</div>
479
+ </Grid.Column>
480
+ <Grid.Column columnWidth="one-third">
481
+ <div style={placeholderStyle(1)}>Filters</div>
482
+ </Grid.Column>
483
+ </Grid.Row>
484
+ </Grid>
485
+ ),
486
+ parameters: {
487
+ controls: { disable: true },
488
+ docs: {
489
+ source: {
490
+ language: 'tsx',
491
+ code: `
492
+ import { Grid } from '@arbor-education/design-system.components';
493
+
494
+ function ReversedSidebarExample() {
495
+ return (
496
+ <Grid>
497
+ <Grid.Row>
498
+ <Grid.Column columnWidth="two-thirds">
499
+ Assessment results
500
+ </Grid.Column>
501
+ <Grid.Column columnWidth="one-third">
502
+ Filters
503
+ </Grid.Column>
504
+ </Grid.Row>
505
+ </Grid>
506
+ );
507
+ }
508
+ export default ReversedSidebarExample;
509
+ `.trim(),
510
+ },
511
+ },
512
+ },
513
+ },
514
+ [
515
+ 'The reversed layout — main content on the left with a narrower right-hand sidebar.',
516
+ 'Useful when filter controls or contextual information should sit to the right of primary content.',
517
+ ].join(' '),
518
+ );
519
+
520
+ export const OneQuarterThreeQuarters: Story = withDescription(
521
+ {
522
+ render: () => (
523
+ <Grid>
524
+ <Grid.Row>
525
+ <Grid.Column columnWidth="one-quarter">
526
+ <div style={placeholderStyle(0)}>Navigation</div>
527
+ </Grid.Column>
528
+ <Grid.Column columnWidth="three-quarters">
529
+ <div style={placeholderStyle(1)}>Main content</div>
530
+ </Grid.Column>
531
+ </Grid.Row>
532
+ </Grid>
533
+ ),
534
+ parameters: {
535
+ controls: { disable: true },
536
+ docs: {
537
+ source: {
538
+ language: 'tsx',
539
+ code: `
540
+ import { Grid } from '@arbor-education/design-system.components';
541
+
542
+ function NarrowSidebarExample() {
543
+ return (
544
+ <Grid>
545
+ <Grid.Row>
546
+ <Grid.Column columnWidth="one-quarter">
547
+ Navigation
548
+ </Grid.Column>
549
+ <Grid.Column columnWidth="three-quarters">
550
+ Main content
551
+ </Grid.Column>
552
+ </Grid.Row>
553
+ </Grid>
554
+ );
555
+ }
556
+ export default NarrowSidebarExample;
557
+ `.trim(),
558
+ },
559
+ },
560
+ },
561
+ },
562
+ [
563
+ 'A narrow `one-quarter` sidebar paired with a wide `three-quarters` content area.',
564
+ 'Use this when the sidebar needs to be more compact than the standard `one-third` variant,',
565
+ 'such as a compact navigation list alongside a data-dense report.',
566
+ ].join(' '),
567
+ );
568
+
569
+ export const ThreeQuartersOneQuarter: Story = withDescription(
570
+ {
571
+ render: () => (
572
+ <Grid>
573
+ <Grid.Row>
574
+ <Grid.Column columnWidth="three-quarters">
575
+ <div style={placeholderStyle(0)}>Main content</div>
576
+ </Grid.Column>
577
+ <Grid.Column columnWidth="one-quarter">
578
+ <div style={placeholderStyle(1)}>Summary panel</div>
579
+ </Grid.Column>
580
+ </Grid.Row>
581
+ </Grid>
582
+ ),
583
+ parameters: {
584
+ controls: { disable: true },
585
+ docs: {
586
+ source: {
587
+ language: 'tsx',
588
+ code: `
589
+ import { Grid } from '@arbor-education/design-system.components';
590
+
591
+ function WideContentNarrowPanelExample() {
592
+ return (
593
+ <Grid>
594
+ <Grid.Row>
595
+ <Grid.Column columnWidth="three-quarters">
596
+ Main content
597
+ </Grid.Column>
598
+ <Grid.Column columnWidth="one-quarter">
599
+ Summary panel
600
+ </Grid.Column>
601
+ </Grid.Row>
602
+ </Grid>
603
+ );
604
+ }
605
+ export default WideContentNarrowPanelExample;
606
+ `.trim(),
607
+ },
608
+ },
609
+ },
610
+ },
611
+ [
612
+ 'Wide primary content on the left with a compact right-hand summary panel.',
613
+ 'The `three-quarters` + `one-quarter` split gives you the most content space',
614
+ 'while still showing a supplementary panel — ideal for report views with a key-metrics sidebar.',
615
+ ].join(' '),
616
+ );
617
+
618
+ export const FourQuarters: Story = withDescription(
619
+ {
620
+ render: FourQuartersTemplate,
621
+ parameters: {
622
+ controls: { disable: true },
623
+ docs: {
624
+ source: {
625
+ language: 'tsx',
626
+ code: `
627
+ import { Grid } from '@arbor-education/design-system.components';
628
+
629
+ function FourQuartersExample() {
630
+ return (
631
+ <Grid>
632
+ <Grid.Row>
633
+ <Grid.Column columnWidth="one-quarter">
634
+ Student summary
635
+ </Grid.Column>
636
+ <Grid.Column columnWidth="one-quarter">
637
+ Attendance
638
+ </Grid.Column>
639
+ <Grid.Column columnWidth="one-quarter">
640
+ Assessment
641
+ </Grid.Column>
642
+ <Grid.Column columnWidth="one-quarter">
643
+ Finance
644
+ </Grid.Column>
645
+ </Grid.Row>
646
+ </Grid>
647
+ );
648
+ }
649
+ export default FourQuartersExample;
650
+ `.trim(),
651
+ },
652
+ },
653
+ },
654
+ },
655
+ [
656
+ 'Four equal `one-quarter` columns across a single row — the card-grid pattern.',
657
+ 'Use this for dashboard metric cards, category overviews, or any set of four equal-weight items',
658
+ 'that should be presented side by side at the page level.',
659
+ ].join(' '),
660
+ );
661
+
662
+ export const MultipleRows: Story = withDescription(
663
+ {
664
+ render: MultipleRowsTemplate,
665
+ parameters: {
666
+ controls: { disable: true },
667
+ docs: {
668
+ source: {
669
+ language: 'tsx',
670
+ code: `
671
+ import { Grid } from '@arbor-education/design-system.components';
672
+
673
+ function MultipleRowsExample() {
674
+ return (
675
+ <Grid>
676
+ <Grid.Row>
677
+ <Grid.Column columnWidth="full">
678
+ Page header — full width
679
+ </Grid.Column>
680
+ </Grid.Row>
681
+ <Grid.Row>
682
+ <Grid.Column columnWidth="one-third">
683
+ Navigation
684
+ </Grid.Column>
685
+ <Grid.Column columnWidth="two-thirds">
686
+ Main content
687
+ </Grid.Column>
688
+ </Grid.Row>
689
+ <Grid.Row>
690
+ <Grid.Column columnWidth="full">
691
+ Footer — full width
692
+ </Grid.Column>
693
+ </Grid.Row>
694
+ </Grid>
695
+ );
696
+ }
697
+ export default MultipleRowsExample;
698
+ `.trim(),
699
+ },
700
+ },
701
+ },
702
+ },
703
+ [
704
+ 'Multiple `Grid.Row` instances stacked inside a single `Grid` wrapper.',
705
+ 'Each row is independent — rows can use different column configurations.',
706
+ 'The `Grid` root (`govuk-main-wrapper`) wraps all rows and provides the outer page padding.',
707
+ ].join(' '),
708
+ );
709
+
710
+ export const AllWidths: Story = withDescription(
711
+ {
712
+ render: AllWidthsTemplate,
713
+ parameters: {
714
+ controls: { disable: true },
715
+ docs: {
716
+ source: {
717
+ language: 'tsx',
718
+ code: `
719
+ import { Grid } from '@arbor-education/design-system.components';
720
+
721
+ // All six columnWidth values, each in its own row for comparison
722
+ function AllWidthsExample() {
723
+ return (
724
+ <Grid>
725
+ <Grid.Row>
726
+ <Grid.Column columnWidth="full">full</Grid.Column>
727
+ </Grid.Row>
728
+ <Grid.Row>
729
+ <Grid.Column columnWidth="three-quarters">three-quarters</Grid.Column>
730
+ </Grid.Row>
731
+ <Grid.Row>
732
+ <Grid.Column columnWidth="two-thirds">two-thirds</Grid.Column>
733
+ </Grid.Row>
734
+ <Grid.Row>
735
+ <Grid.Column columnWidth="one-half">one-half</Grid.Column>
736
+ </Grid.Row>
737
+ <Grid.Row>
738
+ <Grid.Column columnWidth="one-third">one-third</Grid.Column>
739
+ </Grid.Row>
740
+ <Grid.Row>
741
+ <Grid.Column columnWidth="one-quarter">one-quarter</Grid.Column>
742
+ </Grid.Row>
743
+ </Grid>
744
+ );
745
+ }
746
+ export default AllWidthsExample;
747
+ `.trim(),
748
+ },
749
+ },
750
+ },
751
+ },
752
+ [
753
+ 'All six available `columnWidth` values shown in isolation, one per row, from widest to narrowest.',
754
+ 'Use this as a visual reference when choosing the right column proportion for your layout.',
755
+ ].join(' '),
756
+ );
757
+
758
+ // ---------------------------------------------------------------------------
759
+ // Real-page composition examples
760
+ // ---------------------------------------------------------------------------
761
+
762
+ const kpiRowStyle: CSSProperties = {
763
+ display: 'flex',
764
+ gap: 'var(--spacing-medium)',
765
+ marginBottom: 'var(--spacing-medium)',
766
+ };
767
+
768
+ const pageHeaderStyle: CSSProperties = {
769
+ display: 'flex',
770
+ alignItems: 'center',
771
+ justifyContent: 'space-between',
772
+ paddingBottom: 'var(--spacing-medium)',
773
+ borderBottom: 'var(--border-width-thin) solid var(--color-neutral-200)',
774
+ marginBottom: 'var(--spacing-medium)',
775
+ };
776
+
777
+ const actionRowStyle: CSSProperties = {
778
+ display: 'flex',
779
+ gap: 'var(--spacing-small)',
780
+ };
781
+
782
+ const sidebarInnerStyle: CSSProperties = {
783
+ display: 'flex',
784
+ flexDirection: 'column',
785
+ alignItems: 'center',
786
+ gap: 'var(--spacing-medium)',
787
+ paddingRight: 'var(--spacing-medium)',
788
+ };
789
+
790
+ export const StudentProfilePage: Story = withDescription(
791
+ {
792
+ render: () => (
793
+ <Grid>
794
+ <Grid.Row>
795
+ <Grid.Column columnWidth="full">
796
+ <div style={pageHeaderStyle}>
797
+ <div>
798
+ <Heading level={1}>Emma Johnson</Heading>
799
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', marginTop: 'var(--spacing-xsmall)' }}>
800
+ <Tag color="blue">Year 9</Tag>
801
+ <Tag color="teal">Set 2</Tag>
802
+ <Tag color="green">On Roll</Tag>
803
+ </div>
804
+ </div>
805
+ <div style={actionRowStyle}>
806
+ <Button variant="secondary" size="S" iconLeftName="pencil">Edit</Button>
807
+ <Button variant="secondary" size="S" iconLeftName="file">Print</Button>
808
+ <Button variant="primary" size="S">Contact Home</Button>
809
+ </div>
810
+ </div>
811
+ </Grid.Column>
812
+ </Grid.Row>
813
+
814
+ <Grid.Row>
815
+ <Grid.Column columnWidth="one-quarter">
816
+ <div style={sidebarInnerStyle}>
817
+ <Avatar size="extra-large" initials="EJ" />
818
+ <KVPList>
819
+ <KVPList.Row orientation="vertical">
820
+ <KVPList.Term>Date of Birth</KVPList.Term>
821
+ <KVPList.Definition>12 Mar 2010</KVPList.Definition>
822
+ </KVPList.Row>
823
+ <KVPList.Row orientation="vertical">
824
+ <KVPList.Term>Tutor Group</KVPList.Term>
825
+ <KVPList.Definition>9B</KVPList.Definition>
826
+ </KVPList.Row>
827
+ <KVPList.Row orientation="vertical">
828
+ <KVPList.Term>Form Tutor</KVPList.Term>
829
+ <KVPList.Definition>Mr T. Harris</KVPList.Definition>
830
+ </KVPList.Row>
831
+ <KVPList.Row orientation="vertical">
832
+ <KVPList.Term>SEN Status</KVPList.Term>
833
+ <KVPList.Definition>
834
+ <Tag color="orange">Monitor</Tag>
835
+ </KVPList.Definition>
836
+ </KVPList.Row>
837
+ <KVPList.Row orientation="vertical">
838
+ <KVPList.Term>FSM</KVPList.Term>
839
+ <KVPList.Definition>Yes</KVPList.Definition>
840
+ </KVPList.Row>
841
+ </KVPList>
842
+ </div>
843
+ </Grid.Column>
844
+
845
+ <Grid.Column columnWidth="three-quarters">
846
+ <Section title="Academic Overview" headingLevel={2}>
847
+ <div style={kpiRowStyle}>
848
+ <KPICard label="Attendance" value={94.2} isPercentage />
849
+ <KPICard label="Sessions This Term" value={128} />
850
+ <KPICard label="Unauthorised Absences" value={3} />
851
+ <KPICard label="Behaviour Points" value={12} />
852
+ </div>
853
+ </Section>
854
+
855
+ <Section title="Recent Classes" headingLevel={2}>
856
+ <div style={{ display: 'flex', gap: 'var(--spacing-medium)', flexWrap: 'wrap' }}>
857
+ <DataViewCard
858
+ header="Mathematics"
859
+ rows={[
860
+ { label: 'Teacher', value: 'Ms R. Patel' },
861
+ { label: 'Room', value: 'B14' },
862
+ { label: 'Last Grade', value: 'A' },
863
+ ]}
864
+ />
865
+ <DataViewCard
866
+ header="English Literature"
867
+ rows={[
868
+ { label: 'Teacher', value: 'Mr D. Clarke' },
869
+ { label: 'Room', value: 'A03' },
870
+ { label: 'Last Grade', value: 'B+' },
871
+ ]}
872
+ />
873
+ <DataViewCard
874
+ header="Science"
875
+ rows={[
876
+ { label: 'Teacher', value: 'Dr S. Okafor' },
877
+ { label: 'Room', value: 'Lab 2' },
878
+ { label: 'Last Grade', value: 'A*' },
879
+ ]}
880
+ />
881
+ </div>
882
+ </Section>
883
+ </Grid.Column>
884
+ </Grid.Row>
885
+ </Grid>
886
+ ),
887
+ parameters: {
888
+ controls: { disable: true },
889
+ layout: 'fullscreen',
890
+ },
891
+ },
892
+ [
893
+ 'A realistic student profile page composed entirely from design system components.',
894
+ 'The `one-quarter` sidebar holds identity details while `three-quarters` carries KPI metrics and class cards.',
895
+ 'Use this pattern when a student record needs a persistent summary alongside rich contextual content.',
896
+ ].join(' '),
897
+ );
898
+
899
+ export const AttendanceDashboard: Story = withDescription(
900
+ {
901
+ render: () => (
902
+ <Grid>
903
+ <Grid.Row>
904
+ <Grid.Column columnWidth="full">
905
+ <div style={pageHeaderStyle}>
906
+ <div>
907
+ <Heading level={1}>Attendance Overview</Heading>
908
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', marginTop: 'var(--spacing-xsmall)' }}>
909
+ <Badge colour="teal">Autumn Term 2024</Badge>
910
+ <Badge colour="blue">All Year Groups</Badge>
911
+ </div>
912
+ </div>
913
+ <div style={actionRowStyle}>
914
+ <Button variant="secondary" size="S" iconLeftName="download">Export</Button>
915
+ <Button variant="primary" size="S">Generate Report</Button>
916
+ </div>
917
+ </div>
918
+ </Grid.Column>
919
+ </Grid.Row>
920
+
921
+ <Grid.Row>
922
+ <Grid.Column columnWidth="one-quarter">
923
+ <KPICard label="Overall Attendance" value={96.1} isPercentage />
924
+ </Grid.Column>
925
+ <Grid.Column columnWidth="one-quarter">
926
+ <KPICard label="Persistent Absentees" value={23} />
927
+ </Grid.Column>
928
+ <Grid.Column columnWidth="one-quarter">
929
+ <KPICard label="Sessions Recorded" value={2840} />
930
+ </Grid.Column>
931
+ <Grid.Column columnWidth="one-quarter">
932
+ <KPICard label="Punctuality Issues" value={11} />
933
+ </Grid.Column>
934
+ </Grid.Row>
935
+
936
+ <Grid.Row>
937
+ <Grid.Column columnWidth="two-thirds">
938
+ <Section title="Lowest Attendance" headingLevel={2}>
939
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }}>
940
+ <DataViewCard
941
+ header="Callum Wright"
942
+ avatar={{ initials: 'CW' }}
943
+ rows={[
944
+ { label: 'Year Group', value: 'Year 10' },
945
+ { label: 'Attendance', value: '71.4%' },
946
+ { label: 'Unauthorised', value: '14 sessions' },
947
+ ]}
948
+ />
949
+ <DataViewCard
950
+ header="Priya Sharma"
951
+ avatar={{ initials: 'PS' }}
952
+ rows={[
953
+ { label: 'Year Group', value: 'Year 8' },
954
+ { label: 'Attendance', value: '74.8%' },
955
+ { label: 'Unauthorised', value: '11 sessions' },
956
+ ]}
957
+ />
958
+ <DataViewCard
959
+ header="Jordan Blake"
960
+ avatar={{ initials: 'JB' }}
961
+ rows={[
962
+ { label: 'Year Group', value: 'Year 11' },
963
+ { label: 'Attendance', value: '78.2%' },
964
+ { label: 'Unauthorised', value: '9 sessions' },
965
+ ]}
966
+ />
967
+ </div>
968
+ </Section>
969
+ </Grid.Column>
970
+
971
+ <Grid.Column columnWidth="one-third">
972
+ <Section title="Filter by Year Group" headingLevel={2}>
973
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-xsmall)' }}>
974
+ {['Year 7', 'Year 8', 'Year 9', 'Year 10', 'Year 11', 'Year 12', 'Year 13'].map(year => (
975
+ <Tag key={year} color="neutral">{year}</Tag>
976
+ ))}
977
+ </div>
978
+ </Section>
979
+
980
+ <Section title="Absence Codes" headingLevel={2}>
981
+ <KVPList>
982
+ <KVPList.Row orientation="horizontal">
983
+ <KVPList.Term>Authorised</KVPList.Term>
984
+ <KVPList.Definition prominence="strong">847</KVPList.Definition>
985
+ </KVPList.Row>
986
+ <KVPList.Row orientation="horizontal">
987
+ <KVPList.Term>Unauthorised</KVPList.Term>
988
+ <KVPList.Definition prominence="strong">312</KVPList.Definition>
989
+ </KVPList.Row>
990
+ <KVPList.Row orientation="horizontal">
991
+ <KVPList.Term>Medical</KVPList.Term>
992
+ <KVPList.Definition prominence="strong">204</KVPList.Definition>
993
+ </KVPList.Row>
994
+ <KVPList.Row orientation="horizontal">
995
+ <KVPList.Term>Holiday</KVPList.Term>
996
+ <KVPList.Definition prominence="strong">58</KVPList.Definition>
997
+ </KVPList.Row>
998
+ </KVPList>
999
+ </Section>
1000
+ </Grid.Column>
1001
+ </Grid.Row>
1002
+ </Grid>
1003
+ ),
1004
+ parameters: {
1005
+ controls: { disable: true },
1006
+ layout: 'fullscreen',
1007
+ },
1008
+ },
1009
+ [
1010
+ 'A school attendance dashboard showing the full-width four-column KPI pattern followed by a',
1011
+ '`two-thirds` + `one-third` detail layout.',
1012
+ 'The left column surfaces the students most at risk; the right column holds quick filters and summary stats.',
1013
+ ].join(' '),
1014
+ );
1015
+
1016
+ export const StaffDirectory: Story = withDescription(
1017
+ {
1018
+ render: () => (
1019
+ <Grid>
1020
+ <Grid.Row>
1021
+ <Grid.Column columnWidth="full">
1022
+ <div style={pageHeaderStyle}>
1023
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}>
1024
+ <Heading level={1}>Staff Directory</Heading>
1025
+ <Badge colour="blue">24 staff</Badge>
1026
+ </div>
1027
+ <Button variant="primary" size="S" iconLeftName="plus">Add Staff Member</Button>
1028
+ </div>
1029
+ </Grid.Column>
1030
+ </Grid.Row>
1031
+
1032
+ <Grid.Row>
1033
+ <Grid.Column columnWidth="full">
1034
+ <div style={{ display: 'flex', gap: 'var(--spacing-xsmall)', marginBottom: 'var(--spacing-medium)', flexWrap: 'wrap' }}>
1035
+ {[
1036
+ { label: 'Teaching', color: 'blue' },
1037
+ { label: 'Support', color: 'teal' },
1038
+ { label: 'Leadership', color: 'purple' },
1039
+ { label: 'Admin', color: 'orange' },
1040
+ ].map(({ label, color }) => (
1041
+ <Tag key={label} color={color as 'blue' | 'teal' | 'purple' | 'orange'}>{label}</Tag>
1042
+ ))}
1043
+ </div>
1044
+ </Grid.Column>
1045
+ </Grid.Row>
1046
+
1047
+ <Grid.Row>
1048
+ <Grid.Column columnWidth="one-third">
1049
+ <DataViewCard
1050
+ header="Sarah Thompson"
1051
+ avatar={{ initials: 'ST' }}
1052
+ rows={[
1053
+ { label: 'Role', value: 'Head of Maths' },
1054
+ { label: 'Department', value: 'Mathematics' },
1055
+ { label: 'Email', value: 's.thompson@school.edu' },
1056
+ ]}
1057
+ />
1058
+ </Grid.Column>
1059
+ <Grid.Column columnWidth="one-third">
1060
+ <DataViewCard
1061
+ header="James Okafor"
1062
+ avatar={{ initials: 'JO' }}
1063
+ rows={[
1064
+ { label: 'Role', value: 'SENCO' },
1065
+ { label: 'Department', value: 'Inclusion' },
1066
+ { label: 'Email', value: 'j.okafor@school.edu' },
1067
+ ]}
1068
+ />
1069
+ </Grid.Column>
1070
+ <Grid.Column columnWidth="one-third">
1071
+ <DataViewCard
1072
+ header="Rachel Patel"
1073
+ avatar={{ initials: 'RP' }}
1074
+ rows={[
1075
+ { label: 'Role', value: 'Science Teacher' },
1076
+ { label: 'Department', value: 'Science' },
1077
+ { label: 'Email', value: 'r.patel@school.edu' },
1078
+ ]}
1079
+ />
1080
+ </Grid.Column>
1081
+ </Grid.Row>
1082
+
1083
+ <Grid.Row>
1084
+ <Grid.Column columnWidth="one-third">
1085
+ <DataViewCard
1086
+ header="David Clarke"
1087
+ avatar={{ initials: 'DC' }}
1088
+ rows={[
1089
+ { label: 'Role', value: 'English Teacher' },
1090
+ { label: 'Department', value: 'English' },
1091
+ { label: 'Email', value: 'd.clarke@school.edu' },
1092
+ ]}
1093
+ />
1094
+ </Grid.Column>
1095
+ <Grid.Column columnWidth="one-third">
1096
+ <DataViewCard
1097
+ header="Amara Diallo"
1098
+ avatar={{ initials: 'AD' }}
1099
+ rows={[
1100
+ { label: 'Role', value: 'Deputy Head' },
1101
+ { label: 'Department', value: 'Leadership' },
1102
+ { label: 'Email', value: 'a.diallo@school.edu' },
1103
+ ]}
1104
+ />
1105
+ </Grid.Column>
1106
+ <Grid.Column columnWidth="one-third">
1107
+ <DataViewCard
1108
+ header="Tom Harris"
1109
+ avatar={{ initials: 'TH' }}
1110
+ rows={[
1111
+ { label: 'Role', value: 'PE Teacher' },
1112
+ { label: 'Department', value: 'PE & Sport' },
1113
+ { label: 'Email', value: 't.harris@school.edu' },
1114
+ ]}
1115
+ />
1116
+ </Grid.Column>
1117
+ </Grid.Row>
1118
+ </Grid>
1119
+ ),
1120
+ parameters: {
1121
+ controls: { disable: true },
1122
+ layout: 'fullscreen',
1123
+ },
1124
+ },
1125
+ [
1126
+ 'A staff directory page demonstrating the three-column `one-third` grid pattern across multiple rows.',
1127
+ 'Each `DataViewCard` represents a staff member with avatar, role, and contact details.',
1128
+ 'The filter tags at the top use a full-width row; the card grid rows beneath are fully independent.',
1129
+ ].join(' '),
1130
+ );