@scottish-government/designsystem-react 0.7.0 → 0.8.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/@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/Accordion.d.ts +0 -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 +12 -7
- package/dist/components/Breadcrumbs/Breadcrumbs.jsx +20 -15
- package/dist/components/Checkbox/Checkbox.jsx +4 -29
- package/dist/components/{aspect-box/aspect-box.jsx → Checkbox/CheckboxGroup.jsx} +14 -30
- 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 +7 -17
- package/dist/components/RadioButton/RadioGroup.jsx +21 -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/hooks/useTracking.js +21 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context.js +5 -0
- package/package.json +2 -2
- 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 +17 -4
- package/src/components/Accordion/Accordion.tsx +19 -14
- package/src/components/AspectBox/AspectBox.tsx +2 -2
- package/src/components/BackToTop/BackToTop.tsx +2 -2
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +79 -48
- 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 +8 -55
- package/src/components/Checkbox/CheckboxGroup.test.tsx +37 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +41 -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 +10 -41
- package/src/components/RadioButton/RadioGroup.test.tsx +65 -0
- package/src/components/RadioButton/RadioGroup.tsx +31 -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
- package/src/hooks/useTracking.test.tsx +64 -0
- package/src/hooks/useTracking.ts +19 -0
- package/src/utils/context.ts +3 -0
- package/tsconfig.json +1 -1
- package/dist/common/abstract-notification-banner.jsx +0 -63
- package/dist/common/conditional-wrapper.jsx +0 -8
- package/dist/common/file-icon.jsx +0 -51
- package/dist/common/hint-text.jsx +0 -9
- package/dist/common/icon.jsx +0 -57
- package/dist/common/screen-reader-text.jsx +0 -9
- package/dist/common/wrapper-tag.jsx +0 -11
- package/dist/components/accordion/accordion.jsx +0 -102
- package/dist/components/back-to-top/back-to-top.jsx +0 -27
- package/dist/components/breadcrumbs/breadcrumbs.jsx +0 -28
- package/dist/components/button/button.jsx +0 -30
- package/dist/components/checkbox/checkbox.jsx +0 -62
- package/dist/components/confirmation-message/confirmation-message.jsx +0 -24
- package/dist/components/contents-nav/contents-nav.jsx +0 -33
- package/dist/components/cookie-banner/cookie-banner.jsx +0 -21
- package/dist/components/date-picker/date-picker.jsx +0 -54
- package/dist/components/details/details.jsx +0 -17
- package/dist/components/error-message/error-message.jsx +0 -12
- package/dist/components/error-summary/error-summary.jsx +0 -27
- package/dist/components/file-download/file-download.jsx +0 -50
- package/dist/components/hide-this-page/hide-this-page.jsx +0 -71
- package/dist/components/inset-text/inset-text.jsx +0 -14
- package/dist/components/notification-banner/notification-banner.jsx +0 -26
- package/dist/components/notification-panel/notification-panel.jsx +0 -21
- package/dist/components/page-header/page-header.jsx +0 -15
- package/dist/components/page-metadata/page-metadata.jsx +0 -26
- package/dist/components/pagination/pagination.jsx +0 -97
- package/dist/components/phase-banner/phase-banner.jsx +0 -23
- package/dist/components/question/question.jsx +0 -22
- package/dist/components/radio-button/radio-button.jsx +0 -43
- package/dist/components/select/select.jsx +0 -52
- package/dist/components/sequential-navigation/sequential-navigation.jsx +0 -31
- package/dist/components/side-navigation/side-navigation.jsx +0 -52
- package/dist/components/site-header/site-header.jsx +0 -68
- package/dist/components/site-navigation/site-navigation.jsx +0 -22
- package/dist/components/site-search/site-search.jsx +0 -55
- package/dist/components/skip-links/skip-links.jsx +0 -21
- package/dist/components/summary-card/summary-card.jsx +0 -67
- package/dist/components/summary-list/summary-list.jsx +0 -75
- package/dist/components/table/table.jsx +0 -24
- package/dist/components/tabs/tabs.jsx +0 -99
- package/dist/components/tag/tag.jsx +0 -13
- package/dist/components/task-list/task-list.jsx +0 -95
- package/dist/components/text-input/text-input.jsx +0 -58
- package/dist/components/textarea/textarea.jsx +0 -54
- package/dist/components/warning-text/warning-text.jsx +0 -16
- package/dist/icons/ArrowUpward.jsx +0 -41
- package/dist/icons/CalendarToday.jsx +0 -41
- package/dist/icons/Cancel.jsx +0 -40
- package/dist/icons/CheckCircle.jsx +0 -41
- package/dist/icons/ChevronLeft.jsx +0 -41
- package/dist/icons/ChevronRight.jsx +0 -41
- package/dist/icons/Close.jsx +0 -41
- package/dist/icons/Description.jsx +0 -41
- package/dist/icons/DoubleChevronLeft.jsx +0 -40
- package/dist/icons/DoubleChevronRight.jsx +0 -40
- package/dist/icons/Error.jsx +0 -41
- package/dist/icons/ExpandLess.jsx +0 -41
- package/dist/icons/ExpandMore.jsx +0 -41
- package/dist/icons/List.jsx +0 -44
- package/dist/icons/Menu.jsx +0 -41
- package/dist/icons/PriorityHigh.jsx +0 -42
- package/dist/icons/Search.jsx +0 -41
- package/dist/icons/index.js +0 -40
|
@@ -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');
|
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const Item = ({
|
|
2
|
+
children,
|
|
3
|
+
current = false,
|
|
3
4
|
href,
|
|
4
|
-
|
|
5
|
-
}) => {
|
|
5
|
+
linkComponent
|
|
6
|
+
}: SGDS.Component.SiteNavigation.Item) => {
|
|
7
|
+
const classNames = ['ds_site-navigation__link'];
|
|
8
|
+
let ariaCurrent: React.AriaAttributes["aria-current"];
|
|
9
|
+
|
|
10
|
+
if (current) {
|
|
11
|
+
classNames.push('ds_current');
|
|
12
|
+
ariaCurrent = 'page';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function processChildren(children: React.ReactNode) {
|
|
16
|
+
if (linkComponent) {
|
|
17
|
+
return linkComponent({ className: classNames.join(' '), href, children });
|
|
18
|
+
} else if (href) {
|
|
19
|
+
return <a href={href} aria-current={ariaCurrent ? ariaCurrent : undefined} className={classNames.join(' ')}>{children}</a>;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
return (
|
|
7
24
|
<li
|
|
8
25
|
className="ds_site-navigation__item"
|
|
9
26
|
>
|
|
10
|
-
|
|
11
|
-
href={href}
|
|
12
|
-
className={[
|
|
13
|
-
'ds_site-navigation__link',
|
|
14
|
-
current ? 'ds_current' : undefined
|
|
15
|
-
].join(' ')}>{title}</a>
|
|
27
|
+
{processChildren(children)}
|
|
16
28
|
</li>
|
|
17
29
|
);
|
|
18
30
|
};
|
|
19
31
|
|
|
20
|
-
const SiteNavigation
|
|
32
|
+
const SiteNavigation = ({
|
|
33
|
+
children,
|
|
21
34
|
className,
|
|
22
|
-
items,
|
|
23
35
|
...props
|
|
24
|
-
}) => {
|
|
36
|
+
}: SGDS.Component.SiteNavigation) => {
|
|
25
37
|
return (
|
|
26
38
|
<nav
|
|
27
39
|
className={[
|
|
@@ -31,14 +43,14 @@ const SiteNavigation: React.FC<SGDS.Component.SiteNavigation> = ({
|
|
|
31
43
|
{...props}
|
|
32
44
|
>
|
|
33
45
|
<ul className="ds_site-navigation__list">
|
|
34
|
-
{
|
|
35
|
-
<SiteNavLink current={item.current} href={item.href} title={item.title} key={`link-${index}`} />
|
|
36
|
-
))}
|
|
46
|
+
{children}
|
|
37
47
|
</ul>
|
|
38
48
|
</nav>
|
|
39
49
|
);
|
|
40
50
|
};
|
|
41
51
|
|
|
42
52
|
SiteNavigation.displayName = 'SiteNavigation';
|
|
53
|
+
SiteNavigation.Item = Item;
|
|
54
|
+
Item.displayName = 'SiteNavigation.Item';
|
|
43
55
|
|
|
44
56
|
export default SiteNavigation;
|
|
@@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
|
|
|
3
3
|
import DSAutocomplete from '@scottish-government/design-system/src/components/autocomplete/autocomplete';
|
|
4
4
|
import Button from '../Button/Button';
|
|
5
5
|
|
|
6
|
-
const SiteSearch
|
|
6
|
+
const SiteSearch = function ({
|
|
7
7
|
action = '/search',
|
|
8
8
|
autocompleteEndpoint,
|
|
9
9
|
autocompleteSuggestionMappingFunction,
|
|
@@ -14,7 +14,7 @@ const SiteSearch: React.FC<SGDS.Component.SiteSearch> = function ({
|
|
|
14
14
|
name = 'q',
|
|
15
15
|
placeholder = 'Search',
|
|
16
16
|
...props
|
|
17
|
-
}) {
|
|
17
|
+
}: SGDS.Component.SiteSearch) {
|
|
18
18
|
const ref = useRef(null);
|
|
19
19
|
const hasAutocomplete = !!autocompleteEndpoint;
|
|
20
20
|
let autocompleteId = hasAutocomplete ? id + '-autocomplete' : '';
|
|
@@ -54,23 +54,35 @@ test('custom link target', () => {
|
|
|
54
54
|
expect(skipLinksLink).toHaveAttribute('href', `#${CUSTOM_ID}`)
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
test('
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
];
|
|
57
|
+
test('explicit links', () => {
|
|
58
|
+
const LINK_ID = 'foo';
|
|
59
|
+
const LINK_TEXT = 'bar';
|
|
61
60
|
|
|
62
61
|
render(
|
|
63
|
-
<SkipLinks
|
|
62
|
+
<SkipLinks>
|
|
63
|
+
<SkipLinks.Link fragmentId={LINK_ID}>{LINK_TEXT}</SkipLinks.Link>
|
|
64
|
+
</SkipLinks>
|
|
64
65
|
);
|
|
65
66
|
|
|
66
67
|
const skipLinksList = screen.getByRole('list');
|
|
67
68
|
const skipLinksListItems = within(skipLinksList).getAllByRole('listitem');
|
|
68
|
-
const skipLinksSecondLink = within(skipLinksList).
|
|
69
|
+
const skipLinksSecondLink = within(skipLinksList).getByRole('link');
|
|
69
70
|
|
|
70
|
-
expect(skipLinksListItems.length).toEqual(
|
|
71
|
-
expect(skipLinksSecondLink).toHaveAttribute('href', `#${
|
|
72
|
-
expect(skipLinksSecondLink.textContent).toEqual(
|
|
73
|
-
})
|
|
71
|
+
expect(skipLinksListItems.length).toEqual(1);
|
|
72
|
+
expect(skipLinksSecondLink).toHaveAttribute('href', `#${LINK_ID}`);
|
|
73
|
+
expect(skipLinksSecondLink.textContent).toEqual(LINK_TEXT);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('static skip links', () => {
|
|
77
|
+
render(
|
|
78
|
+
<SkipLinks isStatic />
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const skipLinksList = screen.getByRole('list');
|
|
82
|
+
const skipLinksContainer = skipLinksList.parentElement;
|
|
83
|
+
|
|
84
|
+
expect(skipLinksContainer).toHaveClass('ds_skip-links--static');
|
|
85
|
+
});
|
|
74
86
|
|
|
75
87
|
test('passing additional props', () => {
|
|
76
88
|
render(
|
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}) => {
|
|
1
|
+
const Link = ({
|
|
2
|
+
children,
|
|
3
|
+
fragmentId
|
|
4
|
+
}: SGDS.Component.SkipLinks.Link) => {
|
|
5
5
|
return (
|
|
6
6
|
<li
|
|
7
7
|
className="ds_skip-links__item"
|
|
8
8
|
>
|
|
9
|
-
<a href={`#${
|
|
9
|
+
<a href={`#${fragmentId}`} className="ds_skip-links__link">{ children }</a>
|
|
10
10
|
</li>
|
|
11
11
|
);
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const SkipLinks
|
|
15
|
-
|
|
14
|
+
const SkipLinks = ({
|
|
15
|
+
children,
|
|
16
16
|
mainContentId = 'main-content',
|
|
17
17
|
mainLinkText = 'Skip to main content',
|
|
18
|
+
isStatic,
|
|
18
19
|
...props
|
|
19
|
-
}) => {
|
|
20
|
+
}: SGDS.Component.SkipLinks) => {
|
|
20
21
|
return (
|
|
21
22
|
<div
|
|
22
|
-
className=
|
|
23
|
+
className={[
|
|
24
|
+
'ds_skip-links',
|
|
25
|
+
isStatic && 'ds_skip-links--static',
|
|
26
|
+
].join(' ')}
|
|
23
27
|
{...props}
|
|
24
28
|
>
|
|
25
29
|
<ul className="ds_skip-links__list">
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
{items && items.map((item, index: number) => (
|
|
29
|
-
<SkipLink title={item.title} targetId={item.targetId} key={`skiplink-${index}`}/>
|
|
30
|
-
))}
|
|
30
|
+
{children ? children : <Link fragmentId={mainContentId}>{mainLinkText}</Link>}
|
|
31
31
|
</ul>
|
|
32
32
|
</div>
|
|
33
33
|
);
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
SkipLinks.displayName = 'SkipLinks';
|
|
37
|
-
|
|
37
|
+
Link.displayName = 'SkipLink';
|
|
38
|
+
SkipLinks.Link = Link;
|
|
38
39
|
|
|
39
40
|
export default SkipLinks;
|
|
@@ -30,29 +30,21 @@ const ITEMS = [
|
|
|
30
30
|
const TITLE_TEXT = 'Joe Bloggs';
|
|
31
31
|
|
|
32
32
|
test('summary card renders correctly', () => {
|
|
33
|
-
const DESCRIBEDBY_ID = 'summary-card-joe-bloggs';
|
|
34
|
-
|
|
35
33
|
render(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<SummaryList data-testid="bar" items={ITEMS}/>
|
|
45
|
-
</>
|
|
34
|
+
<SummaryCard
|
|
35
|
+
data-testid="foo"
|
|
36
|
+
title={TITLE_TEXT}
|
|
37
|
+
>
|
|
38
|
+
<SummaryCard.Action href={ACTIONS[0].href}>{ACTIONS[0].title}</SummaryCard.Action>
|
|
39
|
+
<SummaryList data-testid="bar" />
|
|
40
|
+
</SummaryCard>
|
|
46
41
|
);
|
|
47
42
|
|
|
48
43
|
const summaryCard = screen.getByTestId('foo');
|
|
49
44
|
const title = within(summaryCard).getByRole('heading');
|
|
50
45
|
const header = title.parentElement as HTMLElement;
|
|
51
46
|
const content = header?.nextElementSibling as HTMLElement;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const thisList = within(content).getByRole('list');
|
|
55
|
-
const comparisonList = screen.getByTestId('bar');
|
|
47
|
+
const action = within(header).getByRole('link');
|
|
56
48
|
|
|
57
49
|
expect(summaryCard).toHaveClass('ds_summary-card');
|
|
58
50
|
|
|
@@ -60,34 +52,20 @@ test('summary card renders correctly', () => {
|
|
|
60
52
|
expect(header?.tagName).toEqual('DIV');
|
|
61
53
|
|
|
62
54
|
expect(title).toHaveClass('ds_summary-card__header-title');
|
|
63
|
-
expect(title).toHaveAttribute('id', DESCRIBEDBY_ID);
|
|
64
55
|
expect(title?.tagName).toEqual('H3');
|
|
65
56
|
expect(title.textContent).toEqual(TITLE_TEXT);
|
|
66
57
|
|
|
67
|
-
expect(
|
|
68
|
-
expect(actionsList.children.length).toEqual(ACTIONS.length);
|
|
69
|
-
expect(actionsList.children[0]).toHaveClass('ds_summary-card__actions-list-item');
|
|
70
|
-
expect(actionsList.children[0].tagName).toEqual('LI');
|
|
71
|
-
expect(actionsList.children[0].innerHTML).toEqual('<a aria-describedby="summary-card-joe-bloggs" class="ds_link" href="#foo">Change</a>');
|
|
72
|
-
expect(actionsList.children[1].innerHTML).toEqual('<button aria-describedby="summary-card-joe-bloggs" class="ds_link" type="button">Delete</button>');
|
|
73
|
-
|
|
74
|
-
fireEvent.click(actionsList.children[1].children[0]);
|
|
75
|
-
|
|
76
|
-
expect(ONCLICK_FUNCTION).toHaveBeenCalled();
|
|
58
|
+
expect(action).toHaveAttribute('aria-describedby', title.id);
|
|
77
59
|
|
|
78
60
|
expect(content).toHaveClass('ds_summary-card__content');
|
|
79
61
|
expect(content?.tagName).toEqual('DIV');
|
|
80
|
-
|
|
81
|
-
expect(thisList?.innerHTML).toEqual(comparisonList?.innerHTML);
|
|
82
62
|
});
|
|
83
63
|
|
|
84
64
|
test('custom heading level', () => {
|
|
85
65
|
render(
|
|
86
66
|
<SummaryCard
|
|
87
67
|
data-testid="foo"
|
|
88
|
-
actions={ACTIONS}
|
|
89
68
|
headingLevel="h2"
|
|
90
|
-
items={ITEMS}
|
|
91
69
|
title={TITLE_TEXT}
|
|
92
70
|
/>
|
|
93
71
|
);
|
|
@@ -98,12 +76,32 @@ test('custom heading level', () => {
|
|
|
98
76
|
expect(title?.tagName).toEqual('H2');
|
|
99
77
|
});
|
|
100
78
|
|
|
79
|
+
test('summary card with multiple actions renders actions as a list', () => {
|
|
80
|
+
render(
|
|
81
|
+
<SummaryCard
|
|
82
|
+
data-testid="foo"
|
|
83
|
+
title={TITLE_TEXT}
|
|
84
|
+
>
|
|
85
|
+
<SummaryCard.Action href={ACTIONS[0].href}>{ACTIONS[0].title}</SummaryCard.Action>
|
|
86
|
+
<SummaryCard.Action onclick={ACTIONS[1].onclick}>{ACTIONS[1].title}</SummaryCard.Action>
|
|
87
|
+
<SummaryList data-testid="bar" />
|
|
88
|
+
</SummaryCard>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const summaryCard = screen.getByTestId('foo');
|
|
92
|
+
const title = within(summaryCard).getByRole('heading');
|
|
93
|
+
const header = title.parentElement as HTMLElement;
|
|
94
|
+
const actionsList = within(header).getByRole('list')
|
|
95
|
+
|
|
96
|
+
expect(actionsList?.tagName).toEqual('UL');
|
|
97
|
+
expect(actionsList.children[0]).toHaveClass('ds_summary-card__actions-list-item');
|
|
98
|
+
expect(actionsList.children[0].tagName).toEqual('LI');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
101
|
test('passing additional props', () => {
|
|
102
102
|
render(
|
|
103
103
|
<SummaryCard
|
|
104
104
|
data-testid="foo"
|
|
105
|
-
actions={ACTIONS}
|
|
106
|
-
items={ITEMS}
|
|
107
105
|
title={TITLE_TEXT}
|
|
108
106
|
data-test="foo"
|
|
109
107
|
/>
|
|
@@ -117,8 +115,6 @@ test('passing additional CSS classes', () => {
|
|
|
117
115
|
render(
|
|
118
116
|
<SummaryCard
|
|
119
117
|
data-testid="foo"
|
|
120
|
-
actions={ACTIONS}
|
|
121
|
-
items={ITEMS}
|
|
122
118
|
title={TITLE_TEXT}
|
|
123
119
|
className="foo"
|
|
124
120
|
/>
|