@arbor-education/design-system.components 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/component-library.md +15 -14
  3. package/dist/components/articleCard/ArticleCard.d.ts +2 -2
  4. package/dist/components/articleCard/ArticleCard.d.ts.map +1 -1
  5. package/dist/components/articleCard/ArticleCard.js +3 -3
  6. package/dist/components/articleCard/ArticleCard.js.map +1 -1
  7. package/dist/components/articleCard/ArticleCard.stories.d.ts +11 -3
  8. package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
  9. package/dist/components/articleCard/ArticleCard.stories.js +16 -11
  10. package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
  11. package/dist/components/iconText/IconText.d.ts +43 -0
  12. package/dist/components/iconText/IconText.d.ts.map +1 -0
  13. package/dist/components/iconText/IconText.js +29 -0
  14. package/dist/components/iconText/IconText.js.map +1 -0
  15. package/dist/components/{icoText/IcoText.stories.d.ts → iconText/IconText.stories.d.ts} +8 -9
  16. package/dist/components/iconText/IconText.stories.d.ts.map +1 -0
  17. package/dist/components/{icoText/IcoText.stories.js → iconText/IconText.stories.js} +81 -81
  18. package/dist/components/iconText/IconText.stories.js.map +1 -0
  19. package/dist/components/iconText/IconText.test.d.ts +2 -0
  20. package/dist/components/iconText/IconText.test.d.ts.map +1 -0
  21. package/dist/components/{icoText/IcoText.test.js → iconText/IconText.test.js} +6 -6
  22. package/dist/components/iconText/IconText.test.js.map +1 -0
  23. package/dist/components/modal/Modal.d.ts +1 -0
  24. package/dist/components/modal/Modal.d.ts.map +1 -1
  25. package/dist/components/modal/Modal.js +2 -2
  26. package/dist/components/modal/Modal.js.map +1 -1
  27. package/dist/components/tag/Tag.d.ts +14 -1
  28. package/dist/components/tag/Tag.d.ts.map +1 -1
  29. package/dist/components/tag/Tag.js +9 -3
  30. package/dist/components/tag/Tag.js.map +1 -1
  31. package/dist/components/tag/Tag.stories.d.ts +1 -1
  32. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  33. package/dist/components/tag/Tag.stories.js +3 -3
  34. package/dist/components/tag/Tag.stories.js.map +1 -1
  35. package/dist/components/tag/Tag.test.js +36 -5
  36. package/dist/components/tag/Tag.test.js.map +1 -1
  37. package/dist/components/tagList/TagList.d.ts +49 -0
  38. package/dist/components/tagList/TagList.d.ts.map +1 -0
  39. package/dist/components/tagList/TagList.js +114 -0
  40. package/dist/components/tagList/TagList.js.map +1 -0
  41. package/dist/components/tagList/TagList.stories.d.ts +130 -0
  42. package/dist/components/tagList/TagList.stories.d.ts.map +1 -0
  43. package/dist/components/tagList/TagList.stories.js +443 -0
  44. package/dist/components/tagList/TagList.stories.js.map +1 -0
  45. package/dist/components/{icoText/IcoText.test.d.ts → tagList/TagList.test.d.ts} +1 -1
  46. package/dist/components/tagList/TagList.test.d.ts.map +1 -0
  47. package/dist/components/tagList/TagList.test.js +246 -0
  48. package/dist/components/tagList/TagList.test.js.map +1 -0
  49. package/dist/components/tagList/useTagListCollapsedLayout.d.ts +19 -0
  50. package/dist/components/tagList/useTagListCollapsedLayout.d.ts.map +1 -0
  51. package/dist/components/tagList/useTagListCollapsedLayout.js +48 -0
  52. package/dist/components/tagList/useTagListCollapsedLayout.js.map +1 -0
  53. package/dist/components/tagList/useVisibleTags.d.ts +18 -0
  54. package/dist/components/tagList/useVisibleTags.d.ts.map +1 -0
  55. package/dist/components/tagList/useVisibleTags.js +41 -0
  56. package/dist/components/tagList/useVisibleTags.js.map +1 -0
  57. package/dist/index.css +130 -10
  58. package/dist/index.css.map +1 -1
  59. package/dist/index.d.ts +3 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/utils/hooks/useElementWidth.d.ts +2 -0
  64. package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
  65. package/dist/utils/hooks/useElementWidth.js +30 -0
  66. package/dist/utils/hooks/useElementWidth.js.map +1 -0
  67. package/dist/utils/hooks/useMeasuredChildWidths.d.ts +8 -0
  68. package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -0
  69. package/dist/utils/hooks/useMeasuredChildWidths.js +26 -0
  70. package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -0
  71. package/dist/utils/hooks/useRovingFocus.d.ts +18 -0
  72. package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -0
  73. package/dist/utils/hooks/useRovingFocus.js +130 -0
  74. package/dist/utils/hooks/useRovingFocus.js.map +1 -0
  75. package/dist/utils/hooks/useRovingFocus.test.d.ts +2 -0
  76. package/dist/utils/hooks/useRovingFocus.test.d.ts.map +1 -0
  77. package/dist/utils/hooks/useRovingFocus.test.js +59 -0
  78. package/dist/utils/hooks/useRovingFocus.test.js.map +1 -0
  79. package/dist/utils/spacedWidths.d.ts +3 -0
  80. package/dist/utils/spacedWidths.d.ts.map +1 -0
  81. package/dist/utils/spacedWidths.js +28 -0
  82. package/dist/utils/spacedWidths.js.map +1 -0
  83. package/dist/utils/spacedWidths.test.d.ts +2 -0
  84. package/dist/utils/spacedWidths.test.d.ts.map +1 -0
  85. package/dist/utils/spacedWidths.test.js +17 -0
  86. package/dist/utils/spacedWidths.test.js.map +1 -0
  87. package/package.json +1 -1
  88. package/src/components/articleCard/ArticleCard.stories.tsx +17 -12
  89. package/src/components/articleCard/ArticleCard.tsx +9 -9
  90. package/src/components/{icoText/IcoText.stories.tsx → iconText/IconText.stories.tsx} +112 -112
  91. package/src/components/{icoText/IcoText.test.tsx → iconText/IconText.test.tsx} +10 -10
  92. package/src/components/{icoText/IcoText.tsx → iconText/IconText.tsx} +27 -20
  93. package/src/components/modal/Modal.tsx +5 -1
  94. package/src/components/tag/Tag.stories.tsx +4 -4
  95. package/src/components/tag/Tag.test.tsx +62 -5
  96. package/src/components/tag/Tag.tsx +61 -3
  97. package/src/components/tag/tag.scss +80 -9
  98. package/src/components/tagList/TagList.stories.tsx +564 -0
  99. package/src/components/tagList/TagList.test.tsx +342 -0
  100. package/src/components/tagList/TagList.tsx +296 -0
  101. package/src/components/tagList/tagList.scss +56 -0
  102. package/src/components/tagList/useTagListCollapsedLayout.ts +83 -0
  103. package/src/components/tagList/useVisibleTags.ts +74 -0
  104. package/src/index.scss +2 -1
  105. package/src/index.ts +3 -1
  106. package/src/tokens.scss +2 -1
  107. package/src/utils/hooks/useElementWidth.ts +39 -0
  108. package/src/utils/hooks/useMeasuredChildWidths.ts +39 -0
  109. package/src/utils/hooks/useRovingFocus.test.tsx +105 -0
  110. package/src/utils/hooks/useRovingFocus.ts +163 -0
  111. package/src/utils/spacedWidths.test.ts +20 -0
  112. package/src/utils/spacedWidths.ts +37 -0
  113. package/dist/components/icoText/IcoText.d.ts +0 -37
  114. package/dist/components/icoText/IcoText.d.ts.map +0 -1
  115. package/dist/components/icoText/IcoText.js +0 -29
  116. package/dist/components/icoText/IcoText.js.map +0 -1
  117. package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
  118. package/dist/components/icoText/IcoText.stories.js.map +0 -1
  119. package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
  120. package/dist/components/icoText/IcoText.test.js.map +0 -1
  121. /package/src/components/{icoText/icoText.scss → iconText/iconText.scss} +0 -0
@@ -37,7 +37,7 @@ const USAGE_GUIDANCE = [
37
37
  '|---|---|',
38
38
  '| Displaying a metric or statistic | [`KPICard`](?path=/docs/components-card-kpicard--docs) |',
39
39
  '| Full control over interior layout | [`Card`](?path=/docs/components-card--docs) directly |',
40
- '| Icon + text without a card shell | [`IcoText`](?path=/docs/components-icotext--docs) |',
40
+ '| Icon + text without a card shell | [`IconText`](?path=/docs/components-icontext--docs) |',
41
41
  '| Selecting from a list (radio/checkbox semantics) | `RadioGroup` or selection list pattern |',
42
42
  ].join('\n');
43
43
 
@@ -64,7 +64,7 @@ const DEVELOPER_NOTES = [
64
64
  '### Accessibility',
65
65
  '',
66
66
  '- **Linked mode**: the `<a>` wrapping `title` is the accessible link target.',
67
- ' If the title text is not self-descriptive, use `aria-label` to override the accessible name.',
67
+ ' Use self-descriptive link text in `title`, because the outer `aria-label` does not rename the inner link.',
68
68
  '- **Shell mode**: `aria-label` or `aria-labelledby` is required when `onClick` is provided.',
69
69
  '- **Decorative icons**: omit `iconScreenReaderText` — the icon is `aria-hidden="true"` automatically.',
70
70
  '',
@@ -81,7 +81,7 @@ const DEVELOPER_NOTES = [
81
81
  const RELATED_COMPONENTS = [
82
82
  '## Related components',
83
83
  '',
84
- '[Card](?path=/docs/components-card--docs) · [KPICard](?path=/docs/components-card-kpicard--docs) · [IcoText](?path=/docs/components-icotext--docs) · [Tag](?path=/docs/components-tag--docs)',
84
+ '[Card](?path=/docs/components-card--docs) · [KPICard](?path=/docs/components-card-kpicard--docs) · [IconText](?path=/docs/components-icontext--docs) · [Tag](?path=/docs/components-tag--docs)',
85
85
  ].join('\n');
86
86
 
87
87
  const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
@@ -137,7 +137,7 @@ const meta = {
137
137
  'icon': {
138
138
  control: { type: 'select' },
139
139
  options: [undefined, 'eye', 'guardians', 'date', 'book-open', 'file', 'settings', 'info', 'triangle-alert'],
140
- description: 'Icon displayed in the left rail via `IcoText.Icon`. Omit for icon-free layout.',
140
+ description: 'Icon displayed in the left rail via `IconText.Icon`. Omit for icon-free layout.',
141
141
  table: { type: { summary: 'IconName' } },
142
142
  },
143
143
  'iconColor': {
@@ -176,8 +176,13 @@ const meta = {
176
176
  description: 'Accessible name. Required on interactive shell cards (when `onClick` is provided).',
177
177
  table: { type: { summary: 'string' } },
178
178
  },
179
- 'href': {
179
+ 'aria-labelledby': {
180
180
  control: 'text',
181
+ description: 'Alternative accessible name for interactive shell cards when a visible label already exists in the card content.',
182
+ table: { type: { summary: 'string' } },
183
+ },
184
+ 'href': {
185
+ control: false,
181
186
  description: 'URL. Mutually exclusive with `onClick`. When provided with `title` + `!disabled`, the title renders as a navigable `<a>`.',
182
187
  table: { type: { summary: 'string' } },
183
188
  },
@@ -190,7 +195,7 @@ const meta = {
190
195
  } satisfies Meta<typeof ArticleCard>;
191
196
 
192
197
  export default meta;
193
- type Story = StoryObj<typeof ArticleCard>;
198
+ type Story = StoryObj<typeof meta>;
194
199
 
195
200
  // ---------------------------------------------------------------------------
196
201
  // Helper: attach a per-story description to docs
@@ -356,7 +361,7 @@ function FullCompositionExample() {
356
361
  iconColor="var(--color-semantic-warning-600)"
357
362
  tagText="Pending review"
358
363
  tagColor="yellow"
359
- onClick={() => console.log('navigate to report')}
364
+ onClick={() => {}}
360
365
  aria-label="Spring term behaviour report"
361
366
  />
362
367
  );
@@ -375,7 +380,7 @@ export default FullCompositionExample;
375
380
  iconColor="var(--color-semantic-warning-600)"
376
381
  tagText="Pending review"
377
382
  tagColor="yellow"
378
- onClick={fn()}
383
+ onClick={() => {}}
379
384
  aria-label="Spring term behaviour report"
380
385
  />
381
386
  </div>
@@ -440,7 +445,7 @@ import { ArticleCard } from '@arbor-education/design-system.components';
440
445
  function InteractiveShellExample() {
441
446
  return (
442
447
  <ArticleCard
443
- onClick={() => console.log('card clicked')}
448
+ onClick={() => {}}
444
449
  aria-label="Year 10 progress report"
445
450
  title="Year 10 progress report"
446
451
  paragraph="Mid-year review — targets, attainment, and next steps."
@@ -456,7 +461,7 @@ export default InteractiveShellExample;
456
461
  render: () => (
457
462
  <div style={{ maxWidth: '400px' }}>
458
463
  <ArticleCard
459
- onClick={fn()}
464
+ onClick={() => {}}
460
465
  aria-label="Year 10 progress report"
461
466
  title="Year 10 progress report"
462
467
  paragraph="Mid-year review — targets, attainment, and next steps."
@@ -498,7 +503,7 @@ export default DisabledExample;
498
503
  render: () => (
499
504
  <div style={{ maxWidth: '400px' }}>
500
505
  <ArticleCard
501
- onClick={fn()}
506
+ onClick={() => {}}
502
507
  aria-label="Summer term report — not yet available"
503
508
  title="Summer term report"
504
509
  paragraph="This report will be available at the end of term."
@@ -508,7 +513,7 @@ export default DisabledExample;
508
513
  </div>
509
514
  ),
510
515
  },
511
- '`disabled=true` dims the card and suppresses all interaction. In interactive shell mode, `onClick` does not fire. The card is removed from the tab order.',
516
+ '`disabled=true` dims the card and suppresses activation. In interactive shell mode, `onClick` no longer fires, but the card still renders with `role="button"`, `tabIndex={0}`, and `aria-disabled={true}`.',
512
517
  );
513
518
 
514
519
  export const LinkedDisabled: Story = withDescription(
@@ -1,16 +1,16 @@
1
1
  import classNames from 'classnames';
2
- import type { IcoTextIconProps } from 'Components/icoText/IcoText';
2
+ import type { IconTextIconProps } from 'Components/iconText/IconText';
3
3
  import type { TagColor } from 'Components/tag/Tag';
4
4
  import { Tag } from 'Components/tag/Tag';
5
5
  import type { IconName } from 'Components/icon/allowedIcons';
6
6
  import { Card, getCardInteractionProps } from 'Components/card/Card';
7
- import { IcoText } from 'Components/icoText/IcoText';
7
+ import { IconText } from 'Components/iconText/IconText';
8
8
 
9
9
  type ArticleCardBaseProps = {
10
10
  className?: string;
11
11
  paragraph?: React.ReactNode;
12
12
  icon?: IconName;
13
- iconColor?: IcoTextIconProps['color'];
13
+ iconColor?: IconTextIconProps['color'];
14
14
  disabled?: boolean;
15
15
  tagText?: string;
16
16
  tagColor?: TagColor;
@@ -62,16 +62,16 @@ export const ArticleCard = (props: ArticleCardProps): React.JSX.Element => {
62
62
 
63
63
  const content = (
64
64
  <article className="ds-article-card">
65
- <IcoText>
65
+ <IconText>
66
66
  {icon && (
67
- <IcoText.Icon
67
+ <IconText.Icon
68
68
  color={iconColor}
69
69
  name={icon}
70
70
  screenReaderText={iconScreenReaderText}
71
71
  />
72
72
  )}
73
73
  {title && (
74
- <IcoText.Heading>
74
+ <IconText.Heading>
75
75
  {hasPrimaryLink
76
76
  ? (
77
77
  <a className="ds-article-card__primary-link" href={href}>
@@ -79,11 +79,11 @@ export const ArticleCard = (props: ArticleCardProps): React.JSX.Element => {
79
79
  </a>
80
80
  )
81
81
  : title}
82
- </IcoText.Heading>
82
+ </IconText.Heading>
83
83
  )}
84
- {paragraph && <IcoText.Paragraph>{paragraph}</IcoText.Paragraph>}
84
+ {paragraph && <IconText.Paragraph>{paragraph}</IconText.Paragraph>}
85
85
  {tagText && <Tag color={tagColor}>{tagText}</Tag>}
86
- </IcoText>
86
+ </IconText>
87
87
  </article>
88
88
  );
89
89
 
@@ -1,21 +1,21 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
1
  import {
3
2
  Controls,
4
3
  Heading as DocHeading,
5
- Markdown,
6
4
  Primary as DocPrimary,
5
+ Markdown,
7
6
  Stories,
8
7
  Subtitle,
9
8
  Title,
10
9
  } from '@storybook/addon-docs/blocks';
11
- import { IcoText } from './IcoText.js';
10
+ import type { Meta, StoryObj } from '@storybook/react-vite';
11
+ import { IconText } from './IconText.js';
12
12
 
13
13
  // ---------------------------------------------------------------------------
14
14
  // Docs page content
15
15
  // ---------------------------------------------------------------------------
16
16
 
17
17
  const DESCRIPTION_INTRO = [
18
- '`IcoText` is a compound layout component that pairs an icon in a left rail with a heading and/or paragraph.',
18
+ '`IconText` is a compound layout component that pairs an icon in a left rail with a heading and/or paragraph.',
19
19
  'It is the canonical icon-plus-text primitive in this design system — used internally by `ArticleCard`',
20
20
  'and available for direct use in any context that needs an icon anchoring labelled content.',
21
21
  ].join(' ');
@@ -60,26 +60,26 @@ const USAGE_GUIDANCE = [
60
60
  '| Content inside a navigable card | [`ArticleCard`](?path=/docs/components-card-articlecard--docs) |',
61
61
  '| Icon with no text | [`Icon`](?path=/docs/components-icon--docs) directly |',
62
62
  '| Long-form prose | Semantic HTML `<p>` with a separate heading |',
63
- '| Interactive element | Wrap `IcoText` inside a `Card` or `Button` — never add `onClick` to `IcoText` itself |',
63
+ '| Interactive element | Wrap `IconText` inside a `Card` or `Button` — never add `onClick` to `IconText` itself |',
64
64
  ].join('\n');
65
65
 
66
66
  const DEVELOPER_NOTES = [
67
67
  '### Critical usage patterns',
68
68
  '',
69
- '**`IcoText.Icon` is automatically hoisted to the left rail** regardless of position in JSX.',
70
- 'The root inspects `children`, pulls out any `IcoText.Icon` elements into a flex-shrink-0 left column,',
69
+ '**`IconText.Icon` is automatically hoisted to the left rail** regardless of position in JSX.',
70
+ 'The root inspects `children`, pulls out any `IconText.Icon` elements into a flex-shrink-0 left column,',
71
71
  'and places all other children (Heading, Paragraph) in the content column.',
72
72
  '',
73
73
  '```tsx',
74
- '<IcoText>',
75
- ' <IcoText.Icon name="guardians" />',
76
- ' <IcoText.Heading>Year 9 Attendance</IcoText.Heading>',
77
- ' <IcoText.Paragraph>28 pupils · 94.2% this term</IcoText.Paragraph>',
78
- '</IcoText>',
74
+ '<IconText>',
75
+ ' <IconText.Icon name="guardians" />',
76
+ ' <IconText.Heading>Year 9 Attendance</IconText.Heading>',
77
+ ' <IconText.Paragraph>28 pupils · 94.2% this term</IconText.Paragraph>',
78
+ '</IconText>',
79
79
  '```',
80
80
  '',
81
- '**`IcoText.Heading` always renders as `<h4>`.** There is no `level` prop. Verify your document',
82
- 'heading hierarchy is correct before using `IcoText` as a standalone heading.',
81
+ '**`IconText.Heading` always renders as `<h4>`.** There is no `level` prop. Verify your document',
82
+ 'heading hierarchy is correct before using `IconText` as a standalone heading.',
83
83
  '',
84
84
  '**Omit `screenReaderText` for decorative icons.** The icon is rendered with `aria-hidden="true"`',
85
85
  'when `screenReaderText` is absent. The heading or surrounding text should carry the accessible meaning.',
@@ -90,16 +90,16 @@ const DEVELOPER_NOTES = [
90
90
  '',
91
91
  '- Decorative icons (no `screenReaderText`): `aria-hidden="true"` — screen readers skip them',
92
92
  '- Meaningful icons: provide `screenReaderText` so the icon has an accessible label',
93
- '- `IcoText.Heading` renders `<h4>` — verify your document heading hierarchy',
94
- '- `IcoText.Paragraph` renders `<p>` — semantically appropriate for supporting text',
93
+ '- `IconText.Heading` renders `<h4>` — verify your document heading hierarchy',
94
+ '- `IconText.Paragraph` renders `<p>` — semantically appropriate for supporting text',
95
95
  '',
96
96
  '---',
97
97
  '',
98
98
  '### TypeScript types',
99
99
  '',
100
100
  '```ts',
101
- "import { IcoText } from '@arbor-education/design-system.components';",
102
- "import type { IcoTextProps, IcoTextIconProps, IcoTextHeadingProps, IcoTextParagraphProps } from '@arbor-education/design-system.components';",
101
+ "import { IconText } from '@arbor-education/design-system.components';",
102
+ "import type { IconTextProps, IconTextIconProps, IconTextHeadingProps, IconTextParagraphProps } from '@arbor-education/design-system.components';",
103
103
  '```',
104
104
  ].join('\n');
105
105
 
@@ -115,7 +115,7 @@ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tw
115
115
  // Custom DocsPage
116
116
  // ---------------------------------------------------------------------------
117
117
 
118
- function IcoTextDocsPage() {
118
+ function IconTextDocsPage() {
119
119
  return (
120
120
  <>
121
121
  <Title />
@@ -125,11 +125,11 @@ function IcoTextDocsPage() {
125
125
  <Markdown>{PROPS_INTRO}</Markdown>
126
126
  <DocPrimary />
127
127
  <Controls />
128
- <DocHeading>IcoText.Icon props</DocHeading>
128
+ <DocHeading>IconText.Icon props</DocHeading>
129
129
  <Markdown>{ICON_PROPS}</Markdown>
130
- <DocHeading>IcoText.Heading props</DocHeading>
130
+ <DocHeading>IconText.Heading props</DocHeading>
131
131
  <Markdown>{HEADING_PROPS}</Markdown>
132
- <DocHeading>IcoText.Paragraph props</DocHeading>
132
+ <DocHeading>IconText.Paragraph props</DocHeading>
133
133
  <Markdown>{PARAGRAPH_PROPS}</Markdown>
134
134
  <DocHeading>Usage guidance</DocHeading>
135
135
  <Markdown>{USAGE_GUIDANCE}</Markdown>
@@ -147,12 +147,12 @@ function IcoTextDocsPage() {
147
147
  // ---------------------------------------------------------------------------
148
148
 
149
149
  const meta = {
150
- title: 'Components/IcoText',
151
- component: IcoText,
150
+ title: 'Components/IconText',
151
+ component: IconText,
152
152
  tags: ['autodocs'],
153
153
  parameters: {
154
154
  layout: 'padded',
155
- docs: { page: IcoTextDocsPage },
155
+ docs: { page: IconTextDocsPage },
156
156
  },
157
157
  argTypes: {
158
158
  className: {
@@ -162,14 +162,14 @@ const meta = {
162
162
  },
163
163
  children: {
164
164
  control: false,
165
- description: 'Compose using `IcoText.Icon`, `IcoText.Heading`, and `IcoText.Paragraph` sub-components.',
165
+ description: 'Compose using `IconText.Icon`, `IconText.Heading`, and `IconText.Paragraph` sub-components.',
166
166
  table: { type: { summary: 'ReactNode' } },
167
167
  },
168
168
  },
169
- } satisfies Meta<typeof IcoText>;
169
+ } satisfies Meta<typeof IconText>;
170
170
 
171
171
  export default meta;
172
- type Story = StoryObj<typeof IcoText>;
172
+ type Story = StoryObj<typeof meta>;
173
173
 
174
174
  // ---------------------------------------------------------------------------
175
175
  // Helper: attach a per-story description to docs
@@ -194,11 +194,11 @@ const IconSizesTemplate = () => (
194
194
  <p className="ds-text" style={{ margin: '0 0 var(--spacing-small)', color: 'var(--color-grey-600)' }}>
195
195
  {`size={${size}}`}
196
196
  </p>
197
- <IcoText>
198
- <IcoText.Icon name="guardians" size={size} />
199
- <IcoText.Heading>Year 9 Attendance</IcoText.Heading>
200
- <IcoText.Paragraph>28 pupils · 94.2% average this term</IcoText.Paragraph>
201
- </IcoText>
197
+ <IconText>
198
+ <IconText.Icon name="guardians" size={size} />
199
+ <IconText.Heading>Year 9 Attendance</IconText.Heading>
200
+ <IconText.Paragraph>28 pupils · 94.2% average this term</IconText.Paragraph>
201
+ </IconText>
202
202
  </div>
203
203
  ))}
204
204
  </div>
@@ -216,23 +216,23 @@ const IconColorsTemplate = () => (
216
216
  <p className="ds-text" style={{ margin: '0 0 var(--spacing-small)', color: 'var(--color-grey-600)' }}>
217
217
  {label}
218
218
  </p>
219
- <IcoText>
220
- <IcoText.Icon name="triangle-alert" color={color} />
221
- <IcoText.Heading>Attendance alert</IcoText.Heading>
222
- <IcoText.Paragraph>3 pupils below 90% this half-term</IcoText.Paragraph>
223
- </IcoText>
219
+ <IconText>
220
+ <IconText.Icon name="triangle-alert" color={color} />
221
+ <IconText.Heading>Attendance alert</IconText.Heading>
222
+ <IconText.Paragraph>3 pupils below 90% this half-term</IconText.Paragraph>
223
+ </IconText>
224
224
  </div>
225
225
  ))}
226
226
  </div>
227
227
  );
228
228
 
229
229
  const MultipleParagraphsTemplate = () => (
230
- <IcoText>
231
- <IcoText.Icon name="date" />
232
- <IcoText.Heading>Parents&apos; evening — Tuesday 14 May</IcoText.Heading>
233
- <IcoText.Paragraph>4:00 pm – 7:00 pm · Main Hall</IcoText.Paragraph>
234
- <IcoText.Paragraph>Appointments available every 10 minutes. Please arrive 5 minutes early.</IcoText.Paragraph>
235
- </IcoText>
230
+ <IconText>
231
+ <IconText.Icon name="date" />
232
+ <IconText.Heading>Parents&apos; evening — Tuesday 14 May</IconText.Heading>
233
+ <IconText.Paragraph>4:00 pm – 7:00 pm · Main Hall</IconText.Paragraph>
234
+ <IconText.Paragraph>Appointments available every 10 minutes. Please arrive 5 minutes early.</IconText.Paragraph>
235
+ </IconText>
236
236
  );
237
237
 
238
238
  // ---------------------------------------------------------------------------
@@ -242,14 +242,14 @@ const MultipleParagraphsTemplate = () => (
242
242
  export const Default: Story = withDescription(
243
243
  {
244
244
  render: () => (
245
- <IcoText>
246
- <IcoText.Icon name="guardians" />
247
- <IcoText.Heading>Year 9 Attendance</IcoText.Heading>
248
- <IcoText.Paragraph>28 pupils · 94.2% average this term</IcoText.Paragraph>
249
- </IcoText>
245
+ <IconText>
246
+ <IconText.Icon name="guardians" />
247
+ <IconText.Heading>Year 9 Attendance</IconText.Heading>
248
+ <IconText.Paragraph>28 pupils · 94.2% average this term</IconText.Paragraph>
249
+ </IconText>
250
250
  ),
251
251
  },
252
- 'The full canonical composition — `IcoText.Icon` in the left rail, `IcoText.Heading` and `IcoText.Paragraph` in the content column.',
252
+ 'The full canonical composition — `IconText.Icon` in the left rail, `IconText.Heading` and `IconText.Paragraph` in the content column.',
253
253
  );
254
254
 
255
255
  export const HeadingOnly: Story = withDescription(
@@ -260,14 +260,14 @@ export const HeadingOnly: Story = withDescription(
260
260
  source: {
261
261
  language: 'tsx',
262
262
  code: `
263
- import { IcoText } from '@arbor-education/design-system.components';
263
+ import { IconText } from '@arbor-education/design-system.components';
264
264
 
265
265
  function HeadingOnlyExample() {
266
266
  return (
267
- <IcoText>
268
- <IcoText.Icon name="book-open" />
269
- <IcoText.Heading>Assessment policy</IcoText.Heading>
270
- </IcoText>
267
+ <IconText>
268
+ <IconText.Icon name="book-open" />
269
+ <IconText.Heading>Assessment policy</IconText.Heading>
270
+ </IconText>
271
271
  );
272
272
  }
273
273
  export default HeadingOnlyExample;
@@ -276,10 +276,10 @@ export default HeadingOnlyExample;
276
276
  },
277
277
  },
278
278
  render: () => (
279
- <IcoText>
280
- <IcoText.Icon name="book-open" />
281
- <IcoText.Heading>Assessment policy</IcoText.Heading>
282
- </IcoText>
279
+ <IconText>
280
+ <IconText.Icon name="book-open" />
281
+ <IconText.Heading>Assessment policy</IconText.Heading>
282
+ </IconText>
283
283
  ),
284
284
  },
285
285
  'Icon + Heading with no Paragraph — the compact pattern for short navigational labels.',
@@ -293,14 +293,14 @@ export const WithoutIcon: Story = withDescription(
293
293
  source: {
294
294
  language: 'tsx',
295
295
  code: `
296
- import { IcoText } from '@arbor-education/design-system.components';
296
+ import { IconText } from '@arbor-education/design-system.components';
297
297
 
298
298
  function WithoutIconExample() {
299
299
  return (
300
- <IcoText>
301
- <IcoText.Heading>Behaviour report</IcoText.Heading>
302
- <IcoText.Paragraph>No incidents recorded this week. All targets met.</IcoText.Paragraph>
303
- </IcoText>
300
+ <IconText>
301
+ <IconText.Heading>Behaviour report</IconText.Heading>
302
+ <IconText.Paragraph>No incidents recorded this week. All targets met.</IconText.Paragraph>
303
+ </IconText>
304
304
  );
305
305
  }
306
306
  export default WithoutIconExample;
@@ -309,13 +309,13 @@ export default WithoutIconExample;
309
309
  },
310
310
  },
311
311
  render: () => (
312
- <IcoText>
313
- <IcoText.Heading>Behaviour report</IcoText.Heading>
314
- <IcoText.Paragraph>No incidents recorded this week. All targets met.</IcoText.Paragraph>
315
- </IcoText>
312
+ <IconText>
313
+ <IconText.Heading>Behaviour report</IconText.Heading>
314
+ <IconText.Paragraph>No incidents recorded this week. All targets met.</IconText.Paragraph>
315
+ </IconText>
316
316
  ),
317
317
  },
318
- 'Heading + Paragraph with no icon. The left rail disappears cleanly — `IcoText.Icon` is entirely optional.',
318
+ 'Heading + Paragraph with no icon. The left rail disappears cleanly — `IconText.Icon` is entirely optional.',
319
319
  );
320
320
 
321
321
  export const IconWithScreenReaderText: Story = withDescription(
@@ -326,19 +326,19 @@ export const IconWithScreenReaderText: Story = withDescription(
326
326
  source: {
327
327
  language: 'tsx',
328
328
  code: `
329
- import { IcoText } from '@arbor-education/design-system.components';
329
+ import { IconText } from '@arbor-education/design-system.components';
330
330
 
331
331
  function IconWithScreenReaderTextExample() {
332
332
  return (
333
- <IcoText>
334
- <IcoText.Icon
333
+ <IconText>
334
+ <IconText.Icon
335
335
  name="triangle-alert"
336
336
  color="var(--color-semantic-warning-600)"
337
337
  screenReaderText="Warning"
338
338
  />
339
- <IcoText.Heading>Attendance below target</IcoText.Heading>
340
- <IcoText.Paragraph>Alice Johnson — 78.4% this term. Intervention recommended.</IcoText.Paragraph>
341
- </IcoText>
339
+ <IconText.Heading>Attendance below target</IconText.Heading>
340
+ <IconText.Paragraph>Alice Johnson — 78.4% this term. Intervention recommended.</IconText.Paragraph>
341
+ </IconText>
342
342
  );
343
343
  }
344
344
  export default IconWithScreenReaderTextExample;
@@ -347,11 +347,11 @@ export default IconWithScreenReaderTextExample;
347
347
  },
348
348
  },
349
349
  render: () => (
350
- <IcoText>
351
- <IcoText.Icon name="triangle-alert" color="var(--color-semantic-warning-600)" screenReaderText="Warning" />
352
- <IcoText.Heading>Attendance below target</IcoText.Heading>
353
- <IcoText.Paragraph>Alice Johnson — 78.4% this term. Intervention recommended.</IcoText.Paragraph>
354
- </IcoText>
350
+ <IconText>
351
+ <IconText.Icon name="triangle-alert" color="var(--color-semantic-warning-600)" screenReaderText="Warning" />
352
+ <IconText.Heading>Attendance below target</IconText.Heading>
353
+ <IconText.Paragraph>Alice Johnson — 78.4% this term. Intervention recommended.</IconText.Paragraph>
354
+ </IconText>
355
355
  ),
356
356
  },
357
357
  'When the icon carries meaning not conveyed by the heading text, provide `screenReaderText`. Without it the icon is `aria-hidden="true"`. Here the warning triangle conveys severity — screen readers announce "Warning, Attendance below target".',
@@ -365,26 +365,26 @@ export const IconSizes: Story = withDescription(
365
365
  source: {
366
366
  language: 'tsx',
367
367
  code: `
368
- import { IcoText } from '@arbor-education/design-system.components';
368
+ import { IconText } from '@arbor-education/design-system.components';
369
369
 
370
370
  function IconSizesExample() {
371
371
  return (
372
372
  <>
373
- <IcoText>
374
- <IcoText.Icon name="guardians" size={12} />
375
- <IcoText.Heading>size 12</IcoText.Heading>
376
- <IcoText.Paragraph>Use in very compact contexts</IcoText.Paragraph>
377
- </IcoText>
378
- <IcoText>
379
- <IcoText.Icon name="guardians" size={16} />
380
- <IcoText.Heading>size 16</IcoText.Heading>
381
- <IcoText.Paragraph>Use in moderately compact contexts</IcoText.Paragraph>
382
- </IcoText>
383
- <IcoText>
384
- <IcoText.Icon name="guardians" size={24} />
385
- <IcoText.Heading>size 24 (default)</IcoText.Heading>
386
- <IcoText.Paragraph>Standard — aligns best with h4 line-height</IcoText.Paragraph>
387
- </IcoText>
373
+ <IconText>
374
+ <IconText.Icon name="guardians" size={12} />
375
+ <IconText.Heading>size 12</IconText.Heading>
376
+ <IconText.Paragraph>Use in very compact contexts</IconText.Paragraph>
377
+ </IconText>
378
+ <IconText>
379
+ <IconText.Icon name="guardians" size={16} />
380
+ <IconText.Heading>size 16</IconText.Heading>
381
+ <IconText.Paragraph>Use in moderately compact contexts</IconText.Paragraph>
382
+ </IconText>
383
+ <IconText>
384
+ <IconText.Icon name="guardians" size={24} />
385
+ <IconText.Heading>size 24 (default)</IconText.Heading>
386
+ <IconText.Paragraph>Standard — aligns best with h4 line-height</IconText.Paragraph>
387
+ </IconText>
388
388
  </>
389
389
  );
390
390
  }
@@ -395,7 +395,7 @@ export default IconSizesExample;
395
395
  },
396
396
  render: IconSizesTemplate,
397
397
  },
398
- 'All three supported icon sizes — 12, 16, and 24 (default). Use smaller sizes only in compact contexts such as table cells or dense lists. The `24` default aligns best with `IcoText.Heading`\'s h4 line-height.',
398
+ 'All three supported icon sizes — 12, 16, and 24 (default). Use smaller sizes only in compact contexts such as table cells or dense lists. The `24` default aligns best with `IconText.Heading`\'s h4 line-height.',
399
399
  );
400
400
 
401
401
  export const IconColors: Story = withDescription(
@@ -406,15 +406,15 @@ export const IconColors: Story = withDescription(
406
406
  source: {
407
407
  language: 'tsx',
408
408
  code: `
409
- import { IcoText } from '@arbor-education/design-system.components';
409
+ import { IconText } from '@arbor-education/design-system.components';
410
410
 
411
411
  function IconColorsExample() {
412
412
  return (
413
- <IcoText>
414
- <IcoText.Icon name="triangle-alert" color="var(--color-semantic-warning-600)" />
415
- <IcoText.Heading>Attendance alert</IcoText.Heading>
416
- <IcoText.Paragraph>3 pupils below 90% this half-term</IcoText.Paragraph>
417
- </IcoText>
413
+ <IconText>
414
+ <IconText.Icon name="triangle-alert" color="var(--color-semantic-warning-600)" />
415
+ <IconText.Heading>Attendance alert</IconText.Heading>
416
+ <IconText.Paragraph>3 pupils below 90% this half-term</IconText.Paragraph>
417
+ </IconText>
418
418
  );
419
419
  }
420
420
  export default IconColorsExample;
@@ -424,7 +424,7 @@ export default IconColorsExample;
424
424
  },
425
425
  render: IconColorsTemplate,
426
426
  },
427
- 'The `color` prop on `IcoText.Icon` accepts any CSS value — always use design token variables, never hardcoded hex. Semantic tokens (`--color-semantic-*`) are most appropriate for status-indicating icons.',
427
+ 'The `color` prop on `IconText.Icon` accepts any CSS value — always use design token variables, never hardcoded hex. Semantic tokens (`--color-semantic-*`) are most appropriate for status-indicating icons.',
428
428
  );
429
429
 
430
430
  export const MultipleParagraphs: Story = withDescription(
@@ -435,16 +435,16 @@ export const MultipleParagraphs: Story = withDescription(
435
435
  source: {
436
436
  language: 'tsx',
437
437
  code: `
438
- import { IcoText } from '@arbor-education/design-system.components';
438
+ import { IconText } from '@arbor-education/design-system.components';
439
439
 
440
440
  function MultipleParagraphsExample() {
441
441
  return (
442
- <IcoText>
443
- <IcoText.Icon name="calendar" />
444
- <IcoText.Heading>Parents' evening — Tuesday 14 May</IcoText.Heading>
445
- <IcoText.Paragraph>4:00 pm – 7:00 pm · Main Hall</IcoText.Paragraph>
446
- <IcoText.Paragraph>Appointments every 10 minutes. Please arrive 5 minutes early.</IcoText.Paragraph>
447
- </IcoText>
442
+ <IconText>
443
+ <IconText.Icon name="date" />
444
+ <IconText.Heading>Parents' evening — Tuesday 14 May</IconText.Heading>
445
+ <IconText.Paragraph>4:00 pm – 7:00 pm · Main Hall</IconText.Paragraph>
446
+ <IconText.Paragraph>Appointments every 10 minutes. Please arrive 5 minutes early.</IconText.Paragraph>
447
+ </IconText>
448
448
  );
449
449
  }
450
450
  export default MultipleParagraphsExample;
@@ -454,5 +454,5 @@ export default MultipleParagraphsExample;
454
454
  },
455
455
  render: MultipleParagraphsTemplate,
456
456
  },
457
- 'Multiple `IcoText.Paragraph` sub-components stack vertically in the content column. Use for supplementary detail lines below the primary heading.',
457
+ 'Multiple `IconText.Paragraph` sub-components stack vertically in the content column. Use for supplementary detail lines below the primary heading.',
458
458
  );
@@ -1,25 +1,25 @@
1
- import { render, screen, within } from '@testing-library/react';
2
1
  import '@testing-library/jest-dom/vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
3
  import { describe, expect, test } from 'vitest';
4
- import { IcoText } from './IcoText.js';
4
+ import { IconText } from './IconText.js';
5
5
 
6
- describe('IcoText', () => {
6
+ describe('IconText', () => {
7
7
  test('renders icon children before content children and keeps content in the content wrapper', () => {
8
8
  const { container } = render(
9
- <IcoText>
10
- <IcoText.Heading>Article title</IcoText.Heading>
11
- <IcoText.Icon name="eye" screenReaderText="Views" />
12
- <IcoText.Paragraph>Helpful supporting copy</IcoText.Paragraph>
9
+ <IconText>
10
+ <IconText.Heading>Article title</IconText.Heading>
11
+ <IconText.Icon name="eye" screenReaderText="Views" />
12
+ <IconText.Paragraph>Helpful supporting copy</IconText.Paragraph>
13
13
  <span>Metadata</span>
14
- <IcoText.Icon name="eye" screenReaderText="More views" />
15
- </IcoText>,
14
+ <IconText.Icon name="eye" screenReaderText="More views" />
15
+ </IconText>,
16
16
  );
17
17
 
18
18
  const root = container.querySelector<HTMLDivElement>('.ds-ico-text');
19
19
  const content = container.querySelector<HTMLDivElement>('.ds-ico-text__content');
20
20
 
21
21
  if (!content) {
22
- throw new Error('Expected IcoText content wrapper to exist');
22
+ throw new Error('Expected IconText content wrapper to exist');
23
23
  }
24
24
 
25
25
  const iconElements = Array.from(root?.querySelectorAll(':scope > .ds-ico-text__icon') ?? []);