@scottish-government/designsystem-react 0.7.1 → 0.8.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/@types/common/AbstractNotificationBanner.d.ts +2 -2
- package/@types/common/ActionLink.d.ts +8 -0
- package/@types/common/FileIcon.d.ts +1 -1
- package/@types/common/Icon.d.ts +1 -1
- package/@types/components/Breadcrumbs.d.ts +2 -5
- package/@types/components/Checkbox.d.ts +0 -2
- package/@types/components/ConfirmationMessage.d.ts +1 -1
- package/@types/components/ContentsNav.d.ts +4 -6
- package/@types/components/DatePicker.d.ts +1 -1
- package/@types/components/ErrorSummary.d.ts +3 -4
- package/@types/components/NotificationPanel.d.ts +1 -1
- package/@types/components/Pagination.d.ts +5 -4
- package/@types/components/PhaseBanner.d.ts +0 -1
- package/@types/components/Question.d.ts +1 -1
- package/@types/components/RadioButton.d.ts +0 -1
- package/@types/components/Select.d.ts +0 -7
- package/@types/components/SequentialNavigation.d.ts +4 -4
- package/@types/components/SideNavigation.d.ts +4 -5
- package/@types/components/SiteFooter.d.ts +25 -0
- package/@types/components/SiteHeader.d.ts +10 -3
- package/@types/components/SiteNavigation.d.ts +2 -3
- package/@types/components/SkipLinks.d.ts +3 -4
- package/@types/components/SummaryCard.d.ts +0 -2
- package/@types/components/SummaryList.d.ts +0 -13
- package/@types/components/Tabs.d.ts +0 -1
- package/@types/components/Tag.d.ts +1 -3
- package/@types/components/TaskList.d.ts +1 -0
- package/@types/sgds.d.ts +13 -2
- package/CHANGELOG.md +63 -1
- package/dist/common/AbstractNotificationBanner.jsx +8 -6
- package/dist/common/ActionLink.jsx +19 -0
- package/dist/common/FileIcon.jsx +2 -7
- package/dist/common/Icon.jsx +3 -9
- package/dist/components/Accordion/Accordion.jsx +2 -2
- package/dist/components/Breadcrumbs/Breadcrumbs.jsx +20 -15
- package/dist/components/Checkbox/Checkbox.jsx +2 -30
- package/dist/components/Checkbox/CheckboxGroup.jsx +69 -0
- package/dist/components/ContentsNav/ContentsNav.jsx +27 -16
- package/dist/components/CookieBanner/CookieBanner.jsx +1 -0
- package/dist/components/DatePicker/DatePicker.jsx +5 -5
- package/dist/components/ErrorSummary/ErrorSummary.jsx +28 -18
- package/dist/components/NotificationBanner/NotificationBanner.jsx +2 -2
- package/dist/components/Pagination/Pagination.jsx +42 -22
- package/dist/components/PhaseBanner/PhaseBanner.jsx +3 -3
- package/dist/components/Question/Question.jsx +3 -3
- package/dist/components/RadioButton/RadioButton.jsx +3 -17
- package/dist/components/RadioButton/RadioGroup.jsx +61 -0
- package/dist/components/Select/Select.jsx +4 -7
- package/dist/components/SequentialNavigation/SequentialNavigation.jsx +31 -18
- package/dist/components/SideNavigation/SideNavigation.jsx +17 -16
- package/dist/components/SiteFooter/SiteFooter.jsx +104 -0
- package/dist/components/SiteHeader/SiteHeader.jsx +113 -32
- package/dist/components/SiteNavigation/SiteNavigation.jsx +20 -7
- package/dist/components/SkipLinks/SkipLinks.jsx +10 -10
- package/dist/components/SummaryCard/SummaryCard.jsx +25 -14
- package/dist/components/SummaryList/SummaryList.jsx +65 -47
- package/dist/components/Tabs/Tabs.jsx +6 -6
- package/dist/components/Tag/Tag.jsx +2 -2
- package/dist/components/TaskList/TaskList.jsx +14 -3
- package/dist/components/TextInput/TextInput.jsx +3 -3
- package/dist/components/Textarea/Textarea.jsx +3 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/AbstractNotificationBanner.test.tsx +1 -1
- package/src/common/AbstractNotificationBanner.tsx +14 -13
- package/src/common/ActionLink.test.tsx +80 -0
- package/src/common/ActionLink.tsx +27 -0
- package/src/common/ConditionalWrapper.tsx +1 -1
- package/src/common/FileIcon.tsx +7 -11
- package/src/common/HintText.tsx +2 -2
- package/src/common/Icon.tsx +13 -17
- package/src/common/ScreenReaderText.tsx +2 -2
- package/src/common/WrapperTag.tsx +2 -2
- package/src/components/Accordion/Accordion.test.tsx +1 -1
- package/src/components/Accordion/Accordion.tsx +6 -7
- package/src/components/AspectBox/AspectBox.tsx +2 -2
- package/src/components/BackToTop/BackToTop.tsx +2 -2
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +79 -47
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +31 -31
- package/src/components/Button/Button.tsx +2 -2
- package/src/components/Checkbox/Checkbox.test.tsx +1 -96
- package/src/components/Checkbox/Checkbox.tsx +3 -55
- package/src/components/Checkbox/CheckboxGroup.test.tsx +37 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +46 -0
- package/src/components/ConfirmationMessage/ConfirmationMessage.tsx +2 -2
- package/src/components/ContentsNav/ContentsNav.test.tsx +40 -51
- package/src/components/ContentsNav/ContentsNav.tsx +32 -25
- package/src/components/CookieBanner/CookieBanner.tsx +3 -3
- package/src/components/DatePicker/DatePicker.test.tsx +1 -1
- package/src/components/DatePicker/DatePicker.tsx +7 -7
- package/src/components/Details/Details.tsx +2 -2
- package/src/components/ErrorMessage/ErrorMessage.tsx +2 -2
- package/src/components/ErrorSummary/ErrorSummary.test.tsx +40 -34
- package/src/components/ErrorSummary/ErrorSummary.tsx +40 -32
- package/src/components/FileDownload/FileDownload.tsx +2 -2
- package/src/components/HideThisPage/HideThisPage.tsx +2 -2
- package/src/components/InsetText/InsetText.tsx +2 -2
- package/src/components/NotificationBanner/NotificationBanner.tsx +6 -7
- package/src/components/NotificationPanel/NotificationPanel.tsx +2 -2
- package/src/components/PageHeader/PageHeader.tsx +2 -2
- package/src/components/PageMetadata/PageMetadata.tsx +4 -5
- package/src/components/Pagination/Pagination.test.tsx +26 -7
- package/src/components/Pagination/Pagination.tsx +70 -36
- package/src/components/PhaseBanner/PhaseBanner.tsx +4 -5
- package/src/components/Question/Question.test.tsx +1 -1
- package/src/components/Question/Question.tsx +5 -5
- package/src/components/RadioButton/RadioButton.test.tsx +7 -126
- package/src/components/RadioButton/RadioButton.tsx +4 -41
- package/src/components/RadioButton/RadioGroup.test.tsx +65 -0
- package/src/components/RadioButton/RadioGroup.tsx +38 -0
- package/src/components/Select/Select.test.tsx +39 -37
- package/src/components/Select/Select.tsx +7 -22
- package/src/components/SequentialNavigation/SequentialNavigation.test.tsx +32 -21
- package/src/components/SequentialNavigation/SequentialNavigation.tsx +52 -30
- package/src/components/SideNavigation/SideNavigation.test.tsx +39 -85
- package/src/components/SideNavigation/SideNavigation.tsx +27 -29
- package/src/components/SiteFooter/SiteFooter.test.tsx +153 -0
- package/src/components/SiteFooter/SiteFooter.tsx +107 -0
- package/src/components/SiteHeader/SiteHeader.test.tsx +87 -79
- package/src/components/SiteHeader/SiteHeader.tsx +103 -40
- package/src/components/SiteNavigation/SiteNavigation.test.tsx +42 -23
- package/src/components/SiteNavigation/SiteNavigation.tsx +28 -16
- package/src/components/SiteSearch/SiteSearch.tsx +2 -2
- package/src/components/SkipLinks/SkipLinks.test.tsx +22 -10
- package/src/components/SkipLinks/SkipLinks.tsx +16 -15
- package/src/components/SummaryCard/SummaryCard.test.tsx +31 -35
- package/src/components/SummaryCard/SummaryCard.tsx +39 -28
- package/src/components/SummaryList/SummaryList.test.tsx +49 -148
- package/src/components/SummaryList/SummaryList.tsx +54 -92
- package/src/components/Table/Table.tsx +2 -2
- package/src/components/Tabs/Tabs.tsx +14 -15
- package/src/components/Tag/Tag.test.tsx +4 -4
- package/src/components/Tag/Tag.tsx +4 -4
- package/src/components/TaskList/TaskList.test.tsx +26 -0
- package/src/components/TaskList/TaskList.tsx +21 -11
- package/src/components/TextInput/TextInput.test.tsx +1 -1
- package/src/components/TextInput/TextInput.tsx +5 -5
- package/src/components/Textarea/Textarea.test.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +5 -5
- package/src/components/WarningText/WarningText.tsx +2 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { Children } from 'react';
|
|
2
|
+
import ConditionalWrapper from '../../common/ConditionalWrapper';
|
|
3
|
+
|
|
4
|
+
const License = ({
|
|
5
|
+
children,
|
|
6
|
+
...props
|
|
7
|
+
}: SGDS.Component.SiteFooter.License) => {
|
|
8
|
+
return (
|
|
9
|
+
<div className="ds_site-footer__copyright" {...props}>
|
|
10
|
+
{children}
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Links = ({
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}: SGDS.Component.SiteFooter.Links) => {
|
|
19
|
+
return (
|
|
20
|
+
<ul className="ds_site-footer__site-items" {...props}>
|
|
21
|
+
{children}
|
|
22
|
+
</ul>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Link = ({
|
|
27
|
+
children,
|
|
28
|
+
href,
|
|
29
|
+
linkComponent,
|
|
30
|
+
...props
|
|
31
|
+
}: SGDS.Component.SiteFooter.Link) => {
|
|
32
|
+
function processChildren(children: React.ReactNode) {
|
|
33
|
+
if (linkComponent) {
|
|
34
|
+
return linkComponent({ href, children });
|
|
35
|
+
} else if (href) {
|
|
36
|
+
return <a href={href}>{children}</a>;
|
|
37
|
+
} else {
|
|
38
|
+
return children;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return <li className="ds_site-items__item" {...props}>
|
|
43
|
+
{processChildren(children)}
|
|
44
|
+
</li>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const Org = ({
|
|
48
|
+
href,
|
|
49
|
+
title,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: SGDS.Component.SiteFooter.Org) => {
|
|
53
|
+
children = Children.map(children, child => {
|
|
54
|
+
let thisChild = child as React.ReactElement<HTMLElement>;
|
|
55
|
+
if (thisChild && ['img', 'svg', 'picture'].includes(thisChild.type as string)) {
|
|
56
|
+
return React.cloneElement(thisChild, { className: 'ds_site-footer__org-logo' });
|
|
57
|
+
} else {
|
|
58
|
+
return child;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="ds_site-footer__org" {...props}>
|
|
64
|
+
<ConditionalWrapper
|
|
65
|
+
condition={typeof href !== 'undefined'}
|
|
66
|
+
wrapper={(children: React.JSX.Element) => <a className="ds_site-footer__org-link" title={title} href={href}>{children}</a>}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</ConditionalWrapper>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const SiteFooter = ({
|
|
75
|
+
children,
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: SGDS.Component.SiteFooter) => {
|
|
79
|
+
return (
|
|
80
|
+
<footer
|
|
81
|
+
className={[
|
|
82
|
+
"ds_site-footer",
|
|
83
|
+
className
|
|
84
|
+
].join(' ')}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
<div className="ds_wrapper">
|
|
88
|
+
<div className="ds_site-footer__content">
|
|
89
|
+
{children}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</footer>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
SiteFooter.Links = Links;
|
|
97
|
+
SiteFooter.Link = Link;
|
|
98
|
+
SiteFooter.License = License;
|
|
99
|
+
SiteFooter.Org = Org;
|
|
100
|
+
|
|
101
|
+
SiteFooter.displayName = 'SiteFooter';
|
|
102
|
+
Links.displayName = 'SiteFooter.Links';
|
|
103
|
+
Link.displayName = 'SiteFooter.Link';
|
|
104
|
+
License.displayName = 'SiteFooter.License';
|
|
105
|
+
Org.displayName = 'SiteFooter.Org';
|
|
106
|
+
|
|
107
|
+
export default SiteFooter;
|
|
@@ -7,25 +7,29 @@ import PhaseBanner from '../PhaseBanner/PhaseBanner';
|
|
|
7
7
|
|
|
8
8
|
test('site header renders correctly (maximal, testing markup structure)', () => {
|
|
9
9
|
render(
|
|
10
|
-
<SiteHeader
|
|
11
|
-
|
|
12
|
-
alt
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
<SiteHeader>
|
|
11
|
+
<SiteHeader.Brand homeUrl="/" siteTitle="Design System React">
|
|
12
|
+
<img src="./scottish-government.svg" alt="gov.scot" loading="lazy" width="300" height="58" />
|
|
13
|
+
</SiteHeader.Brand>
|
|
14
|
+
<SiteHeader.Navigation>
|
|
15
|
+
<SiteNavigation>
|
|
16
|
+
<SiteNavigation.Item href="#about">About</SiteNavigation.Item>
|
|
17
|
+
<SiteNavigation.Item href="#get-started">Get started</SiteNavigation.Item>
|
|
18
|
+
<SiteNavigation.Item href="#styles">Styles</SiteNavigation.Item>
|
|
19
|
+
<SiteNavigation.Item href="#components" current>Components</SiteNavigation.Item>
|
|
20
|
+
<SiteNavigation.Item href="#patterns">Patterns</SiteNavigation.Item>
|
|
21
|
+
<SiteNavigation.Item href="#guidance">Guidance</SiteNavigation.Item>
|
|
22
|
+
</SiteNavigation>
|
|
23
|
+
</SiteHeader.Navigation>
|
|
24
|
+
<SiteHeader.Search>
|
|
25
|
+
<SiteSearch id="site-header-search"/>
|
|
26
|
+
</SiteHeader.Search>
|
|
27
|
+
<SiteHeader.Phase>
|
|
28
|
+
<PhaseBanner phaseName="Beta">
|
|
29
|
+
This is a new service. Your <a href="#feedback">feedback</a> will help us to improve it.
|
|
30
|
+
</PhaseBanner>
|
|
31
|
+
</SiteHeader.Phase>
|
|
32
|
+
</SiteHeader>
|
|
29
33
|
);
|
|
30
34
|
|
|
31
35
|
const siteHeader = screen.getByRole('banner');
|
|
@@ -37,7 +41,7 @@ test('site header renders correctly (maximal, testing markup structure)', () =>
|
|
|
37
41
|
const siteHeaderNavigationMobile = within(siteHeader).getAllByRole('navigation')[0];
|
|
38
42
|
const siteHeaderNavigationDesktop = within(siteHeader).getAllByRole('navigation')[1];
|
|
39
43
|
const siteHeaderPhaseBanner = siteHeader.querySelector('.ds_phase-banner');
|
|
40
|
-
const siteHeaderSearch = within(siteHeader).getByRole('search').parentElement;
|
|
44
|
+
const siteHeaderSearch = within(siteHeader).getByRole('search').parentElement?.parentElement;
|
|
41
45
|
|
|
42
46
|
expect(siteHeader).toHaveClass('ds_site-header');
|
|
43
47
|
expect(siteHeaderContentWrapper).toHaveClass('ds_wrapper');
|
|
@@ -67,15 +71,12 @@ test('site header renders correctly (maximal, testing markup structure)', () =>
|
|
|
67
71
|
});
|
|
68
72
|
|
|
69
73
|
test('site header branding: logo only, default URL', () => {
|
|
70
|
-
const LOGO = {
|
|
71
|
-
alt: 'The Scottish Government',
|
|
72
|
-
src: './scottish-government.svg'
|
|
73
|
-
};
|
|
74
|
-
|
|
75
74
|
render(
|
|
76
|
-
<SiteHeader
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
<SiteHeader>
|
|
76
|
+
<SiteHeader.Brand>
|
|
77
|
+
<img src="./scottish-government.svg" alt="gov.scot" loading="lazy" width="300" height="58" />
|
|
78
|
+
</SiteHeader.Brand>
|
|
79
|
+
</SiteHeader>
|
|
79
80
|
);
|
|
80
81
|
|
|
81
82
|
const siteHeader = screen.getByRole('banner');
|
|
@@ -87,25 +88,22 @@ test('site header branding: logo only, default URL', () => {
|
|
|
87
88
|
expect(siteHeaderLogoLink).toHaveAttribute('href', '/');
|
|
88
89
|
|
|
89
90
|
expect(siteHeaderLogoImg).toHaveClass('ds_site-branding__logo-image');
|
|
90
|
-
expect(siteHeaderLogoImg).toHaveAttribute('src', LOGO.src);
|
|
91
|
-
expect(siteHeaderLogoImg).toHaveAttribute('alt', LOGO.alt);
|
|
92
91
|
|
|
93
92
|
expect(siteHeaderLogoImg.parentElement).toEqual(siteHeaderLogoLink);
|
|
94
93
|
expect(siteHeaderLogoLink.parentElement).toEqual(siteHeaderBranding);
|
|
94
|
+
|
|
95
|
+
expect(siteHeaderLogoImg).toHaveClass('ds_site-branding__logo-image');
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
test('site header branding: logo and site title', () => {
|
|
98
|
-
const LOGO = {
|
|
99
|
-
alt: 'The Scottish Government',
|
|
100
|
-
src: './scottish-government.svg'
|
|
101
|
-
};
|
|
102
99
|
const SITE_TITLE_CONTENT = 'Design System React';
|
|
103
100
|
|
|
104
101
|
render(
|
|
105
|
-
<SiteHeader
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
<SiteHeader>
|
|
103
|
+
<SiteHeader.Brand homeUrl="/" siteTitle={SITE_TITLE_CONTENT}>
|
|
104
|
+
<img src="./scottish-government.svg" alt="gov.scot" loading="lazy" width="300" height="58" />
|
|
105
|
+
</SiteHeader.Brand>
|
|
106
|
+
</SiteHeader>
|
|
109
107
|
);
|
|
110
108
|
|
|
111
109
|
const siteHeader = screen.getByRole('banner');
|
|
@@ -120,64 +118,66 @@ test('site header branding: logo and site title', () => {
|
|
|
120
118
|
});
|
|
121
119
|
|
|
122
120
|
test('site header branding: custom link URL', () => {
|
|
123
|
-
const
|
|
124
|
-
alt: 'The Scottish Government',
|
|
125
|
-
href: '/home.aspx',
|
|
126
|
-
src: './scottish-government.svg'
|
|
127
|
-
};
|
|
121
|
+
const HOME_URL = '/home.aspx';
|
|
128
122
|
|
|
129
123
|
render(
|
|
130
|
-
<SiteHeader
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
<SiteHeader>
|
|
125
|
+
<SiteHeader.Brand homeUrl={HOME_URL}>
|
|
126
|
+
<img src="./scottish-government.svg" alt="gov.scot" loading="lazy" width="300" height="58" />
|
|
127
|
+
</SiteHeader.Brand>
|
|
128
|
+
</SiteHeader>
|
|
133
129
|
);
|
|
134
130
|
|
|
135
131
|
const siteHeader = screen.getByRole('banner');
|
|
136
132
|
const siteHeaderLogoLink = within(siteHeader).getByRole('link');
|
|
137
133
|
|
|
138
|
-
expect(siteHeaderLogoLink).toHaveAttribute('href',
|
|
134
|
+
expect(siteHeaderLogoLink).toHaveAttribute('href', HOME_URL);
|
|
139
135
|
});
|
|
140
136
|
|
|
141
|
-
test('site header with
|
|
142
|
-
const
|
|
143
|
-
action: 'apple',
|
|
144
|
-
autocompleteEndpoint: 'banana',
|
|
145
|
-
autocompleteSuggestionMappingFunction: 'cucumber',
|
|
146
|
-
className: 'durian',
|
|
147
|
-
id: 'eggplant',
|
|
148
|
-
method: 'POST',
|
|
149
|
-
name: 'guava',
|
|
150
|
-
placeholder: 'haw'
|
|
151
|
-
};
|
|
137
|
+
test('site header logo link link with custom element', () => {
|
|
138
|
+
const LINK_CONTENT = <img src="./scottish-government.svg" alt="gov.scot" loading="lazy" width="300" height="58" />
|
|
152
139
|
|
|
153
140
|
render(
|
|
154
|
-
|
|
155
|
-
<SiteHeader
|
|
156
|
-
|
|
157
|
-
|
|
141
|
+
<SiteHeader>
|
|
142
|
+
<SiteHeader.Brand linkComponent={
|
|
143
|
+
({ className, ...props }) => (
|
|
144
|
+
<strong role="link" className={className} {...props}/>
|
|
145
|
+
)}>
|
|
146
|
+
{LINK_CONTENT}
|
|
147
|
+
</SiteHeader.Brand>
|
|
148
|
+
</SiteHeader>
|
|
158
149
|
);
|
|
159
150
|
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const siteSearchReference = screen.getByTestId('sitesearch');
|
|
151
|
+
const item = screen.getByRole('banner');
|
|
152
|
+
const link = within(item).getByRole('link');
|
|
163
153
|
|
|
164
|
-
expect(
|
|
154
|
+
expect(link?.tagName).toEqual('STRONG');
|
|
165
155
|
});
|
|
166
156
|
|
|
167
157
|
test('site header with site navigation', () => {
|
|
168
|
-
const NAVIGATION_ITEMS =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
158
|
+
const NAVIGATION_ITEMS = (
|
|
159
|
+
<>
|
|
160
|
+
<SiteNavigation.Item href="#about">About</SiteNavigation.Item>
|
|
161
|
+
<SiteNavigation.Item href="#get-started">Get started</SiteNavigation.Item>
|
|
162
|
+
<SiteNavigation.Item href="#styles">Styles</SiteNavigation.Item>
|
|
163
|
+
<SiteNavigation.Item href="#components" current>Components</SiteNavigation.Item>
|
|
164
|
+
<SiteNavigation.Item href="#patterns">Patterns</SiteNavigation.Item>
|
|
165
|
+
<SiteNavigation.Item href="#guidance">Guidance</SiteNavigation.Item>
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
176
168
|
|
|
177
169
|
render(
|
|
178
170
|
<>
|
|
179
|
-
<SiteHeader
|
|
180
|
-
|
|
171
|
+
<SiteHeader>
|
|
172
|
+
<SiteHeader.Navigation>
|
|
173
|
+
<SiteNavigation>
|
|
174
|
+
{NAVIGATION_ITEMS}
|
|
175
|
+
</SiteNavigation>
|
|
176
|
+
</SiteHeader.Navigation>
|
|
177
|
+
</SiteHeader>
|
|
178
|
+
<SiteNavigation data-testid="sitenavigation">
|
|
179
|
+
{NAVIGATION_ITEMS}
|
|
180
|
+
</SiteNavigation>
|
|
181
181
|
</>
|
|
182
182
|
);
|
|
183
183
|
|
|
@@ -212,15 +212,23 @@ test('site header with site navigation', () => {
|
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
test('site header with phase banner', () => {
|
|
215
|
-
const
|
|
215
|
+
const PHASE_BANNER = {
|
|
216
216
|
content: 'My content',
|
|
217
217
|
phaseName: 'Beta'
|
|
218
218
|
};
|
|
219
219
|
|
|
220
220
|
render(
|
|
221
221
|
<>
|
|
222
|
-
<SiteHeader
|
|
223
|
-
|
|
222
|
+
<SiteHeader>
|
|
223
|
+
<SiteHeader.Phase>
|
|
224
|
+
<PhaseBanner phaseName={PHASE_BANNER.phaseName}>
|
|
225
|
+
{PHASE_BANNER.content}
|
|
226
|
+
</PhaseBanner>
|
|
227
|
+
</SiteHeader.Phase>
|
|
228
|
+
</SiteHeader>
|
|
229
|
+
<PhaseBanner data-testid="phasebanner" phaseName={PHASE_BANNER.phaseName}>
|
|
230
|
+
{PHASE_BANNER.content}
|
|
231
|
+
</PhaseBanner>
|
|
224
232
|
</>
|
|
225
233
|
);
|
|
226
234
|
|
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef } from 'react';
|
|
4
|
-
|
|
3
|
+
import React, { Children, useEffect, useRef } from 'react';
|
|
5
4
|
import Icon from '../../common/Icon';
|
|
6
|
-
import
|
|
7
|
-
import SiteNavigation from "../SiteNavigation/SiteNavigation";
|
|
8
|
-
import SiteSearch from "../SiteSearch/SiteSearch";
|
|
5
|
+
import SiteNavigation from '../SiteNavigation/SiteNavigation';
|
|
9
6
|
|
|
10
7
|
// @ts-ignore
|
|
11
8
|
import DSMobileMenu from '@scottish-government/design-system/src/components/site-navigation/site-navigation';
|
|
12
9
|
|
|
13
|
-
const
|
|
10
|
+
const Brand = ({
|
|
11
|
+
children,
|
|
12
|
+
homeUrl = '/',
|
|
13
|
+
linkComponent,
|
|
14
|
+
siteTitle
|
|
15
|
+
}: SGDS.Component.SiteHeader.Brand) => {
|
|
16
|
+
function processChildren(children: React.ReactNode) {
|
|
17
|
+
const image = React.cloneElement(children as React.ReactElement<HTMLImageElement>, { className: 'ds_site-branding__logo-image' });
|
|
18
|
+
|
|
19
|
+
if (linkComponent) {
|
|
20
|
+
return linkComponent({ className: 'ds_site-branding__logo ds_site-branding__link', href: homeUrl, children: image });
|
|
21
|
+
} else if (homeUrl) {
|
|
22
|
+
return <a href={homeUrl} className="ds_site-branding__logo ds_site-branding__link">{image}</a>;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{processChildren(children)}
|
|
29
|
+
|
|
30
|
+
{siteTitle &&
|
|
31
|
+
<div className="ds_site-branding__title">
|
|
32
|
+
{siteTitle}
|
|
33
|
+
</div>
|
|
34
|
+
}
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Navigation = ({
|
|
40
|
+
children
|
|
41
|
+
}: any) => {
|
|
42
|
+
return children;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Phase = ({
|
|
46
|
+
children
|
|
47
|
+
}: any) => {
|
|
48
|
+
return children;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const Search = ({
|
|
52
|
+
children
|
|
53
|
+
}: any) => {
|
|
54
|
+
return children;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SiteHeader = ({
|
|
58
|
+
children,
|
|
14
59
|
logo = {},
|
|
15
60
|
navigationItems,
|
|
16
61
|
phaseBanner,
|
|
17
62
|
siteSearch,
|
|
18
63
|
siteTitle,
|
|
19
64
|
...props
|
|
20
|
-
}) => {
|
|
65
|
+
}: SGDS.Component.SiteHeader) => {
|
|
21
66
|
const mobileMenuRef = useRef(null);
|
|
22
67
|
|
|
68
|
+
let branding: React.ReactNode;
|
|
69
|
+
let navigation: React.ReactNode;
|
|
70
|
+
let mobileNavigation: React.ReactNode;
|
|
71
|
+
let phase: React.ReactNode;
|
|
72
|
+
let search: React.ReactNode;
|
|
73
|
+
|
|
74
|
+
// assign to slots
|
|
75
|
+
Children.forEach(children, (child: React.ReactNode) => {
|
|
76
|
+
const thisChild = child as React.ReactElement<any>;
|
|
77
|
+
if (thisChild && thisChild.type === Brand) {
|
|
78
|
+
branding = thisChild;
|
|
79
|
+
} else if (thisChild && thisChild.type === Navigation) {
|
|
80
|
+
navigation = thisChild;
|
|
81
|
+
|
|
82
|
+
if (thisChild.props.children.type === SiteNavigation) {
|
|
83
|
+
mobileNavigation = React.cloneElement(thisChild.props.children as React.ReactElement<SGDS.Component.SiteNavigation>, { className: 'ds_site-navigation--mobile', id: 'mobile-navigation', ref: mobileMenuRef});
|
|
84
|
+
}
|
|
85
|
+
} else if (thisChild && thisChild.type === Phase) {
|
|
86
|
+
phase = thisChild;
|
|
87
|
+
} else if (thisChild && thisChild.type === Search) {
|
|
88
|
+
search = thisChild;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
23
92
|
useEffect(() => {
|
|
24
93
|
if (mobileMenuRef.current) {
|
|
25
94
|
new DSMobileMenu(mobileMenuRef.current).init();
|
|
@@ -31,50 +100,35 @@ const SiteHeader: React.FC<SGDS.Component.SiteHeader> = ({
|
|
|
31
100
|
<div className="ds_wrapper">
|
|
32
101
|
<div className="ds_site-header__content">
|
|
33
102
|
<div className="ds_site-branding">
|
|
34
|
-
{
|
|
35
|
-
<a className="ds_site-branding__logo ds_site-branding__link" href={logo.href ? logo.href : '/'}>
|
|
36
|
-
<img className="ds_site-branding__logo-image" src={logo.src} alt={logo.alt} />
|
|
37
|
-
</a>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
{siteTitle && <div className="ds_site-branding__title">
|
|
41
|
-
{siteTitle}
|
|
42
|
-
</div>}
|
|
103
|
+
{branding}
|
|
43
104
|
</div>
|
|
44
105
|
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
{mobileNavigation &&
|
|
107
|
+
<>
|
|
108
|
+
<div className="ds_site-header__controls">
|
|
109
|
+
<label aria-controls="mobile-navigation" className="ds_site-header__control js-toggle-menu" htmlFor="menu">
|
|
110
|
+
<span className="ds_site-header__control-text">Menu</span>
|
|
111
|
+
<Icon fill className="ds_site-header__control-icon" icon="Menu" aria-hidden="true" />
|
|
112
|
+
<Icon fill className="ds_site-header__control-icon ds_site-header__control-icon--active-icon" icon="Close" aria-hidden="true" />
|
|
113
|
+
</label>
|
|
114
|
+
</div>
|
|
54
115
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
{navigationItems &&
|
|
59
|
-
<SiteNavigation id="mobile-navigation" className="ds_site-navigation--mobile" items={navigationItems} ref={mobileMenuRef} />
|
|
116
|
+
<input className="ds_site-navigation__toggle" id="menu" type="checkbox" />
|
|
117
|
+
</>
|
|
60
118
|
}
|
|
61
119
|
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
}
|
|
120
|
+
{mobileNavigation}
|
|
121
|
+
|
|
122
|
+
<div className="ds_site-header__search">{search}</div>
|
|
65
123
|
</div>
|
|
66
124
|
</div>
|
|
67
125
|
|
|
68
|
-
{
|
|
69
|
-
<PhaseBanner phaseName={phaseBanner.phaseName}>
|
|
70
|
-
{phaseBanner.content}
|
|
71
|
-
</PhaseBanner>
|
|
72
|
-
}
|
|
126
|
+
{phase}
|
|
73
127
|
|
|
74
|
-
{
|
|
128
|
+
{navigation &&
|
|
75
129
|
<div className="ds_site-header__navigation">
|
|
76
130
|
<div className="ds_wrapper">
|
|
77
|
-
|
|
131
|
+
{navigation}
|
|
78
132
|
</div>
|
|
79
133
|
</div>
|
|
80
134
|
}
|
|
@@ -82,6 +136,15 @@ const SiteHeader: React.FC<SGDS.Component.SiteHeader> = ({
|
|
|
82
136
|
);
|
|
83
137
|
};
|
|
84
138
|
|
|
139
|
+
SiteHeader.Brand = Brand;
|
|
140
|
+
SiteHeader.Navigation = Navigation;
|
|
141
|
+
SiteHeader.Phase = Phase;
|
|
142
|
+
SiteHeader.Search = Search;
|
|
143
|
+
|
|
85
144
|
SiteHeader.displayName = 'SiteHeader';
|
|
145
|
+
Brand.displayName = 'SiteHeader.Brand';
|
|
146
|
+
Navigation.displayName = 'SiteHeader.Navigation';
|
|
147
|
+
Phase.displayName = 'SiteHeader.Phase';
|
|
148
|
+
Search.displayName = 'SiteHeader.Search';
|
|
86
149
|
|
|
87
150
|
export default SiteHeader;
|
|
@@ -2,23 +2,23 @@ import { test, expect } from 'vitest';
|
|
|
2
2
|
import { render, screen, within } from '@testing-library/react';
|
|
3
3
|
import SiteNavigation from './SiteNavigation';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
{title: 'Get started', href: '#get-started'},
|
|
8
|
-
{title: 'Styles', href: '#styles'},
|
|
9
|
-
{title: 'Components', href: '#components'},
|
|
10
|
-
{title: 'Patterns', href: '#patterns'},
|
|
11
|
-
{title: 'Guidance', href: '#guidance'},
|
|
12
|
-
]
|
|
5
|
+
const LINK_HREF = '#about';
|
|
6
|
+
const LINK_TEXT = 'About';
|
|
13
7
|
|
|
14
8
|
test('renders correctly', () => {
|
|
15
9
|
render(
|
|
16
|
-
<SiteNavigation
|
|
10
|
+
<SiteNavigation>
|
|
11
|
+
<SiteNavigation.Item href="#about">About</SiteNavigation.Item>
|
|
12
|
+
<SiteNavigation.Item href="#get-started">Get started</SiteNavigation.Item>
|
|
13
|
+
<SiteNavigation.Item href="#styles">Styles</SiteNavigation.Item>
|
|
14
|
+
<SiteNavigation.Item href="#components">Components</SiteNavigation.Item>
|
|
15
|
+
<SiteNavigation.Item href="#patterns">Patterns</SiteNavigation.Item>
|
|
16
|
+
<SiteNavigation.Item href="#guidance">Guidance</SiteNavigation.Item>
|
|
17
|
+
</SiteNavigation>
|
|
17
18
|
);
|
|
18
19
|
|
|
19
20
|
const nav = screen.getByRole('navigation');
|
|
20
21
|
const list = within(nav).getByRole('list');
|
|
21
|
-
const listItems = within(list).getAllByRole('listitem');
|
|
22
22
|
|
|
23
23
|
// check nav
|
|
24
24
|
expect(nav).toHaveClass('ds_site-navigation');
|
|
@@ -27,25 +27,44 @@ test('renders correctly', () => {
|
|
|
27
27
|
// check list
|
|
28
28
|
expect(list.tagName).toEqual('UL');
|
|
29
29
|
expect(list).toHaveClass('ds_site-navigation__list');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('site navigation link renders correctly', () => {
|
|
33
|
+
render(
|
|
34
|
+
<SiteNavigation.Item href={LINK_HREF}>{LINK_TEXT}</SiteNavigation.Item>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const listItem =screen.getByRole('listitem');
|
|
38
|
+
const link = within(listItem).getByRole('link');
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
expect(listItems.length).toEqual(ITEMS.length);
|
|
40
|
+
expect(listItem).toHaveClass('ds_site-navigation__item');
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
expect(link).toHaveClass('ds_site-navigation__link');
|
|
43
|
+
expect(link).not.toHaveClass('ds_current');
|
|
44
|
+
expect(link.textContent).toEqual(LINK_TEXT);
|
|
45
|
+
expect(link).toHaveAttribute('href', LINK_HREF)
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('site navigation link with custom element', () => {
|
|
49
|
+
render(
|
|
50
|
+
<SiteNavigation.Item href={LINK_HREF} linkComponent={
|
|
51
|
+
({ className, ...props }) => (
|
|
52
|
+
<strong role="link" className={className} {...props}/>
|
|
53
|
+
)}>
|
|
54
|
+
{LINK_TEXT}
|
|
55
|
+
</SiteNavigation.Item>
|
|
56
|
+
);
|
|
36
57
|
|
|
37
|
-
|
|
58
|
+
const item = screen.getByRole('listitem');
|
|
59
|
+
const link = within(item).queryByRole('link');
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(link.textContent).toEqual(ITEMS[index].title);
|
|
42
|
-
expect(link).toHaveAttribute('href', ITEMS[index].href)
|
|
43
|
-
});
|
|
61
|
+
expect(link?.tagName).toEqual('STRONG');
|
|
62
|
+
expect(link?.textContent).toEqual(LINK_TEXT);
|
|
44
63
|
});
|
|
45
64
|
|
|
46
65
|
test('highlights current item', () => {
|
|
47
66
|
render(
|
|
48
|
-
<SiteNavigation
|
|
67
|
+
<SiteNavigation.Item href={LINK_HREF} current>{LINK_TEXT}</SiteNavigation.Item>
|
|
49
68
|
);
|
|
50
69
|
|
|
51
70
|
const link = screen.getByRole('link');
|
|
@@ -55,7 +74,7 @@ test('highlights current item', () => {
|
|
|
55
74
|
|
|
56
75
|
test('passing additional props', () => {
|
|
57
76
|
render(
|
|
58
|
-
<SiteNavigation data-test="foo"
|
|
77
|
+
<SiteNavigation data-test="foo"/>
|
|
59
78
|
);
|
|
60
79
|
|
|
61
80
|
const nav = screen.getByRole('navigation');
|
|
@@ -64,7 +83,7 @@ test('passing additional props', () => {
|
|
|
64
83
|
|
|
65
84
|
test('passing additional CSS classes', () => {
|
|
66
85
|
render(
|
|
67
|
-
<SiteNavigation className="foo"
|
|
86
|
+
<SiteNavigation className="foo"/>
|
|
68
87
|
);
|
|
69
88
|
|
|
70
89
|
const nav = screen.getByRole('navigation');
|