@arbor-education/design-system.components 0.21.0 → 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.
- package/CHANGELOG.md +18 -0
- package/component-library.md +15 -14
- package/dist/components/articleCard/ArticleCard.d.ts +2 -2
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.js +3 -3
- package/dist/components/articleCard/ArticleCard.js.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.d.ts +11 -3
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.js +16 -11
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
- package/dist/components/iconText/IconText.d.ts +43 -0
- package/dist/components/iconText/IconText.d.ts.map +1 -0
- package/dist/components/iconText/IconText.js +29 -0
- package/dist/components/iconText/IconText.js.map +1 -0
- package/dist/components/{icoText/IcoText.stories.d.ts → iconText/IconText.stories.d.ts} +8 -9
- package/dist/components/iconText/IconText.stories.d.ts.map +1 -0
- package/dist/components/{icoText/IcoText.stories.js → iconText/IconText.stories.js} +81 -81
- package/dist/components/iconText/IconText.stories.js.map +1 -0
- package/dist/components/iconText/IconText.test.d.ts +2 -0
- package/dist/components/iconText/IconText.test.d.ts.map +1 -0
- package/dist/components/{icoText/IcoText.test.js → iconText/IconText.test.js} +6 -6
- package/dist/components/iconText/IconText.test.js.map +1 -0
- package/dist/components/modal/Modal.d.ts +1 -0
- package/dist/components/modal/Modal.d.ts.map +1 -1
- package/dist/components/modal/Modal.js +2 -2
- package/dist/components/modal/Modal.js.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +2 -2
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.test.js +6 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.test.js.map +1 -1
- package/dist/components/tag/Tag.d.ts +14 -1
- package/dist/components/tag/Tag.d.ts.map +1 -1
- package/dist/components/tag/Tag.js +9 -3
- package/dist/components/tag/Tag.js.map +1 -1
- package/dist/components/tag/Tag.stories.d.ts +1 -1
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +3 -3
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/components/tag/Tag.test.js +36 -5
- package/dist/components/tag/Tag.test.js.map +1 -1
- package/dist/components/tagList/TagList.d.ts +49 -0
- package/dist/components/tagList/TagList.d.ts.map +1 -0
- package/dist/components/tagList/TagList.js +114 -0
- package/dist/components/tagList/TagList.js.map +1 -0
- package/dist/components/tagList/TagList.stories.d.ts +130 -0
- package/dist/components/tagList/TagList.stories.d.ts.map +1 -0
- package/dist/components/tagList/TagList.stories.js +443 -0
- package/dist/components/tagList/TagList.stories.js.map +1 -0
- package/dist/components/{icoText/IcoText.test.d.ts → tagList/TagList.test.d.ts} +1 -1
- package/dist/components/tagList/TagList.test.d.ts.map +1 -0
- package/dist/components/tagList/TagList.test.js +246 -0
- package/dist/components/tagList/TagList.test.js.map +1 -0
- package/dist/components/tagList/useTagListCollapsedLayout.d.ts +19 -0
- package/dist/components/tagList/useTagListCollapsedLayout.d.ts.map +1 -0
- package/dist/components/tagList/useTagListCollapsedLayout.js +48 -0
- package/dist/components/tagList/useTagListCollapsedLayout.js.map +1 -0
- package/dist/components/tagList/useVisibleTags.d.ts +18 -0
- package/dist/components/tagList/useVisibleTags.d.ts.map +1 -0
- package/dist/components/tagList/useVisibleTags.js +41 -0
- package/dist/components/tagList/useVisibleTags.js.map +1 -0
- package/dist/index.css +130 -10
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/hooks/useElementWidth.d.ts +2 -0
- package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
- package/dist/utils/hooks/useElementWidth.js +30 -0
- package/dist/utils/hooks/useElementWidth.js.map +1 -0
- package/dist/utils/hooks/useMeasuredChildWidths.d.ts +8 -0
- package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -0
- package/dist/utils/hooks/useMeasuredChildWidths.js +26 -0
- package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -0
- package/dist/utils/hooks/useRovingFocus.d.ts +18 -0
- package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -0
- package/dist/utils/hooks/useRovingFocus.js +130 -0
- package/dist/utils/hooks/useRovingFocus.js.map +1 -0
- package/dist/utils/hooks/useRovingFocus.test.d.ts +2 -0
- package/dist/utils/hooks/useRovingFocus.test.d.ts.map +1 -0
- package/dist/utils/hooks/useRovingFocus.test.js +59 -0
- package/dist/utils/hooks/useRovingFocus.test.js.map +1 -0
- package/dist/utils/spacedWidths.d.ts +3 -0
- package/dist/utils/spacedWidths.d.ts.map +1 -0
- package/dist/utils/spacedWidths.js +28 -0
- package/dist/utils/spacedWidths.js.map +1 -0
- package/dist/utils/spacedWidths.test.d.ts +2 -0
- package/dist/utils/spacedWidths.test.d.ts.map +1 -0
- package/dist/utils/spacedWidths.test.js +17 -0
- package/dist/utils/spacedWidths.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/articleCard/ArticleCard.stories.tsx +17 -12
- package/src/components/articleCard/ArticleCard.tsx +9 -9
- package/src/components/{icoText/IcoText.stories.tsx → iconText/IconText.stories.tsx} +112 -112
- package/src/components/{icoText/IcoText.test.tsx → iconText/IconText.test.tsx} +10 -10
- package/src/components/{icoText/IcoText.tsx → iconText/IconText.tsx} +27 -20
- package/src/components/modal/Modal.tsx +5 -1
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.test.tsx +12 -0
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +2 -2
- package/src/components/tag/Tag.stories.tsx +4 -4
- package/src/components/tag/Tag.test.tsx +62 -5
- package/src/components/tag/Tag.tsx +61 -3
- package/src/components/tag/tag.scss +80 -9
- package/src/components/tagList/TagList.stories.tsx +564 -0
- package/src/components/tagList/TagList.test.tsx +342 -0
- package/src/components/tagList/TagList.tsx +296 -0
- package/src/components/tagList/tagList.scss +56 -0
- package/src/components/tagList/useTagListCollapsedLayout.ts +83 -0
- package/src/components/tagList/useVisibleTags.ts +74 -0
- package/src/index.scss +2 -1
- package/src/index.ts +3 -1
- package/src/tokens.scss +2 -1
- package/src/utils/hooks/useElementWidth.ts +39 -0
- package/src/utils/hooks/useMeasuredChildWidths.ts +39 -0
- package/src/utils/hooks/useRovingFocus.test.tsx +105 -0
- package/src/utils/hooks/useRovingFocus.ts +163 -0
- package/src/utils/spacedWidths.test.ts +20 -0
- package/src/utils/spacedWidths.ts +37 -0
- package/dist/components/icoText/IcoText.d.ts +0 -37
- package/dist/components/icoText/IcoText.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.js +0 -29
- package/dist/components/icoText/IcoText.js.map +0 -1
- package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.stories.js.map +0 -1
- package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
- package/dist/components/icoText/IcoText.test.js.map +0 -1
- /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 | [`
|
|
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
|
-
'
|
|
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) · [
|
|
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 `
|
|
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
|
-
'
|
|
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
|
|
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={() =>
|
|
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={
|
|
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={() =>
|
|
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={
|
|
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={
|
|
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
|
|
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 {
|
|
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 {
|
|
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?:
|
|
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
|
-
<
|
|
65
|
+
<IconText>
|
|
66
66
|
{icon && (
|
|
67
|
-
<
|
|
67
|
+
<IconText.Icon
|
|
68
68
|
color={iconColor}
|
|
69
69
|
name={icon}
|
|
70
70
|
screenReaderText={iconScreenReaderText}
|
|
71
71
|
/>
|
|
72
72
|
)}
|
|
73
73
|
{title && (
|
|
74
|
-
<
|
|
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
|
-
</
|
|
82
|
+
</IconText.Heading>
|
|
83
83
|
)}
|
|
84
|
-
{paragraph && <
|
|
84
|
+
{paragraph && <IconText.Paragraph>{paragraph}</IconText.Paragraph>}
|
|
85
85
|
{tagText && <Tag color={tagColor}>{tagText}</Tag>}
|
|
86
|
-
</
|
|
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 {
|
|
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
|
-
'`
|
|
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 `
|
|
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
|
-
'**`
|
|
70
|
-
'The root inspects `children`, pulls out any `
|
|
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
|
-
'<
|
|
75
|
-
' <
|
|
76
|
-
' <
|
|
77
|
-
' <
|
|
78
|
-
'</
|
|
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
|
-
'**`
|
|
82
|
-
'heading hierarchy is correct before using `
|
|
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
|
-
'- `
|
|
94
|
-
'- `
|
|
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 {
|
|
102
|
-
"import type {
|
|
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
|
|
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>
|
|
128
|
+
<DocHeading>IconText.Icon props</DocHeading>
|
|
129
129
|
<Markdown>{ICON_PROPS}</Markdown>
|
|
130
|
-
<DocHeading>
|
|
130
|
+
<DocHeading>IconText.Heading props</DocHeading>
|
|
131
131
|
<Markdown>{HEADING_PROPS}</Markdown>
|
|
132
|
-
<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/
|
|
151
|
-
component:
|
|
150
|
+
title: 'Components/IconText',
|
|
151
|
+
component: IconText,
|
|
152
152
|
tags: ['autodocs'],
|
|
153
153
|
parameters: {
|
|
154
154
|
layout: 'padded',
|
|
155
|
-
docs: { page:
|
|
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 `
|
|
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
|
|
169
|
+
} satisfies Meta<typeof IconText>;
|
|
170
170
|
|
|
171
171
|
export default meta;
|
|
172
|
-
type Story = StoryObj<typeof
|
|
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
|
-
<
|
|
198
|
-
<
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
</
|
|
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
|
-
<
|
|
220
|
-
<
|
|
221
|
-
<
|
|
222
|
-
<
|
|
223
|
-
</
|
|
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
|
-
<
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
<
|
|
234
|
-
<
|
|
235
|
-
</
|
|
230
|
+
<IconText>
|
|
231
|
+
<IconText.Icon name="date" />
|
|
232
|
+
<IconText.Heading>Parents' 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
|
-
<
|
|
246
|
-
<
|
|
247
|
-
<
|
|
248
|
-
<
|
|
249
|
-
</
|
|
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 — `
|
|
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 {
|
|
263
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
264
264
|
|
|
265
265
|
function HeadingOnlyExample() {
|
|
266
266
|
return (
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
</
|
|
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
|
-
<
|
|
280
|
-
<
|
|
281
|
-
<
|
|
282
|
-
</
|
|
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 {
|
|
296
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
297
297
|
|
|
298
298
|
function WithoutIconExample() {
|
|
299
299
|
return (
|
|
300
|
-
<
|
|
301
|
-
<
|
|
302
|
-
<
|
|
303
|
-
</
|
|
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
|
-
<
|
|
313
|
-
<
|
|
314
|
-
<
|
|
315
|
-
</
|
|
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 — `
|
|
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 {
|
|
329
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
330
330
|
|
|
331
331
|
function IconWithScreenReaderTextExample() {
|
|
332
332
|
return (
|
|
333
|
-
<
|
|
334
|
-
<
|
|
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
|
-
<
|
|
340
|
-
<
|
|
341
|
-
</
|
|
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
|
-
<
|
|
351
|
-
<
|
|
352
|
-
<
|
|
353
|
-
<
|
|
354
|
-
</
|
|
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 {
|
|
368
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
369
369
|
|
|
370
370
|
function IconSizesExample() {
|
|
371
371
|
return (
|
|
372
372
|
<>
|
|
373
|
-
<
|
|
374
|
-
<
|
|
375
|
-
<
|
|
376
|
-
<
|
|
377
|
-
</
|
|
378
|
-
<
|
|
379
|
-
<
|
|
380
|
-
<
|
|
381
|
-
<
|
|
382
|
-
</
|
|
383
|
-
<
|
|
384
|
-
<
|
|
385
|
-
<
|
|
386
|
-
<
|
|
387
|
-
</
|
|
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 `
|
|
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 {
|
|
409
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
410
410
|
|
|
411
411
|
function IconColorsExample() {
|
|
412
412
|
return (
|
|
413
|
-
<
|
|
414
|
-
<
|
|
415
|
-
<
|
|
416
|
-
<
|
|
417
|
-
</
|
|
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 `
|
|
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 {
|
|
438
|
+
import { IconText } from '@arbor-education/design-system.components';
|
|
439
439
|
|
|
440
440
|
function MultipleParagraphsExample() {
|
|
441
441
|
return (
|
|
442
|
-
<
|
|
443
|
-
<
|
|
444
|
-
<
|
|
445
|
-
<
|
|
446
|
-
<
|
|
447
|
-
</
|
|
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 `
|
|
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 {
|
|
4
|
+
import { IconText } from './IconText.js';
|
|
5
5
|
|
|
6
|
-
describe('
|
|
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
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<
|
|
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
|
-
<
|
|
15
|
-
</
|
|
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
|
|
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') ?? []);
|