@arbor-education/design-system.components 0.21.1 → 0.23.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 +22 -0
- package/component-library.md +77 -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/combobox/Combobox.js +1 -1
- package/dist/components/combobox/Combobox.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts +4 -0
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +144 -12
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.test.js +22 -0
- package/dist/components/combobox/Combobox.test.js.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts +4 -4
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.js +35 -40
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.js +11 -4
- package/dist/components/combobox/ComboboxTrigger.js.map +1 -1
- package/dist/components/combobox/useVisibleTriggerTags.d.ts +21 -0
- package/dist/components/combobox/useVisibleTriggerTags.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.js +46 -0
- package/dist/components/combobox/useVisibleTriggerTags.js.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.d.ts +2 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.js +81 -0
- package/dist/components/combobox/useVisibleTriggerTags.test.js.map +1 -0
- package/dist/components/filterBar/FilterBar.d.ts +71 -0
- package/dist/components/filterBar/FilterBar.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.js +89 -0
- package/dist/components/filterBar/FilterBar.js.map +1 -0
- package/dist/components/filterBar/FilterBar.stories.d.ts +170 -0
- package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.stories.js +894 -0
- package/dist/components/filterBar/FilterBar.stories.js.map +1 -0
- package/dist/components/filterBar/FilterBar.test.d.ts +2 -0
- package/dist/components/filterBar/FilterBar.test.d.ts.map +1 -0
- package/dist/components/filterBar/FilterBar.test.js +164 -0
- package/dist/components/filterBar/FilterBar.test.js.map +1 -0
- package/dist/components/icon/allowedIcons.d.ts +1 -0
- package/dist/components/icon/allowedIcons.d.ts.map +1 -1
- package/dist/components/icon/allowedIcons.js +2 -1
- package/dist/components/icon/allowedIcons.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/ComboboxCellRenderer.test.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.test.js +13 -2
- package/dist/components/table/cellRenderers/ComboboxCellRenderer.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 +272 -13
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
- package/dist/{components/combobox → utils/hooks}/useElementWidth.js +0 -1
- 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/combobox/Combobox.stories.tsx +186 -12
- package/src/components/combobox/Combobox.test.tsx +53 -0
- package/src/components/combobox/Combobox.tsx +3 -3
- package/src/components/combobox/ComboboxButtonTrigger.tsx +52 -56
- package/src/components/combobox/ComboboxTrigger.tsx +19 -16
- package/src/components/combobox/combobox.scss +8 -3
- package/src/components/combobox/useVisibleTriggerTags.test.tsx +91 -0
- package/src/components/combobox/useVisibleTriggerTags.ts +83 -0
- package/src/components/filterBar/FilterBar.stories.tsx +1199 -0
- package/src/components/filterBar/FilterBar.test.tsx +248 -0
- package/src/components/filterBar/FilterBar.tsx +298 -0
- package/src/components/filterBar/filterBar.scss +143 -0
- package/src/components/icon/allowedIcons.tsx +3 -1
- 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/ComboboxCellRenderer.test.tsx +20 -3
- 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 +3 -1
- package/src/index.ts +13 -1
- package/src/tokens.scss +3 -1
- package/src/{components/combobox → utils/hooks}/useElementWidth.ts +0 -1
- 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/combobox/useElementWidth.d.ts.map +0 -1
- package/dist/components/combobox/useElementWidth.js.map +0 -1
- package/dist/components/combobox/useVisibleChips.d.ts +0 -21
- package/dist/components/combobox/useVisibleChips.d.ts.map +0 -1
- package/dist/components/combobox/useVisibleChips.js +0 -59
- package/dist/components/combobox/useVisibleChips.js.map +0 -1
- package/dist/components/combobox/useVisibleChips.test.d.ts +0 -2
- package/dist/components/combobox/useVisibleChips.test.d.ts.map +0 -1
- package/dist/components/combobox/useVisibleChips.test.js +0 -81
- package/dist/components/combobox/useVisibleChips.test.js.map +0 -1
- 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/combobox/useVisibleChips.test.tsx +0 -91
- package/src/components/combobox/useVisibleChips.ts +0 -100
- /package/dist/{components/combobox → utils/hooks}/useElementWidth.d.ts +0 -0
- /package/src/components/{icoText/icoText.scss → iconText/iconText.scss} +0 -0
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
Link,
|
|
56
56
|
List,
|
|
57
57
|
ListFilterPlus,
|
|
58
|
+
ListTree,
|
|
58
59
|
LoaderCircle,
|
|
59
60
|
Lock,
|
|
60
61
|
LockOpen,
|
|
@@ -90,10 +91,10 @@ import {
|
|
|
90
91
|
UsersRound, X,
|
|
91
92
|
} from 'lucide-react';
|
|
92
93
|
import { AskArbor } from './customIcons/AskArbor.js';
|
|
93
|
-
import type { CustomIconProps } from './types.js';
|
|
94
94
|
import { CheckSolid } from './customIcons/CheckSolid.js';
|
|
95
95
|
import { Google } from './customIcons/Google.js';
|
|
96
96
|
import { XSolid } from './customIcons/XSolid.js';
|
|
97
|
+
import type { CustomIconProps } from './types.js';
|
|
97
98
|
|
|
98
99
|
export const allowedIcons = {
|
|
99
100
|
// lucide icons
|
|
@@ -156,6 +157,7 @@ export const allowedIcons = {
|
|
|
156
157
|
'link': Link,
|
|
157
158
|
'list-filter-plus': ListFilterPlus,
|
|
158
159
|
'list': List,
|
|
160
|
+
'list-tree': ListTree,
|
|
159
161
|
'loader': LoaderCircle,
|
|
160
162
|
'lock-open': LockOpen,
|
|
161
163
|
'lock': Lock,
|
|
@@ -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') ?? []);
|
|
@@ -3,20 +3,20 @@ import type { IconName } from 'Components/icon/allowedIcons';
|
|
|
3
3
|
import { Icon } from 'Components/icon/Icon';
|
|
4
4
|
import { Children, isValidElement } from 'react';
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type IconTextProps = {
|
|
7
7
|
children?: React.ReactNode;
|
|
8
8
|
className?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type IconTextHeadingProps = React.HTMLAttributes<HTMLHeadingElement> & {
|
|
12
12
|
children: React.ReactNode;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export type
|
|
15
|
+
export type IconTextParagraphProps = React.HTMLAttributes<HTMLParagraphElement> & {
|
|
16
16
|
children: React.ReactNode;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type IconTextIconProps = {
|
|
20
20
|
className?: string;
|
|
21
21
|
color?: Icon.Props['color'];
|
|
22
22
|
name: IconName;
|
|
@@ -24,33 +24,33 @@ export type IcoTextIconProps = {
|
|
|
24
24
|
size?: 12 | 16 | 24;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const IconTextHeading = ({
|
|
28
28
|
children,
|
|
29
29
|
className,
|
|
30
30
|
...rest
|
|
31
|
-
}:
|
|
31
|
+
}: IconTextHeadingProps): React.JSX.Element => (
|
|
32
32
|
<h4 className={classNames('ds-ico-text__heading', className)} {...rest}>
|
|
33
33
|
{children}
|
|
34
34
|
</h4>
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
-
const
|
|
37
|
+
const IconTextParagraph = ({
|
|
38
38
|
children,
|
|
39
39
|
className,
|
|
40
40
|
...rest
|
|
41
|
-
}:
|
|
41
|
+
}: IconTextParagraphProps): React.JSX.Element => (
|
|
42
42
|
<p className={classNames('ds-ico-text__paragraph', className)} {...rest}>
|
|
43
43
|
{children}
|
|
44
44
|
</p>
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
const
|
|
47
|
+
const IconTextIcon = ({
|
|
48
48
|
className,
|
|
49
49
|
color,
|
|
50
50
|
name,
|
|
51
51
|
screenReaderText,
|
|
52
52
|
size = 24,
|
|
53
|
-
}:
|
|
53
|
+
}: IconTextIconProps): React.JSX.Element => (
|
|
54
54
|
<Icon
|
|
55
55
|
name={name}
|
|
56
56
|
className={classNames('ds-ico-text__icon', className)}
|
|
@@ -60,12 +60,12 @@ const IcoTextIcon = ({
|
|
|
60
60
|
/>
|
|
61
61
|
);
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
const IconTextRoot = ({ children, className }: IconTextProps): React.JSX.Element => {
|
|
64
64
|
const iconChildren: React.ReactNode[] = [];
|
|
65
65
|
const contentChildren: React.ReactNode[] = [];
|
|
66
66
|
|
|
67
67
|
Children.forEach(children, (child) => {
|
|
68
|
-
if (isValidElement(child) && child.type ===
|
|
68
|
+
if (isValidElement(child) && child.type === IconTextIcon) {
|
|
69
69
|
iconChildren.push(child);
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
@@ -81,13 +81,20 @@ const IcoTextRoot = ({ children, className }: IcoTextProps): React.JSX.Element =
|
|
|
81
81
|
);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
IconTextHeading.displayName = 'IconText.Heading';
|
|
85
|
+
IconTextParagraph.displayName = 'IconText.Paragraph';
|
|
86
|
+
IconTextIcon.displayName = 'IconText.Icon';
|
|
87
|
+
IconTextRoot.displayName = 'IconText';
|
|
88
88
|
|
|
89
|
-
export const
|
|
90
|
-
Heading:
|
|
91
|
-
Paragraph:
|
|
92
|
-
Icon:
|
|
89
|
+
export const IconText = Object.assign(IconTextRoot, {
|
|
90
|
+
Heading: IconTextHeading,
|
|
91
|
+
Paragraph: IconTextParagraph,
|
|
92
|
+
Icon: IconTextIcon,
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
export namespace IconText {
|
|
96
|
+
export type Props = IconTextProps;
|
|
97
|
+
export type HeadingProps = IconTextHeadingProps;
|
|
98
|
+
export type ParagraphProps = IconTextParagraphProps;
|
|
99
|
+
export type IconProps = IconTextIconProps;
|
|
100
|
+
}
|
|
@@ -20,6 +20,8 @@ export type ModalProps = {
|
|
|
20
20
|
overlayClassName?: string;
|
|
21
21
|
open?: boolean;
|
|
22
22
|
portalTarget?: HTMLElement | null;
|
|
23
|
+
// Optional id for the dialog content so external triggers can point aria-controls at it.
|
|
24
|
+
contentId?: string;
|
|
23
25
|
children?: React.ReactNode;
|
|
24
26
|
hideCloseButton?: boolean;
|
|
25
27
|
closeHandler?: ModalContextValue['closeHandler'];
|
|
@@ -32,6 +34,7 @@ export const Modal = (props: ModalProps) => {
|
|
|
32
34
|
overlayClassName,
|
|
33
35
|
open,
|
|
34
36
|
portalTarget,
|
|
37
|
+
contentId,
|
|
35
38
|
children,
|
|
36
39
|
closeHandler,
|
|
37
40
|
hideCloseButton = false,
|
|
@@ -46,7 +49,8 @@ export const Modal = (props: ModalProps) => {
|
|
|
46
49
|
<Dialog.Root open={open}>
|
|
47
50
|
<Dialog.Portal container={portalTarget}>
|
|
48
51
|
<Dialog.Overlay ref={overlayRef} className={classNames('ds-modal__overlay', overlayClassName)}>
|
|
49
|
-
|
|
52
|
+
{/* Preserve an optional target for trigger/content relationships such as FilterBar button-to-dialog wiring. */}
|
|
53
|
+
<Dialog.Content id={contentId} className={classNames('ds-modal__container', className)}>
|
|
50
54
|
{title && <ModalHeader><ModalTitle>{title}</ModalTitle></ModalHeader>}
|
|
51
55
|
{children}
|
|
52
56
|
{!hideCloseButton && <ModalCloseButon className="ds-modal__close-button--top-right" />}
|