@arbor-education/design-system.components 0.13.0 → 0.14.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/.agent-memory/blanche-designspert/MEMORY.md +189 -0
- package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
- package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
- package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
- package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
- package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
- package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
- package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
- package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
- package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
- package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
- package/.gather/gather.yaml +9 -0
- package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
- package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
- package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
- package/.gather/skills/analyze-design/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/README.md +5 -0
- package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
- package/.gather/skills/create-page/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
- package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
- package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
- package/.gather/skills/map-legacy/meta.md +4 -0
- package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
- package/.gather/skills/migrate-page/meta.md +4 -0
- package/.gather/skills/write-stories/README.md +157 -0
- package/.gather/skills/write-stories/SKILL.md +841 -0
- package/.gather/skills/write-stories/meta.md +4 -0
- package/.ralph/storybook-upgrade/knowledge.md +308 -0
- package/.ralph/storybook-upgrade/prd.json +777 -0
- package/.ralph/storybook-upgrade/progress.md +342 -0
- package/.storybook/DocsTemplate.tsx +122 -0
- package/.storybook/preview.ts +40 -0
- package/.stylelintignore +2 -0
- package/CHANGELOG.md +14 -0
- package/{.claude/component-library.md → component-library.md} +27 -10
- package/dist/components/articleCard/ArticleCard.d.ts +30 -0
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.js +24 -0
- package/dist/components/articleCard/ArticleCard.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts +18 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.js +112 -0
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts +2 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.js +49 -0
- package/dist/components/articleCard/ArticleCard.test.js.map +1 -0
- package/dist/components/badge/Badge.stories.d.ts +85 -6
- package/dist/components/badge/Badge.stories.d.ts.map +1 -1
- package/dist/components/badge/Badge.stories.js +626 -27
- package/dist/components/badge/Badge.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts +129 -63
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +855 -39
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/button/Button.stories.d.ts +148 -8
- package/dist/components/button/Button.stories.d.ts.map +1 -1
- package/dist/components/button/Button.stories.js +1089 -80
- package/dist/components/button/Button.stories.js.map +1 -1
- package/dist/components/card/Card.d.ts +41 -12
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +46 -17
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +9 -84
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +15 -73
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/card/Card.test.js +50 -152
- package/dist/components/card/Card.test.js.map +1 -1
- package/dist/components/dot/Dot.stories.d.ts +46 -11
- package/dist/components/dot/Dot.stories.d.ts.map +1 -1
- package/dist/components/dot/Dot.stories.js +504 -15
- package/dist/components/dot/Dot.stories.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +769 -17
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +95 -35
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +1174 -69
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
- package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
- package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
- package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.d.ts +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +1 -1
- package/dist/components/heading/Heading.stories.d.ts +449 -50
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +536 -60
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/icoText/IcoText.d.ts +37 -0
- package/dist/components/icoText/IcoText.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.js +29 -0
- package/dist/components/icoText/IcoText.js.map +1 -0
- package/dist/components/icoText/IcoText.stories.d.ts +34 -0
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.stories.js +24 -0
- package/dist/components/icoText/IcoText.stories.js.map +1 -0
- package/dist/components/icoText/IcoText.test.d.ts +2 -0
- package/dist/components/icoText/IcoText.test.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.test.js +27 -0
- package/dist/components/icoText/IcoText.test.js.map +1 -0
- package/dist/components/icon/Icon.stories.d.ts +81 -10
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +979 -8
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/kpiCard/KPICard.d.ts +13 -0
- package/dist/components/kpiCard/KPICard.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.js +8 -0
- package/dist/components/kpiCard/KPICard.js.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts +9 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.js +18 -0
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -0
- package/dist/components/kpiCard/KPICard.test.d.ts +2 -0
- package/dist/components/kpiCard/KPICard.test.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.test.js +37 -0
- package/dist/components/kpiCard/KPICard.test.js.map +1 -0
- package/dist/components/kvpList/KVPList.d.ts +34 -0
- package/dist/components/kvpList/KVPList.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.js +20 -0
- package/dist/components/kvpList/KVPList.js.map +1 -0
- package/dist/components/kvpList/KVPList.stories.d.ts +27 -0
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.stories.js +18 -0
- package/dist/components/kvpList/KVPList.stories.js.map +1 -0
- package/dist/components/kvpList/KVPList.test.d.ts +2 -0
- package/dist/components/kvpList/KVPList.test.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.test.js +29 -0
- package/dist/components/kvpList/KVPList.test.js.map +1 -0
- package/dist/components/pill/Pill.stories.d.ts +71 -19
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +573 -14
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +75 -298
- package/dist/components/progress/Progress.stories.d.ts.map +1 -1
- package/dist/components/progress/Progress.stories.js +449 -52
- package/dist/components/progress/Progress.stories.js.map +1 -1
- package/dist/components/separator/Separator.stories.d.ts +58 -5
- package/dist/components/separator/Separator.stories.d.ts.map +1 -1
- package/dist/components/separator/Separator.stories.js +443 -4
- package/dist/components/separator/Separator.stories.js.map +1 -1
- package/dist/components/singleUser/SingleUser.d.ts +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- package/dist/components/tag/Tag.stories.d.ts +116 -5
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +581 -28
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/index.css +194 -23
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/eslint.config.mts +5 -1
- package/package.json +3 -3
- package/src/components/articleCard/ArticleCard.stories.tsx +132 -0
- package/src/components/articleCard/ArticleCard.test.tsx +121 -0
- package/src/components/articleCard/ArticleCard.tsx +100 -0
- package/src/components/articleCard/articleCard.scss +39 -0
- package/src/components/badge/Badge.stories.tsx +869 -42
- package/src/components/banner/Banner.stories.tsx +1081 -63
- package/src/components/button/Button.stories.tsx +1394 -99
- package/src/components/card/Card.stories.tsx +35 -79
- package/src/components/card/Card.test.tsx +72 -190
- package/src/components/card/Card.tsx +117 -58
- package/src/components/card/card.scss +18 -31
- package/src/components/dot/Dot.stories.tsx +723 -32
- package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
- package/src/components/formField/FormField.stories.tsx +1522 -105
- package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
- package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
- package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
- package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
- package/src/components/heading/Heading.stories.tsx +752 -120
- package/src/components/icoText/IcoText.stories.tsx +47 -0
- package/src/components/icoText/IcoText.test.tsx +41 -0
- package/src/components/icoText/IcoText.tsx +93 -0
- package/src/components/icoText/icoText.scss +34 -0
- package/src/components/icon/Icon.stories.tsx +1446 -12
- package/src/components/kpiCard/KPICard.stories.tsx +47 -0
- package/src/components/kpiCard/KPICard.test.tsx +60 -0
- package/src/components/kpiCard/KPICard.tsx +45 -0
- package/src/components/kpiCard/kpiCard.scss +35 -0
- package/src/components/kvpList/KVPList.stories.tsx +51 -0
- package/src/components/kvpList/KVPList.test.tsx +66 -0
- package/src/components/kvpList/KVPList.tsx +109 -0
- package/src/components/kvpList/kvpList.scss +64 -0
- package/src/components/pill/Pill.stories.tsx +867 -21
- package/src/components/progress/Progress.stories.tsx +625 -58
- package/src/components/separator/Separator.stories.tsx +730 -8
- package/src/components/separator/separator.scss +12 -3
- package/src/components/tag/Tag.stories.tsx +755 -53
- package/src/index.scss +4 -0
- package/src/index.ts +13 -4
- package/src/tokens.scss +6 -0
- package/tokens/json/Arbor.json +30 -0
- package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
- package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
- package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
- package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
- package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
- package/.claude/figma-assessment-7154-58899.md +0 -404
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
- package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
- package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
- /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
- /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
- /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
3
|
+
import { ArticleCard } from './ArticleCard';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Card/ArticleCard',
|
|
7
|
+
component: ArticleCard,
|
|
8
|
+
} satisfies Meta<typeof ArticleCard>;
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
|
|
12
|
+
export const CardWithTitleAndParagraph: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
'title': 'Title of Card',
|
|
15
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
16
|
+
'disabled': false,
|
|
17
|
+
'onClick': fn(),
|
|
18
|
+
'onKeyDown': fn(),
|
|
19
|
+
'aria-label': 'Clickable article card with title and paragraph',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const CardWithTitleParagraphAndIcon: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
'title': 'Title of Card',
|
|
26
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
27
|
+
'icon': 'eye',
|
|
28
|
+
'disabled': false,
|
|
29
|
+
'onClick': fn(),
|
|
30
|
+
'onKeyDown': fn(),
|
|
31
|
+
'aria-label': 'Clickable article card with title paragraph and icon',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const TheEverythingCard: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
'title': 'Title of Card',
|
|
38
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
39
|
+
'icon': 'eye',
|
|
40
|
+
'disabled': false,
|
|
41
|
+
'tagText': 'argle bargle',
|
|
42
|
+
'tagColor': 'orange',
|
|
43
|
+
'onClick': fn(),
|
|
44
|
+
'onKeyDown': fn(),
|
|
45
|
+
'aria-label': 'Clickable article card with all content',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const CardWithTitleParagraphAndTag: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
'title': 'Title of Card',
|
|
52
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
53
|
+
'disabled': false,
|
|
54
|
+
'tagText': 'argle bargle',
|
|
55
|
+
'tagColor': 'orange',
|
|
56
|
+
'onClick': fn(),
|
|
57
|
+
'onKeyDown': fn(),
|
|
58
|
+
'aria-label': 'Clickable article card with title paragraph and tag',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const CardWithTitleAndIcon: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
'title': 'Title of Card',
|
|
65
|
+
'icon': 'eye',
|
|
66
|
+
'disabled': false,
|
|
67
|
+
'onClick': fn(),
|
|
68
|
+
'onKeyDown': fn(),
|
|
69
|
+
'aria-label': 'Clickable article card with title and icon',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const CardWithParagraph: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
76
|
+
'disabled': false,
|
|
77
|
+
'onClick': fn(),
|
|
78
|
+
'onKeyDown': fn(),
|
|
79
|
+
'aria-label': 'Clickable article card with paragraph',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const ClickableDisabledCard: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
'title': 'Title of Card',
|
|
86
|
+
'paragraph': 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
87
|
+
'icon': 'eye',
|
|
88
|
+
'tagText': 'argle bargle',
|
|
89
|
+
'tagColor': 'orange',
|
|
90
|
+
'disabled': true,
|
|
91
|
+
'onClick': fn(),
|
|
92
|
+
'onKeyDown': fn(),
|
|
93
|
+
'aria-label': 'Disabled clickable article card',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const UnclickableCard: Story = {
|
|
98
|
+
args: {
|
|
99
|
+
title: 'Title of Card',
|
|
100
|
+
paragraph: 'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
|
|
101
|
+
icon: 'eye',
|
|
102
|
+
tagText: 'argle bargle',
|
|
103
|
+
tagColor: 'orange',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const TextOnly: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
title: 'Text-only article card',
|
|
110
|
+
paragraph: 'A compact way to present article-style content in the shared shell.',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const LinkedArticle: Story = {
|
|
115
|
+
args: {
|
|
116
|
+
title: 'Primary article link',
|
|
117
|
+
href: '/articles/primary-article-link',
|
|
118
|
+
paragraph: (
|
|
119
|
+
<>
|
|
120
|
+
Read the full article or visit the
|
|
121
|
+
{' '}
|
|
122
|
+
<a href="/authors/design-system">author page</a>
|
|
123
|
+
.
|
|
124
|
+
</>
|
|
125
|
+
),
|
|
126
|
+
icon: 'eye',
|
|
127
|
+
tagText: 'Featured',
|
|
128
|
+
tagColor: 'green',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default meta;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom/vitest';
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
4
|
+
import { ArticleCard } from './ArticleCard';
|
|
5
|
+
|
|
6
|
+
describe('ArticleCard', () => {
|
|
7
|
+
test('renders the legacy card content composition', () => {
|
|
8
|
+
const { container } = render(
|
|
9
|
+
<ArticleCard
|
|
10
|
+
icon="eye"
|
|
11
|
+
paragraph="Helpful supporting copy"
|
|
12
|
+
tagColor="green"
|
|
13
|
+
tagText="Live"
|
|
14
|
+
title="Article summary"
|
|
15
|
+
/>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(screen.getByRole('article')).toBeInTheDocument();
|
|
19
|
+
expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('Article summary');
|
|
20
|
+
expect(screen.getByText('Helpful supporting copy')).toBeInTheDocument();
|
|
21
|
+
expect(screen.getByText('Live')).toBeInTheDocument();
|
|
22
|
+
expect(container.querySelector('.ds-icon-eye')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('renders content without an icon', () => {
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<ArticleCard
|
|
28
|
+
paragraph="Helpful supporting copy"
|
|
29
|
+
title="Article summary"
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('Article summary');
|
|
34
|
+
expect(container.querySelector('.ds-ico-text__icon')).not.toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('renders a tag when provided', () => {
|
|
38
|
+
render(
|
|
39
|
+
<ArticleCard
|
|
40
|
+
paragraph="Helpful supporting copy"
|
|
41
|
+
tagColor="orange"
|
|
42
|
+
tagText="Pinned"
|
|
43
|
+
title="Article summary"
|
|
44
|
+
/>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByText('Pinned')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('renders a primary title link without button semantics when href is provided', () => {
|
|
51
|
+
const { container } = render(
|
|
52
|
+
<ArticleCard
|
|
53
|
+
href="/articles/linked-article"
|
|
54
|
+
paragraph={(
|
|
55
|
+
<>
|
|
56
|
+
By
|
|
57
|
+
{' '}
|
|
58
|
+
<a href="/authors/design-system">Design System Team</a>
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
title="Linked article"
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByRole('link', { name: 'Linked article' })).toHaveAttribute(
|
|
66
|
+
'href',
|
|
67
|
+
'/articles/linked-article',
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByRole('link', { name: 'Design System Team' })).toHaveAttribute(
|
|
70
|
+
'href',
|
|
71
|
+
'/authors/design-system',
|
|
72
|
+
);
|
|
73
|
+
expect(container.querySelector('figure')).not.toHaveAttribute('role', 'button');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('suppresses the primary link when disabled even if href is provided', () => {
|
|
77
|
+
const { container } = render(
|
|
78
|
+
<ArticleCard
|
|
79
|
+
disabled
|
|
80
|
+
href="/articles/linked-article"
|
|
81
|
+
paragraph="Helpful supporting copy"
|
|
82
|
+
title="Linked article"
|
|
83
|
+
/>,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(screen.queryByRole('link', { name: 'Linked article' })).not.toBeInTheDocument();
|
|
87
|
+
expect(container.querySelector('figure')).not.toHaveAttribute('role', 'button');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('uses the shared card shell for interactivity', () => {
|
|
91
|
+
const handleClick = vi.fn();
|
|
92
|
+
|
|
93
|
+
render(
|
|
94
|
+
<ArticleCard
|
|
95
|
+
aria-label="Clickable article card"
|
|
96
|
+
onClick={handleClick}
|
|
97
|
+
paragraph="Helpful supporting copy"
|
|
98
|
+
title="Clickable article"
|
|
99
|
+
/>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const card = screen.getByRole('button', { name: 'Clickable article card' });
|
|
103
|
+
fireEvent.click(card);
|
|
104
|
+
|
|
105
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('renders as a static figure when not clickable', () => {
|
|
109
|
+
const { container } = render(
|
|
110
|
+
<ArticleCard
|
|
111
|
+
paragraph="Helpful supporting copy"
|
|
112
|
+
title="Static article"
|
|
113
|
+
/>,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const card = container.querySelector('figure');
|
|
117
|
+
|
|
118
|
+
expect(card).toBeInTheDocument();
|
|
119
|
+
expect(card).not.toHaveAttribute('role', 'button');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import type { IcoTextIconProps } from 'Components/icoText/IcoText';
|
|
3
|
+
import type { TagColor } from 'Components/tag/Tag';
|
|
4
|
+
import { Tag } from 'Components/tag/Tag';
|
|
5
|
+
import type { IconName } from 'Components/icon/allowedIcons';
|
|
6
|
+
import { Card, getCardInteractionProps } from 'Components/card/Card';
|
|
7
|
+
import { IcoText } from 'Components/icoText/IcoText';
|
|
8
|
+
|
|
9
|
+
type ArticleCardBaseProps = {
|
|
10
|
+
className?: string;
|
|
11
|
+
paragraph?: React.ReactNode;
|
|
12
|
+
icon?: IconName;
|
|
13
|
+
iconColor?: IcoTextIconProps['color'];
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
tagText?: string;
|
|
16
|
+
tagColor?: TagColor;
|
|
17
|
+
iconScreenReaderText?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ArticleCardLinkedProps = {
|
|
21
|
+
'href': string;
|
|
22
|
+
'title': React.ReactNode;
|
|
23
|
+
'onClick'?: undefined;
|
|
24
|
+
'onKeyDown'?: undefined;
|
|
25
|
+
'aria-label'?: string;
|
|
26
|
+
'aria-labelledby'?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ArticleCardCardShellProps = {
|
|
30
|
+
href?: undefined;
|
|
31
|
+
title?: React.ReactNode;
|
|
32
|
+
} & Card.InteractionProps;
|
|
33
|
+
|
|
34
|
+
export type ArticleCardProps = ArticleCardBaseProps & (ArticleCardLinkedProps | ArticleCardCardShellProps);
|
|
35
|
+
|
|
36
|
+
export const ArticleCard = (props: ArticleCardProps): React.JSX.Element => {
|
|
37
|
+
const {
|
|
38
|
+
className,
|
|
39
|
+
href,
|
|
40
|
+
title,
|
|
41
|
+
paragraph,
|
|
42
|
+
icon,
|
|
43
|
+
iconColor,
|
|
44
|
+
iconScreenReaderText,
|
|
45
|
+
disabled = false,
|
|
46
|
+
tagText,
|
|
47
|
+
tagColor,
|
|
48
|
+
} = props;
|
|
49
|
+
const hasPrimaryLink = Boolean(href && title && !disabled);
|
|
50
|
+
|
|
51
|
+
const cardClassName = classNames({
|
|
52
|
+
'ds-card__container--article-link': hasPrimaryLink,
|
|
53
|
+
}, className);
|
|
54
|
+
|
|
55
|
+
const cardInteractionProps = hasPrimaryLink
|
|
56
|
+
? getCardInteractionProps({
|
|
57
|
+
'onKeyDown': undefined,
|
|
58
|
+
'aria-label': props['aria-label'],
|
|
59
|
+
'aria-labelledby': props['aria-labelledby'],
|
|
60
|
+
})
|
|
61
|
+
: getCardInteractionProps(props);
|
|
62
|
+
|
|
63
|
+
const content = (
|
|
64
|
+
<article className="ds-article-card">
|
|
65
|
+
<IcoText>
|
|
66
|
+
{icon && (
|
|
67
|
+
<IcoText.Icon
|
|
68
|
+
color={iconColor}
|
|
69
|
+
name={icon}
|
|
70
|
+
screenReaderText={iconScreenReaderText}
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
{title && (
|
|
74
|
+
<IcoText.Heading>
|
|
75
|
+
{hasPrimaryLink
|
|
76
|
+
? (
|
|
77
|
+
<a className="ds-article-card__primary-link" href={href}>
|
|
78
|
+
{title}
|
|
79
|
+
</a>
|
|
80
|
+
)
|
|
81
|
+
: title}
|
|
82
|
+
</IcoText.Heading>
|
|
83
|
+
)}
|
|
84
|
+
{paragraph && <IcoText.Paragraph>{paragraph}</IcoText.Paragraph>}
|
|
85
|
+
{tagText && <Tag color={tagColor}>{tagText}</Tag>}
|
|
86
|
+
</IcoText>
|
|
87
|
+
</article>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Card
|
|
92
|
+
{...cardInteractionProps}
|
|
93
|
+
className={cardClassName}
|
|
94
|
+
disabled={disabled}
|
|
95
|
+
spacing="default"
|
|
96
|
+
>
|
|
97
|
+
{content}
|
|
98
|
+
</Card>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.ds-card__container--article-link {
|
|
2
|
+
position: relative;
|
|
3
|
+
|
|
4
|
+
&:not(.ds-card__container--disabled) {
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
&:focus-within:not(.ds-card__container--disabled) {
|
|
9
|
+
border: var(--border-weight) solid var(--card-focus-color-border);
|
|
10
|
+
background: var(--card-focus-color-background);
|
|
11
|
+
outline: var(--focus-border) solid var(--card-focus-focus);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ds-article-card {
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ds-article-card__primary-link {
|
|
20
|
+
color: inherit;
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
|
|
23
|
+
&::after {
|
|
24
|
+
content: '';
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
z-index: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:hover,
|
|
31
|
+
&:focus-visible {
|
|
32
|
+
text-decoration: underline;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ds-article-card a:not(.ds-article-card__primary-link) {
|
|
37
|
+
position: relative;
|
|
38
|
+
z-index: 2;
|
|
39
|
+
}
|