@scottish-government/designsystem-react 0.8.0 → 0.10.0-beta.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/.storybook/main.ts +20 -0
- package/.storybook/manager.ts +13 -0
- package/.storybook/preview-head.html +1 -0
- package/.storybook/preview.tsx +56 -0
- package/.storybook/sgdsArgTypes.ts +123 -0
- package/.storybook/sgdsTheme.ts +9 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/@types/common/AbstractNotificationBanner.d.ts +2 -2
- package/@types/common/ActionLink.d.ts +1 -1
- package/@types/common/Icon.d.ts +1 -1
- package/@types/components/Accordion.d.ts +2 -3
- package/@types/components/Button.d.ts +5 -5
- package/@types/components/CategoryItem.d.ts +10 -0
- package/@types/components/CategoryList.d.ts +7 -0
- package/@types/components/Checkbox.d.ts +2 -2
- package/@types/components/ContentsNav.d.ts +1 -1
- package/@types/components/DatePicker.d.ts +1 -1
- package/@types/components/ErrorMessage.d.ts +1 -2
- package/@types/components/ErrorSummary.d.ts +1 -1
- package/@types/components/FileDownload.d.ts +2 -2
- package/@types/components/Metadata.d.ts +1 -1
- package/@types/components/Pagination.d.ts +1 -1
- package/@types/components/RadioButton.d.ts +2 -2
- package/@types/components/SideNavigation.d.ts +1 -1
- package/@types/components/SiteNavigation.d.ts +1 -1
- package/@types/components/SummaryList.d.ts +1 -1
- package/@types/components/Tabs.d.ts +3 -3
- package/@types/components/TextInput.d.ts +1 -1
- package/@types/sgds.d.ts +2 -1
- package/CHANGELOG.md +29 -0
- package/dist/common/AbstractNotificationBanner.jsx +4 -4
- package/dist/common/Icon.jsx +2 -2
- package/dist/components/Accordion/Accordion.jsx +8 -7
- package/dist/components/Button/Button.jsx +6 -6
- package/dist/components/CategoryItem/CategoryItem.jsx +35 -0
- package/dist/components/CategoryList/CategoryList.jsx +55 -0
- package/dist/components/Checkbox/Checkbox.jsx +7 -4
- package/dist/components/Checkbox/CheckboxGroup.jsx +5 -11
- package/dist/components/ContentsNav/ContentsNav.jsx +2 -2
- package/dist/components/DatePicker/DatePicker.jsx +1 -1
- package/dist/components/ErrorMessage/ErrorMessage.jsx +3 -3
- package/dist/components/FileDownload/FileDownload.jsx +2 -2
- package/dist/components/NotificationBanner/NotificationBanner.jsx +2 -2
- package/dist/components/PageMetadata/PageMetadata.jsx +4 -4
- package/dist/components/Pagination/Pagination.jsx +4 -4
- package/dist/components/Question/Question.jsx +1 -1
- package/dist/components/RadioButton/RadioButton.jsx +6 -2
- package/dist/components/RadioButton/RadioGroup.jsx +7 -47
- package/dist/components/Select/Select.jsx +1 -1
- package/dist/components/SideNavigation/SideNavigation.jsx +2 -2
- package/dist/components/SiteHeader/SiteHeader.jsx +3 -3
- package/dist/components/SiteNavigation/SiteNavigation.jsx +2 -2
- package/dist/components/SiteSearch/SiteSearch.jsx +1 -1
- package/dist/components/SkipLinks/SkipLinks.jsx +1 -1
- package/dist/components/SummaryList/SummaryList.jsx +3 -3
- package/dist/components/Tabs/Tabs.jsx +6 -7
- package/dist/components/TextInput/TextInput.jsx +5 -5
- package/dist/components/Textarea/Textarea.jsx +1 -1
- package/dist/hooks/useTracking.js +21 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context.js +5 -0
- package/package.json +15 -4
- package/src/common/AbstractNotificationBanner.test.tsx +1 -1
- package/src/common/AbstractNotificationBanner.tsx +4 -4
- package/src/common/Icon.test.tsx +1 -1
- package/src/common/Icon.tsx +2 -2
- package/src/components/Accordion/Accordion.stories.tsx +111 -0
- package/src/components/Accordion/Accordion.test.tsx +5 -17
- package/src/components/Accordion/Accordion.tsx +11 -10
- package/src/components/AspectBox/AspectBox.stories.tsx +64 -0
- package/src/components/BackToTop/BackToTop.stories.tsx +36 -0
- package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +49 -0
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +0 -1
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +1 -1
- package/src/components/Button/Button.stories.tsx +194 -0
- package/src/components/Button/Button.test.tsx +4 -4
- package/src/components/Button/Button.tsx +9 -9
- package/src/components/CategoryItem/CategoryItem.stories.tsx +55 -0
- package/src/components/CategoryItem/CategoryItem.test.tsx +93 -0
- package/src/components/CategoryItem/CategoryItem.tsx +56 -0
- package/src/components/CategoryList/CategoryList.stories.tsx +65 -0
- package/src/components/CategoryList/CategoryList.test.tsx +59 -0
- package/src/components/CategoryList/CategoryList.tsx +33 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +85 -0
- package/src/components/Checkbox/Checkbox.test.tsx +2 -2
- package/src/components/Checkbox/Checkbox.tsx +11 -6
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +68 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +7 -12
- package/src/components/ConfirmationMessage/ConfirmationMessage.stories.tsx +38 -0
- package/src/components/ContentsNav/ContentsNav.stories.tsx +43 -0
- package/src/components/ContentsNav/ContentsNav.test.tsx +2 -2
- package/src/components/ContentsNav/ContentsNav.tsx +2 -2
- package/src/components/CookieBanner/CookieBanner.stories.tsx +33 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +113 -0
- package/src/components/DatePicker/DatePicker.tsx +1 -1
- package/src/components/Details/Details.stories.tsx +36 -0
- package/src/components/ErrorMessage/ErrorMessage.stories.tsx +19 -0
- package/src/components/ErrorMessage/ErrorMessage.test.tsx +3 -15
- package/src/components/ErrorMessage/ErrorMessage.tsx +1 -3
- package/src/components/ErrorSummary/ErrorSummary.stories.tsx +38 -0
- package/src/components/FileDownload/FileDownload.stories.tsx +75 -0
- package/src/components/FileDownload/FileDownload.test.tsx +1 -1
- package/src/components/FileDownload/FileDownload.tsx +2 -2
- package/src/components/HideThisPage/HideThisPage.stories.tsx +20 -0
- package/src/components/InsetText/InsetText.stories.tsx +21 -0
- package/src/components/NotificationBanner/NotificationBanner.stories.tsx +57 -0
- package/src/components/NotificationBanner/NotificationBanner.test.tsx +1 -1
- package/src/components/NotificationBanner/NotificationBanner.tsx +4 -4
- package/src/components/NotificationPanel/NotificationPanel.stories.tsx +32 -0
- package/src/components/PageHeader/PageHeader.stories.tsx +60 -0
- package/src/components/PageMetadata/PageMetadata.stories.tsx +58 -0
- package/src/components/PageMetadata/PageMetadata.test.tsx +2 -2
- package/src/components/PageMetadata/PageMetadata.tsx +4 -4
- package/src/components/Pagination/Pagination.stories.tsx +69 -0
- package/src/components/Pagination/Pagination.test.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +4 -4
- package/src/components/PhaseBanner/PhaseBanner.stories.tsx +38 -0
- package/src/components/Question/Question.stories.tsx +78 -0
- package/src/components/Question/Question.tsx +1 -1
- package/src/components/RadioButton/RadioButton.stories.tsx +67 -0
- package/src/components/RadioButton/RadioButton.test.tsx +2 -1
- package/src/components/RadioButton/RadioButton.tsx +9 -3
- package/src/components/RadioButton/RadioGroup.stories.tsx +77 -0
- package/src/components/RadioButton/RadioGroup.test.tsx +2 -2
- package/src/components/RadioButton/RadioGroup.tsx +8 -15
- package/src/components/Select/Select.stories.tsx +76 -0
- package/src/components/Select/Select.tsx +1 -1
- package/src/components/SequentialNavigation/SequentialNavigation.stories.tsx +31 -0
- package/src/components/SideNavigation/SideNavigation.stories.tsx +92 -0
- package/src/components/SideNavigation/SideNavigation.test.tsx +2 -2
- package/src/components/SideNavigation/SideNavigation.tsx +2 -2
- package/src/components/SiteFooter/SiteFooter.stories.tsx +65 -0
- package/src/components/SiteHeader/SiteHeader.stories.tsx +92 -0
- package/src/components/SiteHeader/SiteHeader.tsx +2 -7
- package/src/components/SiteNavigation/SiteNavigation.stories.tsx +45 -0
- package/src/components/SiteNavigation/SiteNavigation.test.tsx +1 -1
- package/src/components/SiteNavigation/SiteNavigation.tsx +2 -2
- package/src/components/SiteSearch/SiteSearch.stories.tsx +81 -0
- package/src/components/SiteSearch/SiteSearch.tsx +1 -1
- package/src/components/SkipLinks/SkipLinks.stories.tsx +57 -0
- package/src/components/SkipLinks/SkipLinks.tsx +1 -1
- package/src/components/SummaryCard/SummaryCard.stories.tsx +46 -0
- package/src/components/SummaryList/SummaryList.stories.tsx +75 -0
- package/src/components/SummaryList/SummaryList.test.tsx +1 -1
- package/src/components/SummaryList/SummaryList.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +96 -0
- package/src/components/Tabs/Tabs.stories.tsx +90 -0
- package/src/components/Tabs/Tabs.test.tsx +6 -8
- package/src/components/Tabs/Tabs.tsx +8 -9
- package/src/components/Tag/Tag.stories.tsx +25 -0
- package/src/components/TaskList/TaskList.stories.tsx +129 -0
- package/src/components/TextInput/TextInput.stories.tsx +123 -0
- package/src/components/TextInput/TextInput.test.tsx +2 -2
- package/src/components/TextInput/TextInput.tsx +5 -5
- package/src/components/Textarea/Textarea.stories.tsx +71 -0
- package/src/components/Textarea/Textarea.tsx +1 -1
- package/src/components/WarningText/WarningText.stories.tsx +21 -0
- package/src/hooks/useTracking.test.tsx +64 -0
- package/src/hooks/useTracking.ts +19 -0
- package/src/utils/context.ts +3 -0
- package/static/data/autocomplete-dummy-data.json +2361 -0
- package/static/images/highland-cow.jpg +0 -0
- package/static/images/scottish-government--min.svg +11 -0
- package/static/images/scottish-government.svg +6 -0
- package/tsconfig.json +3 -2
- package/vite.config.ts +45 -11
- package/vitest-setup.ts +1 -0
- package/vitest.shims.d.ts +1 -0
- package/src/utils/slugify.ts +0 -13
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import argTypes from '../../../.storybook/sgdsArgTypes';
|
|
3
|
+
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import coo from '../../../static/images/highland-cow.jpg';
|
|
6
|
+
import AspectBox from './AspectBox';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Components/Aspect Box',
|
|
10
|
+
component: AspectBox,
|
|
11
|
+
argTypes: {
|
|
12
|
+
ratio: {
|
|
13
|
+
control: { type: 'radio' },
|
|
14
|
+
description: 'Aspect ratio to use',
|
|
15
|
+
options: ['1:1', '4:3', '16:9', '21:9'],
|
|
16
|
+
type: 'string'
|
|
17
|
+
},
|
|
18
|
+
children: argTypes.children()
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
(Story) => (
|
|
22
|
+
<div style={{ maxWidth: '32em' }}>
|
|
23
|
+
<Story />
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
args: {children: <img
|
|
28
|
+
alt="A highland cow nuzzling its calf"
|
|
29
|
+
src={coo}
|
|
30
|
+
/>}
|
|
31
|
+
} satisfies Meta<typeof AspectBox>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
ratio: undefined
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Square: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
ratio: '1:1'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const Aspect4To3: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
ratio: '4:3'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Aspect16To9: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
ratio: '16:9'
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Aspect21To9: Story = {
|
|
61
|
+
args: {
|
|
62
|
+
ratio: '21:9'
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import BackToTop from './BackToTop';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Back to top',
|
|
7
|
+
component: BackToTop,
|
|
8
|
+
argTypes: {
|
|
9
|
+
href: {
|
|
10
|
+
control: { type: 'text' },
|
|
11
|
+
description: 'URL fragment to scroll to',
|
|
12
|
+
table: {
|
|
13
|
+
type: {
|
|
14
|
+
summary: 'string'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<div>
|
|
22
|
+
<p>Scroll down to see 👇</p>
|
|
23
|
+
<Story />
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
]
|
|
27
|
+
} satisfies Meta<typeof BackToTop>;
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
type Story = StoryObj<typeof meta>;
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
href: undefined,
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import argTypes from '../../../.storybook/sgdsArgTypes';
|
|
3
|
+
|
|
4
|
+
import Breadcrumbs from './Breadcrumbs';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Breadcrumbs',
|
|
8
|
+
component: Breadcrumbs,
|
|
9
|
+
argTypes: {
|
|
10
|
+
linkComponent: argTypes.linkComponent(),
|
|
11
|
+
children: argTypes.children()
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
children: <>
|
|
15
|
+
<Breadcrumbs.Item href="#home">
|
|
16
|
+
Home
|
|
17
|
+
</Breadcrumbs.Item>
|
|
18
|
+
<Breadcrumbs.Item href="#category">
|
|
19
|
+
Category
|
|
20
|
+
</Breadcrumbs.Item>
|
|
21
|
+
<Breadcrumbs.Item>
|
|
22
|
+
Page
|
|
23
|
+
</Breadcrumbs.Item>
|
|
24
|
+
</>
|
|
25
|
+
}
|
|
26
|
+
} satisfies Meta<typeof Breadcrumbs>;
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof meta>;
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const HideLastItem: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
children: <>
|
|
38
|
+
<Breadcrumbs.Item href="#home">
|
|
39
|
+
Home
|
|
40
|
+
</Breadcrumbs.Item>
|
|
41
|
+
<Breadcrumbs.Item href="#category">
|
|
42
|
+
Category
|
|
43
|
+
</Breadcrumbs.Item>
|
|
44
|
+
<Breadcrumbs.Item isHidden>
|
|
45
|
+
Page
|
|
46
|
+
</Breadcrumbs.Item>
|
|
47
|
+
</>
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -16,7 +16,6 @@ test('breadcrumbs render correctly', () => {
|
|
|
16
16
|
|
|
17
17
|
const nav = screen.getByRole('navigation');
|
|
18
18
|
const list = within(nav).getByRole('list');
|
|
19
|
-
const listItems = within(list).getAllByRole('listitem');
|
|
20
19
|
|
|
21
20
|
// check nav
|
|
22
21
|
expect(nav).toHaveAttribute('aria-label', 'Breadcrumb');
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import Button from './Button';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/Button',
|
|
6
|
+
component: Button,
|
|
7
|
+
parameters: {
|
|
8
|
+
controls: { sort: 'alpha' }
|
|
9
|
+
},
|
|
10
|
+
argTypes: {
|
|
11
|
+
buttonStyle: {
|
|
12
|
+
options: ['primary', 'secondary'],
|
|
13
|
+
control: { type: 'radio' },
|
|
14
|
+
type: 'string'
|
|
15
|
+
},
|
|
16
|
+
hasLinkStyle: {
|
|
17
|
+
description: 'Make the button look like a link',
|
|
18
|
+
control: 'boolean',
|
|
19
|
+
table: {
|
|
20
|
+
type: {
|
|
21
|
+
summary: 'boolean'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
href: {
|
|
26
|
+
control: { type: 'text' },
|
|
27
|
+
description: 'Href attribute, changes element to <code><a></code> if set',
|
|
28
|
+
table: {
|
|
29
|
+
type: {
|
|
30
|
+
summary: 'string'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
icon: {
|
|
35
|
+
description: 'Name of the icon component to use',
|
|
36
|
+
options: ['ArrowUpward', 'CalendarToday', 'Cancel', 'CheckCircle', 'ChevronLeft', 'ChevronRight', 'Close', 'Description', 'DoubleChevronLeft', 'DoubleChevronRight' ,'Error', 'ExpandLess', 'ExpandMore', 'List', 'PriorityHigh', 'Search'],
|
|
37
|
+
control: { type: 'select' },
|
|
38
|
+
type: 'string'
|
|
39
|
+
},
|
|
40
|
+
isIconLeft: {
|
|
41
|
+
description: 'Show icon on left of button',
|
|
42
|
+
control: 'boolean',
|
|
43
|
+
table: {
|
|
44
|
+
type: {
|
|
45
|
+
summary: 'boolean'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
isIconOnly: {
|
|
50
|
+
description: 'Show only the icon',
|
|
51
|
+
control: 'boolean',
|
|
52
|
+
table: {
|
|
53
|
+
type: {
|
|
54
|
+
summary: 'boolean'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
isSmall: {
|
|
59
|
+
control: 'boolean',
|
|
60
|
+
table: {
|
|
61
|
+
type: {
|
|
62
|
+
summary: 'boolean'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
type: {
|
|
67
|
+
options: ['submit', 'reset', 'button'],
|
|
68
|
+
control: { type: 'radio' },
|
|
69
|
+
type: 'string'
|
|
70
|
+
},
|
|
71
|
+
width: {
|
|
72
|
+
options: ['fluid', 'fixed', 'max'],
|
|
73
|
+
control: { type: 'radio' },
|
|
74
|
+
type: 'string'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
args: {
|
|
78
|
+
children: 'Button text'
|
|
79
|
+
}
|
|
80
|
+
} satisfies Meta<typeof Button>;
|
|
81
|
+
|
|
82
|
+
export default meta;
|
|
83
|
+
type Story = StoryObj<typeof meta>;
|
|
84
|
+
|
|
85
|
+
export const Default: Story = {
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const Secondary: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
buttonStyle: 'secondary'
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const SmallButton: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
isSmall: true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const FluidWidth: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
width: 'fluid'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const FixedWidth: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
width: 'fixed'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const MaxWidth: Story = {
|
|
113
|
+
args: {
|
|
114
|
+
width: 'max'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const WithIcon: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
icon: 'Search'
|
|
121
|
+
},
|
|
122
|
+
parameters: {
|
|
123
|
+
controls: {
|
|
124
|
+
exclude: [
|
|
125
|
+
'isIconOnly',
|
|
126
|
+
'hasLinkStyle'
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const IconOnLeft: Story = {
|
|
133
|
+
args: {
|
|
134
|
+
icon: 'Search',
|
|
135
|
+
isIconLeft: true
|
|
136
|
+
},
|
|
137
|
+
parameters: {
|
|
138
|
+
controls: {
|
|
139
|
+
exclude: [
|
|
140
|
+
'isIconOnly',
|
|
141
|
+
'hasLinkStyle'
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const IconOnly: Story = {
|
|
148
|
+
args: {
|
|
149
|
+
icon: 'Search',
|
|
150
|
+
isIconOnly: true
|
|
151
|
+
},
|
|
152
|
+
parameters: {
|
|
153
|
+
controls: {
|
|
154
|
+
exclude: [
|
|
155
|
+
'isIconLeft',
|
|
156
|
+
'hasLinkStyle',
|
|
157
|
+
'width'
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const LinkStyledAsButton: Story = {
|
|
164
|
+
args: {
|
|
165
|
+
href: '#'
|
|
166
|
+
},
|
|
167
|
+
parameters: {
|
|
168
|
+
controls: {
|
|
169
|
+
exclude: [
|
|
170
|
+
'hasLinkStyle',
|
|
171
|
+
'type'
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const ButtonStyledAsLink: Story = {
|
|
178
|
+
args: {
|
|
179
|
+
hasLinkStyle: true
|
|
180
|
+
},
|
|
181
|
+
parameters: {
|
|
182
|
+
controls: {
|
|
183
|
+
exclude: [
|
|
184
|
+
'buttonStyle',
|
|
185
|
+
'icon',
|
|
186
|
+
'isIconLeft',
|
|
187
|
+
'isIconOnly',
|
|
188
|
+
'isSmall',
|
|
189
|
+
'type',
|
|
190
|
+
'width'
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -52,7 +52,7 @@ test('max-width button', () => {
|
|
|
52
52
|
|
|
53
53
|
test('small button', () => {
|
|
54
54
|
render(
|
|
55
|
-
<Button
|
|
55
|
+
<Button isSmall>Button text</Button>
|
|
56
56
|
);
|
|
57
57
|
|
|
58
58
|
const button = screen.getByRole('button');
|
|
@@ -73,7 +73,7 @@ test('button with icon', () => {
|
|
|
73
73
|
|
|
74
74
|
test('button with icon (left)', () => {
|
|
75
75
|
render(
|
|
76
|
-
<Button icon="ChevronLeft"
|
|
76
|
+
<Button icon="ChevronLeft" isIconLeft>Button text</Button>
|
|
77
77
|
);
|
|
78
78
|
|
|
79
79
|
const button = screen.getByRole('button');
|
|
@@ -83,7 +83,7 @@ test('button with icon (left)', () => {
|
|
|
83
83
|
|
|
84
84
|
test('button only icon', () => {
|
|
85
85
|
render(
|
|
86
|
-
<Button icon="Search"
|
|
86
|
+
<Button icon="Search" isIconOnly>Button text</Button>
|
|
87
87
|
);
|
|
88
88
|
|
|
89
89
|
const button = screen.getByRole('button');
|
|
@@ -106,7 +106,7 @@ test('link styled as button', () => {
|
|
|
106
106
|
|
|
107
107
|
test('button styled as link', () => {
|
|
108
108
|
render(
|
|
109
|
-
<Button
|
|
109
|
+
<Button hasLinkStyle>Button text</Button>
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
const button = screen.getByRole('button');
|
|
@@ -7,11 +7,11 @@ const Button = ({
|
|
|
7
7
|
children,
|
|
8
8
|
className,
|
|
9
9
|
icon,
|
|
10
|
-
|
|
11
|
-
iconOnly = false,
|
|
10
|
+
hasLinkStyle,
|
|
12
11
|
href,
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
isIconLeft,
|
|
13
|
+
isIconOnly = false,
|
|
14
|
+
isSmall,
|
|
15
15
|
type = 'button',
|
|
16
16
|
width,
|
|
17
17
|
...props
|
|
@@ -26,19 +26,19 @@ const Button = ({
|
|
|
26
26
|
<WrapperTag
|
|
27
27
|
tagName={tagName}
|
|
28
28
|
className={[
|
|
29
|
-
!
|
|
29
|
+
!hasLinkStyle ? 'ds_button' : 'ds_link',
|
|
30
30
|
width && `ds_button--${width}`,
|
|
31
31
|
buttonStyle && `ds_button--${buttonStyle}`,
|
|
32
|
-
|
|
33
|
-
(icon && !
|
|
34
|
-
|
|
32
|
+
isSmall && 'ds_button--small',
|
|
33
|
+
(icon && !isIconOnly) ? 'ds_button--has-icon' : undefined,
|
|
34
|
+
isIconLeft && 'ds_button--has-icon--left',
|
|
35
35
|
className
|
|
36
36
|
].join(' ')}
|
|
37
37
|
href={href}
|
|
38
38
|
{...(tagName === 'button' ? { type: type } : {})}
|
|
39
39
|
{...props}
|
|
40
40
|
>
|
|
41
|
-
{
|
|
41
|
+
{isIconOnly ? <ScreenReaderText>{children}</ScreenReaderText> : children}
|
|
42
42
|
|
|
43
43
|
{icon && <Icon icon={icon}/>}
|
|
44
44
|
</WrapperTag>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import argTypes from '../../../.storybook/sgdsArgTypes';
|
|
3
|
+
|
|
4
|
+
import CategoryItem from './CategoryItem';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Category item',
|
|
8
|
+
component: CategoryItem,
|
|
9
|
+
argTypes: {
|
|
10
|
+
children: argTypes.children(),
|
|
11
|
+
headingLevel: argTypes.headingLevel(),
|
|
12
|
+
href: {
|
|
13
|
+
description: 'URL used in the title\'s link',
|
|
14
|
+
type: 'string'
|
|
15
|
+
},
|
|
16
|
+
linkComponent: argTypes.linkComponent(),
|
|
17
|
+
tagName: {
|
|
18
|
+
description: 'HTML tag name to use for the item',
|
|
19
|
+
type: 'string'
|
|
20
|
+
},
|
|
21
|
+
title: {
|
|
22
|
+
description: 'The title of the category item',
|
|
23
|
+
type: 'string'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
args: {
|
|
27
|
+
title: 'Public transport, bus passes and discounts',
|
|
28
|
+
children: 'Find information about local travel by road, rail and water and National Entitlement Cards.',
|
|
29
|
+
href: '/driving-transport/local-travel'
|
|
30
|
+
}
|
|
31
|
+
} satisfies Meta<typeof CategoryItem>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const NoSummary: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
children: undefined
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const DifferentHeadingLevel: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
headingLevel: 'h3'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const DifferentContainerElement: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
tagName: 'article'
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import CategoryItem from './CategoryItem';
|
|
4
|
+
|
|
5
|
+
const ITEM_TITLE = 'Bananas';
|
|
6
|
+
const ITEM_HREF = '#foo';
|
|
7
|
+
const ITEM_CONTENT = 'A banana is an elongated, edible fruit—botanically a berry—produced by several kinds of large treelike herbaceous flowering plants in the genus Musa.';
|
|
8
|
+
|
|
9
|
+
test('category item renders correctly', () => {
|
|
10
|
+
render(
|
|
11
|
+
<CategoryItem title={ITEM_TITLE} href={ITEM_HREF} data-testid="category-item">
|
|
12
|
+
{ITEM_CONTENT}
|
|
13
|
+
</CategoryItem>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const categoryItem = screen.getByTestId('category-item');
|
|
17
|
+
const title = screen.getByRole('heading');
|
|
18
|
+
const link = screen.getByRole('link');
|
|
19
|
+
const content = categoryItem.querySelector('p');
|
|
20
|
+
|
|
21
|
+
expect(categoryItem).toHaveClass('ds_category-item');
|
|
22
|
+
expect(categoryItem.tagName).toEqual('DIV');
|
|
23
|
+
|
|
24
|
+
expect(title).toHaveClass('ds_category-item__title');
|
|
25
|
+
expect(title.tagName).toEqual('H2');
|
|
26
|
+
expect(title.textContent).toEqual(ITEM_TITLE);
|
|
27
|
+
expect(title.parentElement).toEqual(categoryItem);
|
|
28
|
+
|
|
29
|
+
expect(link).toHaveClass('ds_category-item__link');
|
|
30
|
+
expect(link).toHaveAttribute('href', ITEM_HREF);
|
|
31
|
+
expect(link.textContent).toEqual(ITEM_TITLE);
|
|
32
|
+
expect(link.parentElement).toEqual(title);
|
|
33
|
+
|
|
34
|
+
expect(content).toHaveClass('ds_category-item__summary');
|
|
35
|
+
expect(content?.textContent).toEqual(ITEM_CONTENT);
|
|
36
|
+
expect(content?.parentElement).toEqual(categoryItem);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('custom heading level', () => {
|
|
40
|
+
render(
|
|
41
|
+
<CategoryItem title={ITEM_TITLE} href={ITEM_HREF} headingLevel="h3" data-testid="category-item">
|
|
42
|
+
{ITEM_CONTENT}
|
|
43
|
+
</CategoryItem>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const title = screen.getByRole('heading');
|
|
47
|
+
expect(title.tagName).toEqual('H3');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('custom element', () => {
|
|
51
|
+
render(
|
|
52
|
+
<CategoryItem title={ITEM_TITLE} href={ITEM_HREF} tagName="article" data-testid="category-item">
|
|
53
|
+
{ITEM_CONTENT}
|
|
54
|
+
</CategoryItem>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const categoryItem = screen.getByTestId('category-item');
|
|
58
|
+
expect(categoryItem.tagName).toEqual('ARTICLE');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('custom link component', () => {
|
|
62
|
+
render(
|
|
63
|
+
<CategoryItem title={ITEM_TITLE} href={ITEM_HREF} tagName="article" data-testid="category-item" linkComponent={
|
|
64
|
+
({ className, ...props }) => (
|
|
65
|
+
<span role="link" className={className} {...props}/>
|
|
66
|
+
)}>
|
|
67
|
+
{ITEM_CONTENT}
|
|
68
|
+
</CategoryItem>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const link = screen.getByRole('link');
|
|
72
|
+
|
|
73
|
+
expect(link?.tagName).toEqual('SPAN');
|
|
74
|
+
expect(link?.textContent).toEqual(ITEM_TITLE);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('passing additional props', () => {
|
|
78
|
+
render(
|
|
79
|
+
<CategoryItem data-test="foo" data-testid="category-item"/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const categoryItem = screen.getByTestId('category-item');
|
|
83
|
+
expect(categoryItem?.dataset.test).toEqual('foo');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('passing additional CSS classes', () => {
|
|
87
|
+
render(
|
|
88
|
+
<CategoryItem className="foo" data-testid="category-item"/>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const categoryItem = screen.getByTestId('category-item');
|
|
92
|
+
expect(categoryItem).toHaveClass('foo');
|
|
93
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ConditionalWrapper from "../../common/ConditionalWrapper";
|
|
2
|
+
import WrapperTag from "../../common/WrapperTag";
|
|
3
|
+
|
|
4
|
+
const CategoryItem = ({
|
|
5
|
+
children,
|
|
6
|
+
className,
|
|
7
|
+
headingLevel = 'h2',
|
|
8
|
+
href,
|
|
9
|
+
linkComponent,
|
|
10
|
+
tagName = 'div',
|
|
11
|
+
title,
|
|
12
|
+
...props
|
|
13
|
+
}: SGDS.Component.CategoryItem) => {
|
|
14
|
+
const LINK_CLASS = 'ds_category-item__link';
|
|
15
|
+
|
|
16
|
+
function getLinkElement(children: React.ReactNode) {
|
|
17
|
+
if (linkComponent) {
|
|
18
|
+
return linkComponent({ className: LINK_CLASS, href, children });
|
|
19
|
+
} else if (href) {
|
|
20
|
+
return <a href={href} className={LINK_CLASS}>{children}</a>;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<WrapperTag
|
|
26
|
+
tagName={tagName}
|
|
27
|
+
className={[
|
|
28
|
+
'ds_category-item',
|
|
29
|
+
className
|
|
30
|
+
].join(' ')}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<WrapperTag
|
|
34
|
+
className="ds_category-item__title"
|
|
35
|
+
tagName={headingLevel}
|
|
36
|
+
>
|
|
37
|
+
<ConditionalWrapper
|
|
38
|
+
condition={typeof href !== 'undefined'}
|
|
39
|
+
wrapper={(children: React.JSX.Element) => getLinkElement(children)}
|
|
40
|
+
>
|
|
41
|
+
{title}
|
|
42
|
+
</ConditionalWrapper>
|
|
43
|
+
</WrapperTag>
|
|
44
|
+
|
|
45
|
+
{children &&
|
|
46
|
+
<p className="ds_category-item__summary">
|
|
47
|
+
{children}
|
|
48
|
+
</p>
|
|
49
|
+
}
|
|
50
|
+
</WrapperTag>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
CategoryItem.displayName = 'CategoryItem';
|
|
55
|
+
|
|
56
|
+
export default CategoryItem;
|