@scottish-government/designsystem-react 0.10.2 → 0.12.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/components/Accordion.d.ts +3 -2
- package/@types/components/ButtonGroup.d.ts +5 -0
- package/@types/components/PageHeader.d.ts +2 -1
- package/@types/components/RadioButton.d.ts +2 -2
- package/@types/components/SearchFacets.d.ts +18 -0
- package/@types/components/SearchFilters.d.ts +14 -0
- package/@types/components/SearchResult.d.ts +30 -0
- package/@types/components/SearchSort.d.ts +9 -0
- package/@types/components/SideNavigation.d.ts +1 -1
- package/CHANGELOG.md +39 -5
- package/dist/common/AbstractNotificationBanner.d.ts +9 -0
- package/dist/common/ActionLink.d.ts +5 -0
- package/dist/common/ConditionalWrapper.d.ts +8 -0
- package/dist/common/FileIcon.d.ts +6 -0
- package/dist/common/HintText.d.ts +5 -0
- package/dist/common/Icon.d.ts +6 -0
- package/dist/common/ScreenReaderText.d.ts +5 -0
- package/dist/common/WrapperTag.d.ts +8 -0
- package/dist/components/Accordion/Accordion.d.ts +10 -0
- package/dist/components/Accordion/Accordion.jsx +8 -3
- package/dist/components/AspectBox/AspectBox.d.ts +6 -0
- package/dist/components/BackToTop/BackToTop.d.ts +5 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +9 -0
- package/dist/components/Button/Button.d.ts +5 -0
- package/dist/components/ButtonGroup/ButtonGroup.d.ts +5 -0
- package/dist/components/ButtonGroup/ButtonGroup.jsx +13 -0
- package/dist/components/CategoryItem/CategoryItem.d.ts +5 -0
- package/dist/components/CategoryList/CategoryList.d.ts +6 -0
- package/dist/components/Checkbox/Checkbox.d.ts +5 -0
- package/dist/components/Checkbox/CheckboxGroup.d.ts +6 -0
- package/dist/components/ConfirmationMessage/ConfirmationMessage.d.ts +5 -0
- package/dist/components/ContentsNav/ContentsNav.d.ts +10 -0
- package/dist/components/CookieBanner/CookieBanner.d.ts +9 -0
- package/dist/components/DatePicker/DatePicker.d.ts +5 -0
- package/dist/components/Details/Details.d.ts +5 -0
- package/dist/components/ErrorMessage/ErrorMessage.d.ts +5 -0
- package/dist/components/ErrorSummary/ErrorSummary.d.ts +9 -0
- package/dist/components/FileDownload/FileDownload.d.ts +5 -0
- package/dist/components/HideThisPage/HideThisPage.d.ts +6 -0
- package/dist/components/InsetText/InsetText.d.ts +5 -0
- package/dist/components/NotificationBanner/NotificationBanner.d.ts +9 -0
- package/dist/components/NotificationPanel/NotificationPanel.d.ts +5 -0
- package/dist/components/PageHeader/PageHeader.d.ts +5 -0
- package/dist/components/PageHeader/PageHeader.jsx +2 -2
- package/dist/components/PageMetadata/PageMetadata.d.ts +9 -0
- package/dist/components/Pagination/Pagination.d.ts +7 -0
- package/dist/components/PhaseBanner/PhaseBanner.d.ts +5 -0
- package/dist/components/Question/Question.d.ts +5 -0
- package/dist/components/RadioButton/RadioButton.d.ts +5 -0
- package/dist/components/RadioButton/RadioGroup.d.ts +6 -0
- package/dist/components/RadioButton/RadioGroup.jsx +1 -1
- package/dist/components/SearchFacets/SearchFacets.d.ts +14 -0
- package/dist/components/SearchFacets/SearchFacets.jsx +101 -0
- package/dist/components/SearchFilters/SearchFilters.d.ts +16 -0
- package/dist/components/SearchFilters/SearchFilters.jsx +63 -0
- package/dist/components/SearchResult/SearchResult.d.ts +28 -0
- package/dist/components/SearchResult/SearchResult.jsx +93 -0
- package/dist/components/SearchSort/SearchSort.d.ts +10 -0
- package/dist/components/SearchSort/SearchSort.jsx +28 -0
- package/dist/components/Select/Select.d.ts +5 -0
- package/dist/components/SequentialNavigation/SequentialNavigation.d.ts +13 -0
- package/dist/components/SequentialNavigation/SequentialNavigation.jsx +0 -1
- package/dist/components/SideNavigation/SideNavigation.d.ts +13 -0
- package/dist/components/SideNavigation/SideNavigation.jsx +2 -2
- package/dist/components/SiteFooter/SiteFooter.d.ts +22 -0
- package/dist/components/SiteHeader/SiteHeader.d.ts +22 -0
- package/dist/components/SiteHeader/SiteHeader.jsx +0 -1
- package/dist/components/SiteNavigation/SiteNavigation.d.ts +9 -0
- package/dist/components/SiteSearch/SiteSearch.d.ts +5 -0
- package/dist/components/SkipLinks/SkipLinks.d.ts +10 -0
- package/dist/components/SkipLinks/SkipLinks.jsx +42 -0
- package/dist/components/SummaryCard/SummaryCard.d.ts +10 -0
- package/dist/components/SummaryList/SummaryList.d.ts +18 -0
- package/dist/components/Table/Table.d.ts +5 -0
- package/dist/components/Tabs/Tabs.d.ts +10 -0
- package/dist/components/Tag/Tag.d.ts +5 -0
- package/dist/components/TaskList/TaskList.d.ts +13 -0
- package/dist/components/TextInput/TextInput.d.ts +5 -0
- package/dist/components/Textarea/Textarea.d.ts +5 -0
- package/dist/components/WarningText/WarningText.d.ts +5 -0
- package/dist/hooks/useTracking.d.ts +1 -0
- package/dist/images/documents/audio.d.ts +4 -0
- package/dist/images/documents/csv.d.ts +4 -0
- package/dist/images/documents/excel.d.ts +4 -0
- package/dist/images/documents/file.d.ts +4 -0
- package/dist/images/documents/generic.d.ts +4 -0
- package/dist/images/documents/geodata.d.ts +4 -0
- package/dist/images/documents/ical.d.ts +4 -0
- package/dist/images/documents/ico.d.ts +4 -0
- package/dist/images/documents/image.d.ts +4 -0
- package/dist/images/documents/index.d.ts +22 -0
- package/dist/images/documents/odf.d.ts +4 -0
- package/dist/images/documents/odg.d.ts +4 -0
- package/dist/images/documents/odp.d.ts +4 -0
- package/dist/images/documents/ods.d.ts +4 -0
- package/dist/images/documents/odt.d.ts +4 -0
- package/dist/images/documents/pdf.d.ts +4 -0
- package/dist/images/documents/ppt.d.ts +4 -0
- package/dist/images/documents/rtf.d.ts +4 -0
- package/dist/images/documents/text.d.ts +4 -0
- package/dist/images/documents/video.d.ts +4 -0
- package/dist/images/documents/word.d.ts +4 -0
- package/dist/images/documents/xml.d.ts +4 -0
- package/dist/images/documents/zip.d.ts +4 -0
- package/dist/images/icons/arrow_upward.d.ts +4 -0
- package/dist/images/icons/calendar_today.d.ts +4 -0
- package/dist/images/icons/cancel.d.ts +4 -0
- package/dist/images/icons/check_circle.d.ts +4 -0
- package/dist/images/icons/chevron_left.d.ts +4 -0
- package/dist/images/icons/chevron_right.d.ts +4 -0
- package/dist/images/icons/close.d.ts +4 -0
- package/dist/images/icons/description.d.ts +4 -0
- package/dist/images/icons/double_chevron_left.d.ts +4 -0
- package/dist/images/icons/double_chevron_right.d.ts +4 -0
- package/dist/images/icons/error.d.ts +4 -0
- package/dist/images/icons/expand_less.d.ts +4 -0
- package/dist/images/icons/expand_more.d.ts +4 -0
- package/dist/images/icons/index.d.ts +17 -0
- package/dist/images/icons/list.d.ts +4 -0
- package/dist/images/icons/menu.d.ts +4 -0
- package/dist/images/icons/priority_high.d.ts +4 -0
- package/dist/images/icons/search.d.ts +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context.d.ts +4 -0
- package/package.json +6 -6
- package/src/components/Accordion/Accordion.Item.stories.tsx +10 -9
- package/src/components/Accordion/Accordion.stories.tsx +4 -4
- package/src/components/Accordion/Accordion.test.tsx +48 -14
- package/src/components/Accordion/Accordion.tsx +12 -1
- package/src/components/Breadcrumbs/Breadcrumbs.Item.stories.tsx +8 -1
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +41 -0
- package/src/components/ButtonGroup/ButtonGroup.test.tsx +45 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +20 -0
- package/src/components/ContentsNav/ContentsNav.Item.stories.tsx +8 -0
- package/src/components/ErrorSummary/ErrorSummary.Item.stories.tsx +7 -0
- package/src/components/PageHeader/PageHeader.tsx +2 -1
- package/src/components/PageMetadata/PageMetadata.Item.stories.tsx +9 -0
- package/src/components/RadioButton/RadioGroup.tsx +2 -2
- package/src/components/SearchFacets/SearchFacets.Group.stories.tsx +56 -0
- package/src/components/SearchFacets/SearchFacets.Item.stories.tsx +53 -0
- package/src/components/SearchFacets/SearchFacets.stories.tsx +38 -0
- package/src/components/SearchFacets/SearchFacets.test.tsx +214 -0
- package/src/components/SearchFacets/SearchFacets.tsx +99 -0
- package/src/components/SearchFilters/SearchFilters.Panel.stories.tsx +201 -0
- package/src/components/SearchFilters/SearchFilters.stories.tsx +137 -0
- package/src/components/SearchFilters/SearchFilters.test.tsx +161 -0
- package/src/components/SearchFilters/SearchFilters.tsx +89 -0
- package/src/components/SearchResult/SearchResult.stories.tsx +111 -0
- package/src/components/SearchResult/SearchResult.test.tsx +215 -0
- package/src/components/SearchResult/SearchResult.tsx +137 -0
- package/src/components/SearchSort/SearchSort.stories.tsx +32 -0
- package/src/components/SearchSort/SearchSort.test.tsx +129 -0
- package/src/components/SearchSort/SearchSort.tsx +45 -0
- package/src/components/SequentialNavigation/SequentialNavigation.Next.stories.tsx +1 -1
- package/src/components/SequentialNavigation/SequentialNavigation.Previous.stories.tsx +1 -1
- package/src/components/SequentialNavigation/SequentialNavigation.tsx +0 -1
- package/src/components/SideNavigation/SideNavigation.Item.stories.tsx +9 -0
- package/src/components/SideNavigation/SideNavigation.List.stories.tsx +7 -0
- package/src/components/SideNavigation/SideNavigation.tsx +2 -1
- package/src/components/SiteFooter/SiteFooter.License.stories.tsx +7 -0
- package/src/components/SiteFooter/SiteFooter.Link.stories.tsx +9 -0
- package/src/components/SiteFooter/SiteFooter.Org.stories.tsx +7 -0
- package/src/components/SiteHeader/SiteHeader.tsx +0 -2
- package/src/components/SiteNavigation/SiteNavigation.Item.stories.tsx +10 -0
- package/src/components/SkipLinks/SkipLinks.Item.stories.tsx +11 -1
- package/src/components/SkipLinks/SkipLinks.tsx +10 -0
- package/src/components/SummaryCard/SummaryCard.Action.stories.tsx +7 -0
- package/src/components/SummaryCard/SummaryCard.stories.tsx +7 -0
- package/src/components/SummaryList/SummaryList.Item.stories.tsx +15 -0
- package/src/components/SummaryList/SummaryList.Value.stories.tsx +5 -2
- package/src/components/Tabs/Tabs.Item.stories.tsx +4 -1
- package/src/components/TaskList/TaskList.Group.stories.tsx +9 -0
- package/src/components/TaskList/TaskList.Item.stories.tsx +7 -0
- package/tsconfig.json +14 -14
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Children, createContext, useContext } from 'react';
|
|
2
|
+
import ConditionalWrapper from '../../common/ConditionalWrapper';
|
|
3
|
+
import AspectBox from '../AspectBox/AspectBox';
|
|
4
|
+
import Metadata from '../PageMetadata/PageMetadata';
|
|
5
|
+
|
|
6
|
+
const SearchResultLinkHrefContext = createContext('');
|
|
7
|
+
|
|
8
|
+
const SearchResultContent = ({
|
|
9
|
+
children
|
|
10
|
+
}: SGDS.Component.SearchResult.Content) => {
|
|
11
|
+
const otherChildren: any[] = [];
|
|
12
|
+
let imageChild: React.ReactNode = null;
|
|
13
|
+
|
|
14
|
+
// assign to slots
|
|
15
|
+
Children.forEach(children, (child: React.ReactNode) => {
|
|
16
|
+
const thisChild = child as React.ReactElement<any>;
|
|
17
|
+
if (thisChild && thisChild.type === SearchResultMedia) {
|
|
18
|
+
imageChild = thisChild;
|
|
19
|
+
} else {
|
|
20
|
+
otherChildren.push(thisChild);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
imageChild ?
|
|
26
|
+
(<div className="ds_search-result__has-media">
|
|
27
|
+
{imageChild}
|
|
28
|
+
<div className="ds_search-result__summary">{otherChildren}</div>
|
|
29
|
+
</div>)
|
|
30
|
+
:
|
|
31
|
+
(<div className="ds_search-result__summary">
|
|
32
|
+
{otherChildren}
|
|
33
|
+
</div>)
|
|
34
|
+
)
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const SearchResultContext = ({
|
|
38
|
+
children,
|
|
39
|
+
title = 'Part of'
|
|
40
|
+
}: SGDS.Component.SearchResult.Context) => {
|
|
41
|
+
return (
|
|
42
|
+
<dl className="ds_search-result__context">
|
|
43
|
+
<dt className="ds_search-result__context-key">{title}:</dt>
|
|
44
|
+
{children}
|
|
45
|
+
</dl>
|
|
46
|
+
)
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const SearchResultContextItem = ({
|
|
50
|
+
children
|
|
51
|
+
}: SGDS.Component.SearchResult.ContextItem) => {
|
|
52
|
+
return (
|
|
53
|
+
<dd className="ds_search-result__context-value">
|
|
54
|
+
{children}
|
|
55
|
+
</dd>
|
|
56
|
+
)
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const SearchResultMedia = ({
|
|
60
|
+
children
|
|
61
|
+
}: SGDS.Component.SearchResult.Media) => {
|
|
62
|
+
return (
|
|
63
|
+
<div className="ds_search-result__media-wrapper">
|
|
64
|
+
<a className="ds_search-result__media-link" href={useContext(SearchResultLinkHrefContext)} tabIndex={-1} aria-hidden="true">
|
|
65
|
+
<AspectBox className="ds_search-result__media" ratio="1:1">
|
|
66
|
+
{children}
|
|
67
|
+
</AspectBox>
|
|
68
|
+
</a>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const SearchResultMeta = ({
|
|
74
|
+
children
|
|
75
|
+
}: SGDS.Component.SearchResult.Meta) => {
|
|
76
|
+
return (
|
|
77
|
+
<Metadata className="ds_search-result__metadata" isInline>
|
|
78
|
+
{children}
|
|
79
|
+
</Metadata>
|
|
80
|
+
)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const SearchResult = ({
|
|
84
|
+
children,
|
|
85
|
+
href,
|
|
86
|
+
isPromoted,
|
|
87
|
+
linkComponent,
|
|
88
|
+
promotedTitle = 'Recommended',
|
|
89
|
+
title,
|
|
90
|
+
...props
|
|
91
|
+
}: SGDS.Component.SearchResult) => {
|
|
92
|
+
const LINK_CLASS = 'ds_search-result__link';
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className={[
|
|
96
|
+
'ds_search-result',
|
|
97
|
+
isPromoted ? 'ds_search-result--promoted' : ''
|
|
98
|
+
].join(' ')}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<ConditionalWrapper
|
|
102
|
+
condition={!!isPromoted}
|
|
103
|
+
wrapper={(children: React.JSX.Element) => <div className="ds_search-result--promoted-content">
|
|
104
|
+
<header className="ds_search-result--promoted-title">{promotedTitle}</header>
|
|
105
|
+
{children}
|
|
106
|
+
</div>}
|
|
107
|
+
>
|
|
108
|
+
<SearchResultLinkHrefContext value={href}>
|
|
109
|
+
<h3 className="ds_search-result__title">
|
|
110
|
+
{linkComponent ?
|
|
111
|
+
linkComponent({ className: LINK_CLASS, children: title, href }) :
|
|
112
|
+
<a className={LINK_CLASS} href={href}>{title}</a>
|
|
113
|
+
}
|
|
114
|
+
</h3>
|
|
115
|
+
|
|
116
|
+
{children}
|
|
117
|
+
</SearchResultLinkHrefContext>
|
|
118
|
+
</ConditionalWrapper>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
SearchResult.Content = SearchResultContent;
|
|
124
|
+
SearchResult.Context = SearchResultContext;
|
|
125
|
+
SearchResult.ContextItem = SearchResultContextItem;
|
|
126
|
+
SearchResult.Media = SearchResultMedia;
|
|
127
|
+
SearchResult.Meta = SearchResultMeta;
|
|
128
|
+
SearchResult.MetaItem = Metadata.Item;
|
|
129
|
+
|
|
130
|
+
SearchResultContent.displayName = 'SearchResult.Content';
|
|
131
|
+
SearchResultContext.displayName = 'SearchResult.Context';
|
|
132
|
+
SearchResultContextItem.displayName = 'SearchResult.ContextItem';
|
|
133
|
+
SearchResultMedia.displayName = 'SearchResult.Media';
|
|
134
|
+
SearchResultMeta.displayName = 'SearchResult.Meta';
|
|
135
|
+
SearchResult.MetaItem.displayName = 'SearchResult.MetaItem';
|
|
136
|
+
|
|
137
|
+
export default SearchResult;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import argTypes from '../../../.storybook/sgdsArgTypes';
|
|
3
|
+
|
|
4
|
+
import SearchSort from './SearchSort';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Search results/Sort',
|
|
8
|
+
component: SearchSort,
|
|
9
|
+
argTypes: {
|
|
10
|
+
children: argTypes.children(),
|
|
11
|
+
id: argTypes.id(),
|
|
12
|
+
label: argTypes.label({ defaultValue: 'Sort by' }),
|
|
13
|
+
onApply: argTypes.onClick({ description: 'Callback function to be called when the Apply sort button is clicked' }),
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
id: 'sort-by',
|
|
17
|
+
label: 'Sort by'
|
|
18
|
+
}
|
|
19
|
+
} satisfies Meta<typeof SearchSort>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: (args) => (
|
|
26
|
+
<SearchSort {...args}>
|
|
27
|
+
<SearchSort.Option value="relevance">Most relevant</SearchSort.Option>
|
|
28
|
+
<SearchSort.Option value="date">Updated (newest)</SearchSort.Option>
|
|
29
|
+
<SearchSort.Option value="adate">Updated (oldest)</SearchSort.Option>
|
|
30
|
+
</SearchSort>
|
|
31
|
+
)
|
|
32
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import SearchSort from './SearchSort';
|
|
4
|
+
|
|
5
|
+
const SELECT_ID = 'sort-by';
|
|
6
|
+
const LABEL_TEXT = 'Sort by';
|
|
7
|
+
|
|
8
|
+
test('renders correctly', () => {
|
|
9
|
+
render(
|
|
10
|
+
<SearchSort data-testid="search-sort">
|
|
11
|
+
</SearchSort>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const searchSort = screen.getByTestId('search-sort');
|
|
15
|
+
const select = screen.getByRole('combobox');
|
|
16
|
+
const selectWrapper = select.parentElement;
|
|
17
|
+
const label = selectWrapper?.previousElementSibling;
|
|
18
|
+
const selectArrow = select.nextElementSibling;
|
|
19
|
+
const button = within(searchSort).getByRole('button');
|
|
20
|
+
|
|
21
|
+
expect(select).toHaveClass('ds_select');
|
|
22
|
+
expect(select.id).toEqual(SELECT_ID);
|
|
23
|
+
expect(select).toHaveAttribute('name', SELECT_ID);
|
|
24
|
+
|
|
25
|
+
expect(selectWrapper).toHaveClass('ds_select-wrapper');
|
|
26
|
+
expect(selectWrapper?.tagName).toEqual('DIV');
|
|
27
|
+
|
|
28
|
+
expect(label).toHaveClass('ds_label');
|
|
29
|
+
expect(label).toHaveAttribute('for', SELECT_ID);
|
|
30
|
+
expect(label).toHaveTextContent(LABEL_TEXT);
|
|
31
|
+
|
|
32
|
+
expect(selectArrow).toHaveClass('ds_select-arrow');
|
|
33
|
+
expect(selectArrow).toHaveAttribute('aria-hidden');
|
|
34
|
+
expect(selectArrow?.textContent).toEqual('');
|
|
35
|
+
|
|
36
|
+
expect(searchSort).toBeInTheDocument();
|
|
37
|
+
expect(searchSort).toHaveClass('ds_sort-options');
|
|
38
|
+
expect(searchSort.tagName).toEqual('DIV');
|
|
39
|
+
|
|
40
|
+
expect(button).toHaveClass('ds_button', 'ds_button--small', 'ds_button--secondary');
|
|
41
|
+
expect(button).toHaveTextContent('Apply sort');
|
|
42
|
+
expect(button).toHaveAttribute('type', 'submit');
|
|
43
|
+
expect(button.previousElementSibling).toEqual(selectWrapper);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('custom id and label', () => {
|
|
47
|
+
const CUSTOM_ID = 'custom-sort-by';
|
|
48
|
+
const CUSTOM_LABEL = 'Custom sort by';
|
|
49
|
+
|
|
50
|
+
render(
|
|
51
|
+
<SearchSort id={CUSTOM_ID} label={CUSTOM_LABEL} data-testid="search-sort">
|
|
52
|
+
</SearchSort>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const select = screen.getByRole('combobox');
|
|
56
|
+
const selectWrapper = select.parentElement;
|
|
57
|
+
const label = selectWrapper?.previousElementSibling;
|
|
58
|
+
|
|
59
|
+
expect(select.id).toEqual(CUSTOM_ID);
|
|
60
|
+
expect(select).toHaveAttribute('name', CUSTOM_ID);
|
|
61
|
+
expect(label).toHaveAttribute('for', CUSTOM_ID);
|
|
62
|
+
expect(label).toHaveTextContent(CUSTOM_LABEL);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('event handler onApply', async () => {
|
|
66
|
+
const onApply = vi.fn();
|
|
67
|
+
render(
|
|
68
|
+
<SearchSort onApply={onApply} data-testid="search-sort">
|
|
69
|
+
</SearchSort>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const button = within(screen.getByTestId('search-sort')).getByRole('button');
|
|
73
|
+
await button.click();
|
|
74
|
+
expect(onApply).toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('passing additional props', () => {
|
|
78
|
+
render(
|
|
79
|
+
<SearchSort data-test="foo" data-testid="search-sort">
|
|
80
|
+
</SearchSort>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const searchSort = screen.getByTestId('search-sort');
|
|
84
|
+
expect(searchSort.dataset.test).toEqual('foo');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('passing additional CSS classes', () => {
|
|
88
|
+
render(
|
|
89
|
+
<SearchSort className="foo" data-testid="search-sort">
|
|
90
|
+
</SearchSort>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const searchSort = screen.getByTestId('search-sort');
|
|
94
|
+
expect(searchSort).toHaveClass('foo', 'ds_sort-options');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('renders options correctly', () => {
|
|
98
|
+
const OPTION_VALUE_1 = 'relevance';
|
|
99
|
+
const OPTION_TEXT_1 = 'Most relevant';
|
|
100
|
+
const OPTION_VALUE_2 = 'date';
|
|
101
|
+
const OPTION_TEXT_2 = 'Updated (newest)';
|
|
102
|
+
const OPTION_VALUE_3 = 'adate';
|
|
103
|
+
const OPTION_TEXT_3 = 'Updated (oldest)';
|
|
104
|
+
|
|
105
|
+
render(
|
|
106
|
+
<SearchSort data-testid="search-sort">
|
|
107
|
+
<SearchSort.Option value={OPTION_VALUE_1}>{OPTION_TEXT_1}</SearchSort.Option>
|
|
108
|
+
<SearchSort.Option value={OPTION_VALUE_2}>{OPTION_TEXT_2}</SearchSort.Option>
|
|
109
|
+
<SearchSort.Option value={OPTION_VALUE_3}>{OPTION_TEXT_3}</SearchSort.Option>
|
|
110
|
+
</SearchSort>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const select = screen.getByRole('combobox');
|
|
114
|
+
const options = within(select).getAllByRole('option');
|
|
115
|
+
|
|
116
|
+
expect(options).toHaveLength(4);
|
|
117
|
+
|
|
118
|
+
expect(options[0]).toHaveTextContent('');
|
|
119
|
+
expect(options[0]).toHaveAttribute('value', '');
|
|
120
|
+
|
|
121
|
+
expect(options[1]).toHaveTextContent(OPTION_TEXT_1);
|
|
122
|
+
expect(options[1]).toHaveAttribute('value', OPTION_VALUE_1);
|
|
123
|
+
|
|
124
|
+
expect(options[2]).toHaveTextContent(OPTION_TEXT_2);
|
|
125
|
+
expect(options[2]).toHaveAttribute('value', OPTION_VALUE_2);
|
|
126
|
+
|
|
127
|
+
expect(options[3]).toHaveTextContent(OPTION_TEXT_3);
|
|
128
|
+
expect(options[3]).toHaveAttribute('value', OPTION_VALUE_3);
|
|
129
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AllHTMLAttributes } from "react";
|
|
2
|
+
import Button from "../Button/Button";
|
|
3
|
+
import Select from "../Select/Select";
|
|
4
|
+
|
|
5
|
+
const Option = ({
|
|
6
|
+
children,
|
|
7
|
+
value
|
|
8
|
+
}: AllHTMLAttributes<HTMLOptionElement>) => {
|
|
9
|
+
return (
|
|
10
|
+
<option value={value}>
|
|
11
|
+
{children}
|
|
12
|
+
</option>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SearchSort = ({
|
|
17
|
+
children,
|
|
18
|
+
className,
|
|
19
|
+
id = 'sort-by',
|
|
20
|
+
label = 'Sort by',
|
|
21
|
+
onApply,
|
|
22
|
+
...props
|
|
23
|
+
}: SGDS.Component.SearchSort) => {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className={[
|
|
27
|
+
'ds_sort-options',
|
|
28
|
+
className
|
|
29
|
+
].join(' ')}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<Select id={id} label={label}>
|
|
33
|
+
{children}
|
|
34
|
+
</Select>
|
|
35
|
+
|
|
36
|
+
<Button onClick={onApply} isSmall buttonStyle="secondary" type="submit">Apply sort</Button>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
SearchSort.displayName = 'SearchSort';
|
|
42
|
+
Option.displayName = 'SearchSort.Option';
|
|
43
|
+
SearchSort.Option = Option;
|
|
44
|
+
|
|
45
|
+
export default SearchSort;
|
|
@@ -7,7 +7,7 @@ const meta = {
|
|
|
7
7
|
title: 'Components/SequentialNavigation/SequentialNavigation.Next',
|
|
8
8
|
component: SequentialNavigation.Next,
|
|
9
9
|
argTypes: {
|
|
10
|
-
href: argTypes.href(),
|
|
10
|
+
href: argTypes.href({type: {name: 'string', required: true}}),
|
|
11
11
|
linkComponent: argTypes.linkComponent(),
|
|
12
12
|
textLabel: {
|
|
13
13
|
description: 'String to use for the label that precedes the link text',
|
|
@@ -7,7 +7,7 @@ const meta = {
|
|
|
7
7
|
title: 'Components/SequentialNavigation/SequentialNavigation.Previous',
|
|
8
8
|
component: SequentialNavigation.Previous,
|
|
9
9
|
argTypes: {
|
|
10
|
-
href: argTypes.href(),
|
|
10
|
+
href: argTypes.href({type: {name: 'string', required: true}}),
|
|
11
11
|
linkComponent: argTypes.linkComponent(),
|
|
12
12
|
textLabel: {
|
|
13
13
|
description: 'String to use for the label that precedes the link text',
|
|
@@ -6,6 +6,15 @@ import SideNavigation from './SideNavigation';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SideNavigation/SideNavigation.Item',
|
|
8
8
|
component: SideNavigation.Item,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<nav className="ds_side-navigation">
|
|
12
|
+
<ul className="ds_side-navigation__list">
|
|
13
|
+
<Story />
|
|
14
|
+
</ul>
|
|
15
|
+
</nav>
|
|
16
|
+
)
|
|
17
|
+
],
|
|
9
18
|
argTypes: {
|
|
10
19
|
href: argTypes.href(),
|
|
11
20
|
isCurrent: argTypes.isCurrent(),
|
|
@@ -6,6 +6,13 @@ import SideNavigation from './SideNavigation';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SideNavigation/SideNavigation.List',
|
|
8
8
|
component: SideNavigation.List,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<nav className="ds_side-navigation">
|
|
12
|
+
<Story />
|
|
13
|
+
</nav>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
10
17
|
isRoot: {
|
|
11
18
|
description: 'Indicates that this is the root list in a nested structure. Required for mobile navigation.',
|
|
@@ -43,6 +43,7 @@ const SideNavigationItem = function ({
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const SideNavigation = function ({
|
|
46
|
+
ariaLabel = 'Sections',
|
|
46
47
|
children,
|
|
47
48
|
className,
|
|
48
49
|
...props
|
|
@@ -57,7 +58,7 @@ const SideNavigation = function ({
|
|
|
57
58
|
|
|
58
59
|
return (
|
|
59
60
|
<nav
|
|
60
|
-
aria-label=
|
|
61
|
+
aria-label={ariaLabel}
|
|
61
62
|
className={[
|
|
62
63
|
'ds_side-navigation',
|
|
63
64
|
className
|
|
@@ -6,6 +6,13 @@ import SiteFooter from './SiteFooter';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SiteFooter/SiteFooter.License',
|
|
8
8
|
component: SiteFooter.License,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<nav className="ds_site-footer" style={{borderTop: 0}}>
|
|
12
|
+
<Story />
|
|
13
|
+
</nav>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
10
17
|
children: argTypes.children()
|
|
11
18
|
},
|
|
@@ -6,6 +6,15 @@ import SiteFooter from './SiteFooter';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SiteFooter/SiteFooter.Link',
|
|
8
8
|
component: SiteFooter.Link,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<div className="ds_site-footer__site-items" style={{ borderBottom: 0 }}>
|
|
12
|
+
<ul>
|
|
13
|
+
<Story />
|
|
14
|
+
</ul>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
],
|
|
9
18
|
argTypes: {
|
|
10
19
|
href: argTypes.href(),
|
|
11
20
|
linkComponent: argTypes.linkComponent(),
|
|
@@ -6,6 +6,13 @@ import SiteFooter from './SiteFooter';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SiteFooter/SiteFooter.Org',
|
|
8
8
|
component: SiteFooter.Org,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<div className="ds_site-footer__content" style={{borderTop: 0}}>
|
|
12
|
+
<Story />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
10
17
|
href: argTypes.href(),
|
|
11
18
|
title: {
|
|
@@ -6,7 +6,17 @@ import SiteNavigation from './SiteNavigation';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SiteNavigation/SiteNavigation.Item',
|
|
8
8
|
component: SiteNavigation.Item,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<nav className="ds_site-navigation">
|
|
12
|
+
<ul className="ds_site-navigation__list">
|
|
13
|
+
<Story />
|
|
14
|
+
</ul>
|
|
15
|
+
</nav>
|
|
16
|
+
)
|
|
17
|
+
],
|
|
9
18
|
argTypes: {
|
|
19
|
+
href: argTypes.href({type: {name: 'string', required: true}}),
|
|
10
20
|
linkComponent: argTypes.linkComponent(),
|
|
11
21
|
children: argTypes.children()
|
|
12
22
|
},
|
|
@@ -6,10 +6,20 @@ import SkipLinks from './SkipLinks';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SkipLinks/SkipLinks.Link',
|
|
8
8
|
component: SkipLinks.Link,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<ul className="ds_skip-links">
|
|
12
|
+
<Story />
|
|
13
|
+
</ul>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
10
17
|
fragmentId: {
|
|
11
18
|
description: 'ID of the destination element',
|
|
12
|
-
type:
|
|
19
|
+
type: {
|
|
20
|
+
name: 'string',
|
|
21
|
+
required: true
|
|
22
|
+
}
|
|
13
23
|
},
|
|
14
24
|
children: argTypes.children()
|
|
15
25
|
},
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import dsSkipLinks from '@scottish-government/design-system/src/components/skip-links/skip-links';
|
|
5
|
+
|
|
1
6
|
const Link = ({
|
|
2
7
|
children,
|
|
3
8
|
fragmentId
|
|
@@ -18,6 +23,11 @@ const SkipLinks = ({
|
|
|
18
23
|
isStatic,
|
|
19
24
|
...props
|
|
20
25
|
}: SGDS.Component.SkipLinks) => {
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
dsSkipLinks.init();
|
|
29
|
+
});
|
|
30
|
+
|
|
21
31
|
return (
|
|
22
32
|
<div
|
|
23
33
|
className={[
|
|
@@ -6,6 +6,13 @@ import SummaryCard from './SummaryCard';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SummaryCard/SummaryCard.Action',
|
|
8
8
|
component: SummaryCard.Action,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<div className="ds_summary-card__header">
|
|
12
|
+
<Story />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
10
17
|
href: argTypes.href(),
|
|
11
18
|
onClick: argTypes.onClick(),
|
|
@@ -9,6 +9,13 @@ const meta = {
|
|
|
9
9
|
component: SummaryCard,
|
|
10
10
|
argTypes: {
|
|
11
11
|
headingLevel: argTypes.headingLevel(),
|
|
12
|
+
title: {
|
|
13
|
+
description: 'The title of the summary card',
|
|
14
|
+
type: {
|
|
15
|
+
name: 'string',
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
},
|
|
12
19
|
children: argTypes.children()
|
|
13
20
|
},
|
|
14
21
|
args: {
|
|
@@ -6,10 +6,25 @@ import SummaryList from './SummaryList';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/SummaryList/SummaryList.Item',
|
|
8
8
|
component: SummaryList.Item,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<ul>
|
|
12
|
+
<Story />
|
|
13
|
+
</ul>
|
|
14
|
+
)
|
|
15
|
+
],
|
|
9
16
|
argTypes: {
|
|
17
|
+
Title: {
|
|
18
|
+
description: 'Title of the summary list item.',
|
|
19
|
+
type: {
|
|
20
|
+
name: 'string',
|
|
21
|
+
required: true
|
|
22
|
+
}
|
|
23
|
+
},
|
|
10
24
|
children: argTypes.children()
|
|
11
25
|
},
|
|
12
26
|
args: {
|
|
27
|
+
title: 'Contact details',
|
|
13
28
|
children: [
|
|
14
29
|
<SummaryList.Value name="Email">
|
|
15
30
|
email@gov.scot
|
|
@@ -8,8 +8,11 @@ const meta = {
|
|
|
8
8
|
component: SummaryList.Value,
|
|
9
9
|
argTypes: {
|
|
10
10
|
name: {
|
|
11
|
-
description: '
|
|
12
|
-
type:
|
|
11
|
+
description: 'Name of the summary value',
|
|
12
|
+
type: {
|
|
13
|
+
name: 'string',
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
13
16
|
},
|
|
14
17
|
children: argTypes.children()
|
|
15
18
|
},
|
|
@@ -6,6 +6,15 @@ import TaskList from './TaskList';
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: 'Components/TaskList/TaskList.Group',
|
|
8
8
|
component: TaskList.Group,
|
|
9
|
+
decorators: [
|
|
10
|
+
Story => (
|
|
11
|
+
<div className="ds_task-list">
|
|
12
|
+
<ul>
|
|
13
|
+
<Story />
|
|
14
|
+
</ul>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
],
|
|
9
18
|
argTypes: {
|
|
10
19
|
headingId: {
|
|
11
20
|
description: 'ID of the task list\'s heading element',
|