@scottish-government/designsystem-react 0.7.1 → 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/Checkbox/CheckboxGroup.jsx +63 -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 +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
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { Children
|
|
1
|
+
import { Children } from 'react';
|
|
2
2
|
import Icon from './Icon';
|
|
3
3
|
import ScreenReaderText from './ScreenReaderText';
|
|
4
4
|
|
|
5
|
-
const Buttons
|
|
5
|
+
const Buttons = ({
|
|
6
6
|
children
|
|
7
|
-
}) => {
|
|
7
|
+
}: SGDS.Common.AbstractNotificationBanner.Buttons) => {
|
|
8
8
|
return (<>{children}</>);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const AbstractNotificationBanner
|
|
12
|
-
& { Buttons: React.FC<SGDS.Common.AbstractNotificationBanner.Buttons> } = ({
|
|
11
|
+
const AbstractNotificationBanner = ({
|
|
13
12
|
children,
|
|
14
13
|
className,
|
|
15
14
|
close,
|
|
15
|
+
hasColourIcon,
|
|
16
|
+
hasInverseIcon,
|
|
16
17
|
icon,
|
|
17
|
-
iconColour,
|
|
18
|
-
iconInverse,
|
|
19
18
|
title = 'Information',
|
|
20
19
|
...props
|
|
21
|
-
}) => {
|
|
20
|
+
}: SGDS.Common.AbstractNotificationBanner) => {
|
|
22
21
|
let content: any[] = [];
|
|
23
22
|
let buttons;
|
|
24
23
|
|
|
25
24
|
Children.forEach(children, (child) => {
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const thisChild = child as React.ReactElement<HTMLAnchorElement>;
|
|
26
|
+
if (thisChild && thisChild.type === Buttons) {
|
|
27
|
+
buttons = thisChild;
|
|
28
28
|
} else {
|
|
29
|
-
content.push(
|
|
29
|
+
content.push(thisChild);
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
@@ -52,8 +52,8 @@ const AbstractNotificationBanner: React.FC<SGDS.Common.AbstractNotificationBanne
|
|
|
52
52
|
<span
|
|
53
53
|
className={[
|
|
54
54
|
'ds_notification__icon',
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
hasInverseIcon && 'ds_notification__icon--inverse',
|
|
56
|
+
hasColourIcon && 'ds_notification__icon--colour'
|
|
57
57
|
].join(' ')} aria-hidden="true">
|
|
58
58
|
<Icon icon={icon} />
|
|
59
59
|
</span>
|
|
@@ -83,5 +83,6 @@ const AbstractNotificationBanner: React.FC<SGDS.Common.AbstractNotificationBanne
|
|
|
83
83
|
|
|
84
84
|
AbstractNotificationBanner.displayName = 'AbstractNotificationBanner';
|
|
85
85
|
AbstractNotificationBanner.Buttons = Buttons;
|
|
86
|
+
Buttons.displayName = 'Buttons';
|
|
86
87
|
|
|
87
88
|
export default AbstractNotificationBanner;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import ActionLink from './ActionLink';
|
|
4
|
+
|
|
5
|
+
const ONCLICK_FUNCTION = vi.fn();
|
|
6
|
+
const ACTION_HREF = "#foo"
|
|
7
|
+
const ACTION_ONCLICK = ONCLICK_FUNCTION;
|
|
8
|
+
const ACTION_TEXT = 'Name';
|
|
9
|
+
const DESCRIBEDBY_ID = 'q1-name';
|
|
10
|
+
|
|
11
|
+
test('button action', () => {
|
|
12
|
+
render(
|
|
13
|
+
<ActionLink
|
|
14
|
+
describedby={DESCRIBEDBY_ID}
|
|
15
|
+
href={undefined}
|
|
16
|
+
onclick={ACTION_ONCLICK}
|
|
17
|
+
>
|
|
18
|
+
{ACTION_TEXT}
|
|
19
|
+
</ActionLink>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const action = screen.getByRole('button');
|
|
23
|
+
|
|
24
|
+
expect(action).toHaveClass('ds_link');
|
|
25
|
+
expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
|
|
26
|
+
expect(action).toHaveAttribute('type', 'button');
|
|
27
|
+
expect(action).not.toHaveAttribute('href');
|
|
28
|
+
expect(action.tagName).toEqual('BUTTON');
|
|
29
|
+
expect(action.textContent).toEqual(ACTION_TEXT);
|
|
30
|
+
|
|
31
|
+
fireEvent.click(action);
|
|
32
|
+
|
|
33
|
+
expect(ONCLICK_FUNCTION).toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('link action', () => {
|
|
37
|
+
render(
|
|
38
|
+
<ActionLink
|
|
39
|
+
describedby={DESCRIBEDBY_ID}
|
|
40
|
+
href={ACTION_HREF}
|
|
41
|
+
onclick={ACTION_ONCLICK}
|
|
42
|
+
>
|
|
43
|
+
{ACTION_TEXT}
|
|
44
|
+
</ActionLink >
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const action = screen.getByRole('link');
|
|
48
|
+
|
|
49
|
+
expect(action).toHaveClass('ds_link');
|
|
50
|
+
expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
|
|
51
|
+
expect(action).toHaveAttribute('href', ACTION_HREF);
|
|
52
|
+
expect(action).not.toHaveAttribute('type');
|
|
53
|
+
expect(action.tagName).toEqual('A');
|
|
54
|
+
expect(action.textContent).toEqual(ACTION_TEXT);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('action with custom element', () => {
|
|
58
|
+
render(
|
|
59
|
+
<ActionLink
|
|
60
|
+
describedby={DESCRIBEDBY_ID}
|
|
61
|
+
href={ACTION_HREF}
|
|
62
|
+
onclick={ACTION_ONCLICK}
|
|
63
|
+
linkComponent={
|
|
64
|
+
({ className, ...props }) => (
|
|
65
|
+
<strong role="link" className={className} {...props}/>
|
|
66
|
+
)}>
|
|
67
|
+
{ACTION_TEXT}
|
|
68
|
+
</ActionLink>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const action = screen.getByRole('link');
|
|
72
|
+
|
|
73
|
+
expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
|
|
74
|
+
expect(action?.tagName).toEqual('STRONG');
|
|
75
|
+
expect(action?.textContent).toEqual(ACTION_TEXT);
|
|
76
|
+
|
|
77
|
+
fireEvent.click(action);
|
|
78
|
+
|
|
79
|
+
expect(ONCLICK_FUNCTION).toHaveBeenCalled();
|
|
80
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const ActionLink = ({
|
|
2
|
+
children,
|
|
3
|
+
describedby,
|
|
4
|
+
href,
|
|
5
|
+
linkComponent,
|
|
6
|
+
onclick
|
|
7
|
+
}: SGDS.Common.ActionLink) => {
|
|
8
|
+
const CLASSNAME = 'ds_link';
|
|
9
|
+
|
|
10
|
+
function processChildren(children: React.ReactNode) {
|
|
11
|
+
if (linkComponent) {
|
|
12
|
+
return linkComponent({ className: CLASSNAME, href, children, onClick: onclick, 'aria-describedby': describedby });
|
|
13
|
+
} else if (href) {
|
|
14
|
+
return <a aria-describedby={describedby} onClick={onclick} href={href} className={CLASSNAME}>{children}</a>;
|
|
15
|
+
} else {
|
|
16
|
+
return <button type="button" aria-describedby={describedby} onClick={onclick} className={CLASSNAME}>{children}</button>;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
processChildren(children)
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
ActionLink.displayName = 'ActionLink';
|
|
26
|
+
|
|
27
|
+
export default ActionLink;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wraps all children in a specified HTML tag if a condition is met.
|
|
3
3
|
*/
|
|
4
|
-
const ConditionalWrapper
|
|
4
|
+
const ConditionalWrapper = ({ condition, wrapper, children }:SGDS.Common.ConditionalWrapper) =>
|
|
5
5
|
condition ? wrapper(children) : children;
|
|
6
6
|
|
|
7
7
|
ConditionalWrapper.displayName = 'ConditionalWrapper';
|
package/src/common/FileIcon.tsx
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import * as FileIcons from '../images/documents';
|
|
3
3
|
|
|
4
|
-
const FileIcon
|
|
4
|
+
const FileIcon = ({
|
|
5
5
|
ariaLabel = '',
|
|
6
6
|
className,
|
|
7
7
|
icon
|
|
8
|
-
}) => {
|
|
9
|
-
const
|
|
10
|
-
{
|
|
11
|
-
className: className,
|
|
12
|
-
'aria-label': ariaLabel
|
|
13
|
-
}
|
|
14
|
-
);
|
|
8
|
+
}: SGDS.Common.FileIcon) => {
|
|
9
|
+
const FileIconComponent = FileIcons[icon];
|
|
15
10
|
|
|
16
11
|
return (
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
12
|
+
<FileIconComponent
|
|
13
|
+
aria-label={ariaLabel}
|
|
14
|
+
className={className}
|
|
15
|
+
/>
|
|
20
16
|
);
|
|
21
17
|
};
|
|
22
18
|
|
package/src/common/HintText.tsx
CHANGED
package/src/common/Icon.tsx
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import * as Icons from '../images/icons';
|
|
3
3
|
|
|
4
|
-
const Icon
|
|
4
|
+
const Icon = ({
|
|
5
5
|
ariaLabel,
|
|
6
6
|
className,
|
|
7
7
|
fill,
|
|
8
8
|
icon,
|
|
9
9
|
iconSize
|
|
10
|
-
}) => {
|
|
11
|
-
const
|
|
12
|
-
{
|
|
13
|
-
'aria-hidden': ariaLabel ? undefined : true,
|
|
14
|
-
'aria-label': ariaLabel,
|
|
15
|
-
className: [
|
|
16
|
-
'ds_icon',
|
|
17
|
-
className,
|
|
18
|
-
fill && 'ds_icon--fill',
|
|
19
|
-
iconSize && `ds_icon--${iconSize}`
|
|
20
|
-
].join(' ')
|
|
21
|
-
}
|
|
22
|
-
);
|
|
10
|
+
}: SGDS.Common.Icon) => {
|
|
11
|
+
const IconComponent = Icons[icon];
|
|
23
12
|
|
|
24
13
|
return (
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
14
|
+
<IconComponent
|
|
15
|
+
aria-hidden={ariaLabel ? undefined : true}
|
|
16
|
+
aria-label={ariaLabel}
|
|
17
|
+
className={[
|
|
18
|
+
'ds_icon',
|
|
19
|
+
className,
|
|
20
|
+
fill && 'ds_icon--fill',
|
|
21
|
+
iconSize && `ds_icon--${iconSize}`
|
|
22
|
+
].join(' ')}
|
|
23
|
+
/>
|
|
28
24
|
);
|
|
29
25
|
};
|
|
30
26
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wraps all children in a specified HTML tag.
|
|
3
3
|
*/
|
|
4
|
-
const WrapperTag
|
|
4
|
+
const WrapperTag = ({
|
|
5
5
|
children,
|
|
6
6
|
tagName = 'div',
|
|
7
7
|
...props
|
|
8
|
-
}) => {
|
|
8
|
+
}: SGDS.Common.WrapperTag) => {
|
|
9
9
|
const TagName = tagName;
|
|
10
10
|
return <TagName {...props}>{children}</TagName>;
|
|
11
11
|
};
|
|
@@ -32,7 +32,6 @@ test('accordion renders correctly', () => {
|
|
|
32
32
|
|
|
33
33
|
const accordion = screen.getByTestId(ACCORDION_ID);
|
|
34
34
|
const openAllButton = document.querySelector('.ds_accordion__open-all');
|
|
35
|
-
const accordionItems = document.querySelectorAll('.ds_accordion-item');
|
|
36
35
|
const firstAccordionTitle = document.querySelector('.ds_accordion-item__title');
|
|
37
36
|
|
|
38
37
|
expect(accordion).toHaveClass('ds_accordion');
|
|
@@ -43,8 +42,6 @@ test('accordion renders correctly', () => {
|
|
|
43
42
|
expect(openAllButton?.textContent).toEqual('Open all sections');
|
|
44
43
|
expect(openAllButton?.innerHTML).toEqual('Open all <span class="visually-hidden">sections</span>');
|
|
45
44
|
|
|
46
|
-
expect(accordionItems.length).toEqual(3);
|
|
47
|
-
|
|
48
45
|
expect(firstAccordionTitle?.tagName).toEqual(DEFAULT_HEADING_LEVEL.toUpperCase());
|
|
49
46
|
});
|
|
50
47
|
|
|
@@ -102,6 +99,22 @@ test('accordion with custom heading level', () => {
|
|
|
102
99
|
expect(firstAccordionTitle?.tagName).toEqual(HEADING_LEVEL.toUpperCase());
|
|
103
100
|
});
|
|
104
101
|
|
|
102
|
+
test('accordion item with nonsense heading level falls back to h3', () => {
|
|
103
|
+
render(
|
|
104
|
+
<Accordion id={ACCORDION_ID} data-testid={ACCORDION_ID} headingLevel="bananas">
|
|
105
|
+
<Accordion.Item id="accordion-1" title="Healthcare for veterans">
|
|
106
|
+
<p>Veterans are entitled to the same healthcare as any citizen. And there
|
|
107
|
+
are health care options and support available specifically for veterans.</p>
|
|
108
|
+
<p>If you have a health condition that’s related to your service, you’re
|
|
109
|
+
entitled to priority treatment based on clinical need.</p>
|
|
110
|
+
</Accordion.Item>
|
|
111
|
+
</Accordion>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const firstAccordionTitle = document.querySelector('.ds_accordion-item__title');
|
|
115
|
+
expect(firstAccordionTitle?.tagName).toEqual('H3');
|
|
116
|
+
});
|
|
117
|
+
|
|
105
118
|
test('passing additional props to accordion', () => {
|
|
106
119
|
render(
|
|
107
120
|
<Accordion id={ACCORDION_ID} data-testid={ACCORDION_ID} data-test="foo">
|
|
@@ -216,7 +229,7 @@ test('passing additional props to accordion item', () => {
|
|
|
216
229
|
);
|
|
217
230
|
|
|
218
231
|
const accordionItem = screen.getByTestId(ACCORDION_ITEM_ID);
|
|
219
|
-
expect(accordionItem
|
|
232
|
+
expect(accordionItem.dataset.test).toEqual('foo');
|
|
220
233
|
});
|
|
221
234
|
|
|
222
235
|
test('passing additional CSS classes', () => {
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { createContext, useContext, useEffect, useRef } from 'react';
|
|
2
2
|
import WrapperTag from '../../common/WrapperTag';
|
|
3
3
|
// @ts-ignore
|
|
4
4
|
import DSAccordion from '@scottish-government/design-system/src/components/accordion/accordion';
|
|
5
5
|
|
|
6
6
|
let accordionItemCounter = 0;
|
|
7
|
+
const AccordionHeadingLevelContext = createContext('h3');
|
|
7
8
|
|
|
8
|
-
const AccordionItem
|
|
9
|
+
const AccordionItem = ({
|
|
9
10
|
children,
|
|
10
11
|
className,
|
|
11
|
-
headingLevel = 'h3',
|
|
12
12
|
id: rawId,
|
|
13
13
|
open = false,
|
|
14
14
|
title,
|
|
15
15
|
...props
|
|
16
|
-
}) => {
|
|
16
|
+
}: SGDS.Component.Accordion.Item) => {
|
|
17
17
|
accordionItemCounter = accordionItemCounter + 1;
|
|
18
18
|
const processedId = rawId || `accordion-item-${accordionItemCounter}`;
|
|
19
|
+
const DEFAULT_HEADING_LEVEL = 'h3';
|
|
20
|
+
|
|
21
|
+
let headingLevel = useContext(AccordionHeadingLevelContext);
|
|
22
|
+
|
|
23
|
+
if (!['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(headingLevel)) {
|
|
24
|
+
headingLevel = DEFAULT_HEADING_LEVEL;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
return (
|
|
20
28
|
<div
|
|
21
29
|
className={[
|
|
@@ -43,7 +51,7 @@ const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
|
|
|
43
51
|
>
|
|
44
52
|
{title}
|
|
45
53
|
</WrapperTag>
|
|
46
|
-
<span className=
|
|
54
|
+
<span className="ds_accordion-item__indicator" />
|
|
47
55
|
<label
|
|
48
56
|
className="ds_accordion-item__label"
|
|
49
57
|
htmlFor={`${processedId}-control`}
|
|
@@ -58,14 +66,13 @@ const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
|
|
|
58
66
|
);
|
|
59
67
|
};
|
|
60
68
|
|
|
61
|
-
const Accordion
|
|
62
|
-
& { Item: React.FC<SGDS.Component.Accordion.Item> } = ({
|
|
69
|
+
const Accordion = ({
|
|
63
70
|
children,
|
|
64
71
|
className,
|
|
65
72
|
headingLevel = 'h3',
|
|
66
73
|
hideOpenAll,
|
|
67
74
|
...props
|
|
68
|
-
}) => {
|
|
75
|
+
}: SGDS.Component.Accordion) => {
|
|
69
76
|
const ref = useRef(null);
|
|
70
77
|
|
|
71
78
|
useEffect(() => {
|
|
@@ -78,10 +85,6 @@ const Accordion: React.FC<SGDS.Component.Accordion>
|
|
|
78
85
|
hideOpenAll = true;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
function processChild(child: any) {
|
|
82
|
-
return React.cloneElement(child, { headingLevel: headingLevel });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
88
|
return (
|
|
86
89
|
<div
|
|
87
90
|
className={[
|
|
@@ -106,13 +109,15 @@ const Accordion: React.FC<SGDS.Component.Accordion>
|
|
|
106
109
|
</button>
|
|
107
110
|
)}
|
|
108
111
|
|
|
109
|
-
{
|
|
112
|
+
<AccordionHeadingLevelContext value={headingLevel}>
|
|
113
|
+
{children}
|
|
114
|
+
</AccordionHeadingLevelContext>
|
|
110
115
|
</div>
|
|
111
116
|
);
|
|
112
117
|
};
|
|
113
118
|
|
|
114
119
|
Accordion.displayName = 'Accordion';
|
|
115
|
-
AccordionItem.displayName = '
|
|
120
|
+
AccordionItem.displayName = 'Accordion.Item';
|
|
116
121
|
Accordion.Item = AccordionItem;
|
|
117
122
|
|
|
118
123
|
export default Accordion;
|
|
@@ -2,12 +2,12 @@ import React, { Children, useEffect, useRef } from 'react';
|
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
import DSAspectBox from '@scottish-government/design-system/src/components/aspect-box/aspect-box-fallback';
|
|
4
4
|
|
|
5
|
-
const AspectBox
|
|
5
|
+
const AspectBox = ({
|
|
6
6
|
children,
|
|
7
7
|
className,
|
|
8
8
|
ratio,
|
|
9
9
|
...props
|
|
10
|
-
}) => {
|
|
10
|
+
}: SGDS.Component.AspectBox) => {
|
|
11
11
|
const ref = useRef(null);
|
|
12
12
|
|
|
13
13
|
useEffect(() => {
|
|
@@ -3,11 +3,11 @@ import Icon from '../../common/Icon';
|
|
|
3
3
|
// @ts-ignore
|
|
4
4
|
import DSBackToTop from '@scottish-government/design-system/src/components/back-to-top/back-to-top';
|
|
5
5
|
|
|
6
|
-
const BackToTop
|
|
6
|
+
const BackToTop = ({
|
|
7
7
|
className,
|
|
8
8
|
href = '#page-top',
|
|
9
9
|
...props
|
|
10
|
-
}) => {
|
|
10
|
+
}: SGDS.Component.BackToTop) => {
|
|
11
11
|
const ref = useRef(null);
|
|
12
12
|
|
|
13
13
|
useEffect(() => {
|
|
@@ -2,20 +2,20 @@ import { test, expect } from 'vitest';
|
|
|
2
2
|
import { render, screen, within } from '@testing-library/react';
|
|
3
3
|
import Breadcrumbs from './Breadcrumbs';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
{ href: 'category', title: 'Category' },
|
|
8
|
-
{ title: 'Page' }
|
|
9
|
-
];
|
|
5
|
+
const LINK_HREF = '#home';
|
|
6
|
+
const LINK_TEXT = 'Home';
|
|
10
7
|
|
|
11
|
-
test('
|
|
8
|
+
test('breadcrumbs render correctly', () => {
|
|
12
9
|
render(
|
|
13
|
-
<Breadcrumbs
|
|
10
|
+
<Breadcrumbs>
|
|
11
|
+
<Breadcrumbs.Item href="home">Home</Breadcrumbs.Item>
|
|
12
|
+
<Breadcrumbs.Item href="category">Category</Breadcrumbs.Item>
|
|
13
|
+
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
|
14
|
+
</Breadcrumbs>
|
|
14
15
|
);
|
|
15
16
|
|
|
16
17
|
const nav = screen.getByRole('navigation');
|
|
17
18
|
const list = within(nav).getByRole('list');
|
|
18
|
-
const listItems = within(list).getAllByRole('listitem');
|
|
19
19
|
|
|
20
20
|
// check nav
|
|
21
21
|
expect(nav).toHaveAttribute('aria-label', 'Breadcrumb');
|
|
@@ -23,67 +23,98 @@ test('renders correctly', () => {
|
|
|
23
23
|
// check list
|
|
24
24
|
expect(list.tagName).toEqual('OL');
|
|
25
25
|
expect(list).toHaveClass('ds_breadcrumbs');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('passing additional props to breadcrumbs', () => {
|
|
29
|
+
render(
|
|
30
|
+
<Breadcrumbs data-test="foo"/>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const nav = screen.getByRole('navigation');
|
|
34
|
+
expect(nav.dataset.test).toEqual('foo');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('passing additional CSS classes to breadcrumbs', () => {
|
|
38
|
+
render(
|
|
39
|
+
<Breadcrumbs className="foo"/>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const nav = screen.getByRole('navigation');
|
|
43
|
+
expect(nav).toHaveClass('foo');
|
|
44
|
+
});
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
test('breadcrumb item with link', () => {
|
|
47
|
+
render(
|
|
48
|
+
<Breadcrumbs.Item href={LINK_HREF}>{LINK_TEXT}</Breadcrumbs.Item>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const item = screen.getByRole('listitem');
|
|
52
|
+
const link = within(item).getByRole('link');
|
|
53
|
+
|
|
54
|
+
expect(item).toHaveClass('ds_breadcrumbs__item');
|
|
55
|
+
expect(item?.tagName).toEqual('LI');
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
expect(link).toHaveClass('ds_breadcrumbs__link');
|
|
58
|
+
expect(link).toHaveAttribute('href', LINK_HREF);
|
|
59
|
+
expect(link?.tagName).toEqual('A');
|
|
60
|
+
expect(link?.textContent).toEqual(LINK_TEXT);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('breadcrumb item without link', () => {
|
|
64
|
+
render(
|
|
65
|
+
<Breadcrumbs.Item>{LINK_TEXT}</Breadcrumbs.Item>
|
|
66
|
+
);
|
|
32
67
|
|
|
33
|
-
|
|
68
|
+
const item = screen.getByRole('listitem');
|
|
69
|
+
const link = within(item).queryByRole('link');
|
|
34
70
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} else {
|
|
39
|
-
expect(link).toBeNull();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
71
|
+
expect(item).toHaveClass('ds_breadcrumbs__item');
|
|
72
|
+
expect(item?.tagName).toEqual('LI');
|
|
73
|
+
expect(item?.textContent).toEqual(LINK_TEXT);
|
|
42
74
|
|
|
43
|
-
|
|
44
|
-
const categoryLink = within(list).getByRole('link', { name: 'Category' });
|
|
45
|
-
expect(categoryLink).toHaveAttribute('href', 'category');
|
|
75
|
+
expect(link).not.toBeInTheDocument();
|
|
46
76
|
});
|
|
47
77
|
|
|
48
|
-
test('
|
|
78
|
+
test('hidden breadcrumb item', () => {
|
|
49
79
|
render(
|
|
50
|
-
<Breadcrumbs
|
|
51
|
-
hideLastItem
|
|
52
|
-
items={ITEMS}
|
|
53
|
-
/>
|
|
80
|
+
<Breadcrumbs.Item data-testid="Breadcrumbs.Item" isHidden>{LINK_TEXT}</Breadcrumbs.Item>
|
|
54
81
|
);
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
83
|
+
const item = screen.getByRole('listitem');
|
|
84
|
+
expect(item).toHaveClass('visually-hidden');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('renders breadcrumb with custom element', () => {
|
|
88
|
+
render(
|
|
89
|
+
<Breadcrumbs.Item href="category" linkComponent={
|
|
90
|
+
({ className, ...props }) => (
|
|
91
|
+
<span role="link" className={className} {...props}/>
|
|
92
|
+
)}>
|
|
93
|
+
{LINK_TEXT}
|
|
94
|
+
</Breadcrumbs.Item>
|
|
95
|
+
);
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
expect(
|
|
97
|
+
const item = screen.getByRole('listitem');
|
|
98
|
+
const link = within(item).queryByRole('link');
|
|
99
|
+
|
|
100
|
+
expect(link?.tagName).toEqual('SPAN');
|
|
101
|
+
expect(link?.textContent).toEqual(LINK_TEXT);
|
|
65
102
|
});
|
|
66
103
|
|
|
67
|
-
test('passing additional props', () => {
|
|
104
|
+
test('passing additional props to breadcrumb item', () => {
|
|
68
105
|
render(
|
|
69
|
-
<Breadcrumbs
|
|
70
|
-
items={ITEMS}
|
|
71
|
-
data-test="foo"
|
|
72
|
-
/>
|
|
106
|
+
<Breadcrumbs.Item data-test="foo"/>
|
|
73
107
|
);
|
|
74
108
|
|
|
75
|
-
const nav = screen.getByRole('
|
|
109
|
+
const nav = screen.getByRole('listitem');
|
|
76
110
|
expect(nav.dataset.test).toEqual('foo');
|
|
77
111
|
});
|
|
78
112
|
|
|
79
|
-
test('passing additional CSS classes', () => {
|
|
113
|
+
test('passing additional CSS classes to breadcrumb item', () => {
|
|
80
114
|
render(
|
|
81
|
-
<Breadcrumbs
|
|
82
|
-
items={ITEMS}
|
|
83
|
-
className="foo"
|
|
84
|
-
/>
|
|
115
|
+
<Breadcrumbs.Item className="foo"/>
|
|
85
116
|
);
|
|
86
117
|
|
|
87
|
-
const nav = screen.getByRole('
|
|
118
|
+
const nav = screen.getByRole('listitem');
|
|
88
119
|
expect(nav).toHaveClass('foo');
|
|
89
120
|
});
|