@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,161 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import Filters from './SearchFilters';
|
|
4
|
+
import Checkbox from '../Checkbox/Checkbox';
|
|
5
|
+
|
|
6
|
+
test('search filters boilerplate renders correctly', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Filters data-testid="searchfilters"/>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const filters = screen.getByTestId('searchfilters');
|
|
12
|
+
|
|
13
|
+
expect(filters).toHaveClass('ds_search-filters');
|
|
14
|
+
expect(filters.tagName).toEqual('DIV');
|
|
15
|
+
|
|
16
|
+
const details = filters.querySelector('.ds_details');
|
|
17
|
+
expect(details).toBeInTheDocument();
|
|
18
|
+
expect(details).toHaveClass('ds_no-margin')
|
|
19
|
+
expect(details?.tagName).toEqual('DIV');
|
|
20
|
+
expect(details).toHaveAttribute('data-module', 'ds-details');
|
|
21
|
+
expect(details?.parentElement).toEqual(filters);
|
|
22
|
+
|
|
23
|
+
const detailsToggle = filters.querySelector('.ds_details__toggle');
|
|
24
|
+
expect(detailsToggle).toBeInTheDocument();
|
|
25
|
+
expect(detailsToggle).toHaveClass('visually-hidden');
|
|
26
|
+
expect(detailsToggle?.tagName).toEqual('INPUT');
|
|
27
|
+
expect(detailsToggle).toHaveAttribute('type', 'checkbox');
|
|
28
|
+
expect(detailsToggle).toHaveAttribute('id', 'filters-toggle');
|
|
29
|
+
expect(detailsToggle?.parentElement).toEqual(details);
|
|
30
|
+
|
|
31
|
+
const detailsSummary = filters.querySelector('.ds_details__summary');
|
|
32
|
+
expect(detailsSummary).toBeInTheDocument();
|
|
33
|
+
expect(detailsSummary?.tagName).toEqual('LABEL');
|
|
34
|
+
expect(detailsSummary).toHaveAttribute('for', 'filters-toggle');
|
|
35
|
+
expect(detailsSummary).toHaveTextContent('Search filters');
|
|
36
|
+
expect(detailsSummary?.parentElement).toEqual(details);
|
|
37
|
+
expect(detailsSummary?.previousElementSibling).toEqual(detailsToggle);
|
|
38
|
+
|
|
39
|
+
const skipLinks = filters.querySelector('.ds_skip-links');
|
|
40
|
+
|
|
41
|
+
const detailsText = filters.querySelector('.ds_details__text');
|
|
42
|
+
expect(detailsText).toBeInTheDocument();
|
|
43
|
+
expect(detailsText?.tagName).toEqual('DIV');
|
|
44
|
+
expect(detailsText?.parentElement).toEqual(details);
|
|
45
|
+
expect(detailsText?.previousElementSibling).toEqual(skipLinks);
|
|
46
|
+
|
|
47
|
+
const form = filters.querySelector('#filters');
|
|
48
|
+
expect(form).toBeInTheDocument();
|
|
49
|
+
expect(form?.tagName).toEqual('FORM');
|
|
50
|
+
|
|
51
|
+
const formTitle = within(form).getByRole('heading');
|
|
52
|
+
expect(formTitle).toBeInTheDocument();
|
|
53
|
+
expect(formTitle.tagName).toEqual('H2');
|
|
54
|
+
expect(formTitle).toHaveTextContent('Filter by');
|
|
55
|
+
|
|
56
|
+
const accordion = form?.querySelector('.ds_accordion');;
|
|
57
|
+
expect(accordion).toBeInTheDocument();
|
|
58
|
+
expect(accordion).toHaveClass('js-initialised', 'ds_accordion--small', 'ds_!_margin-top--0');
|
|
59
|
+
expect(accordion?.parentElement).toEqual(form);
|
|
60
|
+
expect(accordion?.tagName).toEqual('DIV');
|
|
61
|
+
expect(accordion?.previousElementSibling).toEqual(formTitle);
|
|
62
|
+
|
|
63
|
+
const applyButton = within(filters).getByRole('button');
|
|
64
|
+
expect(applyButton).toBeInTheDocument();
|
|
65
|
+
expect(applyButton).toHaveClass('ds_button', 'ds_button--max', 'ds_button--small', 'ds_no-margin');
|
|
66
|
+
expect(applyButton).toHaveTextContent('Apply filter');
|
|
67
|
+
expect(applyButton?.tagName).toEqual('BUTTON');
|
|
68
|
+
expect(applyButton?.parentElement).toEqual(form);
|
|
69
|
+
expect(applyButton?.previousElementSibling).toEqual(accordion);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('search filter panel boilerplate renders correctly', () => {
|
|
73
|
+
render(
|
|
74
|
+
<Filters.Panel data-testid="searchfilter" />
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const filterPanel = screen.getByTestId('searchfilter');
|
|
78
|
+
expect(filterPanel).toHaveClass('ds_accordion-item');
|
|
79
|
+
expect(filterPanel.tagName).toEqual('DIV');
|
|
80
|
+
|
|
81
|
+
const filterPanelBody = filterPanel.querySelector('.ds_accordion-item__body');
|
|
82
|
+
expect(filterPanelBody).toBeInTheDocument();
|
|
83
|
+
expect(filterPanelBody?.parentElement).toEqual(filterPanel);
|
|
84
|
+
|
|
85
|
+
const fieldset = filterPanelBody?.querySelector('fieldset');
|
|
86
|
+
expect(fieldset).toBeInTheDocument();
|
|
87
|
+
expect(fieldset?.tagName).toEqual('FIELDSET');
|
|
88
|
+
expect(fieldset?.parentElement).toEqual(filterPanelBody);
|
|
89
|
+
|
|
90
|
+
const legend = fieldset?.querySelector('legend');
|
|
91
|
+
expect(legend).toBeInTheDocument();
|
|
92
|
+
expect(legend).toHaveClass('visually-hidden');
|
|
93
|
+
expect(legend?.tagName).toEqual('LEGEND');
|
|
94
|
+
expect(legend?.parentElement).toEqual(fieldset);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('search filter panel with scrollable content', () => {
|
|
98
|
+
render(
|
|
99
|
+
<Filters.Panel isScrollable data-testid="searchfilter"/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const filterPanel = screen.getByTestId('searchfilter');
|
|
103
|
+
const filterPanelBody = filterPanel.querySelector('.ds_accordion-item__body');
|
|
104
|
+
const fieldset = filterPanelBody?.querySelector('fieldset');
|
|
105
|
+
const scrollableDiv = fieldset?.querySelector('.ds_search-filters__scrollable');
|
|
106
|
+
|
|
107
|
+
expect(scrollableDiv).toBeInTheDocument();
|
|
108
|
+
expect(scrollableDiv?.parentElement).toEqual(fieldset);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('search filter panel with an active filter count', () => {
|
|
112
|
+
render(
|
|
113
|
+
<Filters.Panel activeFilterCount={3} heading="Content type" data-testid="searchfilter"/>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const filterPanel = screen.getByTestId('searchfilter');
|
|
117
|
+
|
|
118
|
+
const accordionTitle = filterPanel.querySelector('.ds_accordion-item__title');
|
|
119
|
+
expect(accordionTitle).toBeInTheDocument();
|
|
120
|
+
expect(accordionTitle).toHaveTextContent('Content type');
|
|
121
|
+
expect(accordionTitle).toHaveTextContent('(3 selected)');
|
|
122
|
+
|
|
123
|
+
const filterCountDiv = accordionTitle?.querySelector('.ds_search-filters__filter-count');
|
|
124
|
+
expect(filterCountDiv).toBeInTheDocument();
|
|
125
|
+
expect(filterCountDiv).toHaveTextContent('(3 selected)');
|
|
126
|
+
expect(filterCountDiv?.parentElement).toEqual(accordionTitle);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('search filter checkbox group renders correctly', () => {
|
|
130
|
+
render(
|
|
131
|
+
<Filters.CheckboxGroup data-testid="searchfiltercheckboxgroup">
|
|
132
|
+
<Checkbox label="Pension Credit" id="pensioncredit" />
|
|
133
|
+
</Filters.CheckboxGroup>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const checkboxGroup = screen.getByTestId('searchfiltercheckboxgroup');
|
|
137
|
+
expect(checkboxGroup).toHaveClass('ds_search-filters__checkboxes');
|
|
138
|
+
|
|
139
|
+
const checkbox = within(checkboxGroup).getByRole('checkbox');
|
|
140
|
+
const checkboxWrapper = checkbox.parentElement;
|
|
141
|
+
expect(checkboxWrapper).toBeInTheDocument();
|
|
142
|
+
expect(checkboxWrapper).toHaveClass('ds_checkbox', 'ds_checkbox--small');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('passing additional props', () => {
|
|
146
|
+
render(
|
|
147
|
+
<Filters data-test="foo" data-testid="searchfilters"/>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const searchFilters = screen.getByTestId('searchfilters');
|
|
151
|
+
expect(searchFilters?.dataset.test).toEqual('foo');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('passing additional CSS classes', () => {
|
|
155
|
+
render(
|
|
156
|
+
<Filters className="foo" data-testid="searchfilters"/>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const searchFilters = screen.getByTestId('searchfilters');
|
|
160
|
+
expect(searchFilters).toHaveClass('foo');
|
|
161
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import Accordion from "../Accordion/Accordion";
|
|
2
|
+
import Button from "../Button/Button";
|
|
3
|
+
import SkipLinks from "../SkipLinks/SkipLinks";
|
|
4
|
+
import ConditionalWrapper from "../../common/ConditionalWrapper";
|
|
5
|
+
import CheckboxGroup from "../Checkbox/CheckboxGroup";
|
|
6
|
+
|
|
7
|
+
export const FilterPanel = ({
|
|
8
|
+
activeFilterCount = 0,
|
|
9
|
+
children,
|
|
10
|
+
isScrollable = false,
|
|
11
|
+
legend,
|
|
12
|
+
heading = 'Filter',
|
|
13
|
+
...props
|
|
14
|
+
}: SGDS.Component.SearchFilters.Panel) => {
|
|
15
|
+
const headingWithCount = <>
|
|
16
|
+
{heading}
|
|
17
|
+
{activeFilterCount > 0 && <div className="ds_search-filters__filter-count">({activeFilterCount} selected)</div>}
|
|
18
|
+
</>;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Accordion.Item heading={headingWithCount} {...props}>
|
|
22
|
+
<fieldset>
|
|
23
|
+
<legend className="visually-hidden">{legend}</legend>
|
|
24
|
+
|
|
25
|
+
<ConditionalWrapper
|
|
26
|
+
condition={isScrollable}
|
|
27
|
+
wrapper={(children: React.JSX.Element) => <div className="ds_search-filters__scrollable">{children}</div>}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</ConditionalWrapper>
|
|
31
|
+
</fieldset>
|
|
32
|
+
</Accordion.Item>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const FilterCheckboxGroup = ({
|
|
37
|
+
children,
|
|
38
|
+
...props
|
|
39
|
+
}: any) => {
|
|
40
|
+
return (
|
|
41
|
+
<CheckboxGroup
|
|
42
|
+
className="ds_search-filters__checkboxes"
|
|
43
|
+
isSmall
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</CheckboxGroup>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const Filters = ({
|
|
52
|
+
children,
|
|
53
|
+
searchResultsContainerId = 'search-results',
|
|
54
|
+
...props
|
|
55
|
+
}: SGDS.Component.SearchFilters) => {
|
|
56
|
+
return (
|
|
57
|
+
<div className="ds_search-filters" {...props}>
|
|
58
|
+
<div className="ds_details ds_no-margin" data-module="ds-details">
|
|
59
|
+
<input id="filters-toggle" type="checkbox" className="ds_details__toggle visually-hidden"/>
|
|
60
|
+
<label htmlFor="filters-toggle" className="ds_details__summary">
|
|
61
|
+
Search filters
|
|
62
|
+
</label>
|
|
63
|
+
|
|
64
|
+
<SkipLinks isStatic>
|
|
65
|
+
<SkipLinks.Link fragmentId={searchResultsContainerId}>Skip to results</SkipLinks.Link>
|
|
66
|
+
</SkipLinks>
|
|
67
|
+
|
|
68
|
+
<div className="ds_details__text">
|
|
69
|
+
<form id="filters">
|
|
70
|
+
<h2 className="ds_search-filters__title ds_h4">Filter by</h2>
|
|
71
|
+
|
|
72
|
+
<Accordion className="ds_!_margin-top--0" isSmall hideOpenAll>
|
|
73
|
+
{children}
|
|
74
|
+
</Accordion>
|
|
75
|
+
|
|
76
|
+
<Button isSmall width="max" className="ds_no-margin">Apply filter</Button>
|
|
77
|
+
</form>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Filters.Panel = FilterPanel;
|
|
85
|
+
Filters.CheckboxGroup = FilterCheckboxGroup;
|
|
86
|
+
FilterCheckboxGroup.displayName = 'Filters.CheckboxGroup';
|
|
87
|
+
FilterPanel.displayName = 'Filters.Panel';
|
|
88
|
+
|
|
89
|
+
export default Filters;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import argTypes from '../../../.storybook/sgdsArgTypes';
|
|
3
|
+
|
|
4
|
+
import SearchResult from './SearchResult';
|
|
5
|
+
|
|
6
|
+
//@ts-ignore
|
|
7
|
+
import coo from '../../../static/images/highland-cow.jpg';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Components/Search results/Result',
|
|
11
|
+
component: SearchResult,
|
|
12
|
+
argTypes: {
|
|
13
|
+
children: argTypes.children()
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
title: 'Application incomplete'
|
|
17
|
+
}
|
|
18
|
+
} satisfies Meta<typeof SearchResult>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: (args) => (
|
|
25
|
+
<SearchResult href="#foo" title="Greenhouse gas statistics 1990-2022" {...args}>
|
|
26
|
+
<SearchResult.Content>
|
|
27
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
28
|
+
</SearchResult.Content>
|
|
29
|
+
</SearchResult>
|
|
30
|
+
)
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Metadata: Story = {
|
|
34
|
+
render: (args) => (
|
|
35
|
+
<SearchResult href="#foo" title="Greenhouse gas statistics 1990-2022" {...args}>
|
|
36
|
+
<SearchResult.Content>
|
|
37
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
38
|
+
</SearchResult.Content>
|
|
39
|
+
<SearchResult.Meta>
|
|
40
|
+
<SearchResult.MetaItem name="Publication type">
|
|
41
|
+
Statistics
|
|
42
|
+
</SearchResult.MetaItem>
|
|
43
|
+
<SearchResult.MetaItem name="Date">
|
|
44
|
+
18 June 2024
|
|
45
|
+
</SearchResult.MetaItem>
|
|
46
|
+
</SearchResult.Meta>
|
|
47
|
+
</SearchResult>
|
|
48
|
+
)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Context: Story = {
|
|
52
|
+
render: (args) => (
|
|
53
|
+
<SearchResult href="#foo" title="Greenhouse gas statistics 1990-2022" {...args}>
|
|
54
|
+
<SearchResult.Content>
|
|
55
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
56
|
+
</SearchResult.Content>
|
|
57
|
+
<SearchResult.Context title="Part of">
|
|
58
|
+
<SearchResult.ContextItem><a href="#">Environment statistics</a></SearchResult.ContextItem>
|
|
59
|
+
<SearchResult.ContextItem><a href="#">Energy statistics</a></SearchResult.ContextItem>
|
|
60
|
+
</SearchResult.Context>
|
|
61
|
+
</SearchResult>
|
|
62
|
+
)
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Media: Story = {
|
|
66
|
+
render: (args) => (
|
|
67
|
+
<SearchResult href="#foo" title="Application incomplete" {...args}>
|
|
68
|
+
<SearchResult.Content>
|
|
69
|
+
<SearchResult.Media>
|
|
70
|
+
<img src={coo} alt="" />
|
|
71
|
+
</SearchResult.Media>
|
|
72
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
73
|
+
</SearchResult.Content>
|
|
74
|
+
</SearchResult>
|
|
75
|
+
)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const Promoted: Story = {
|
|
79
|
+
render: (args) => (
|
|
80
|
+
<SearchResult isPromoted href="#foo" title="Greenhouse gas statistics 1990-2022" {...args}>
|
|
81
|
+
<SearchResult.Content>
|
|
82
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
83
|
+
</SearchResult.Content>
|
|
84
|
+
</SearchResult>
|
|
85
|
+
)
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const KitchenSink: Story = {
|
|
89
|
+
render: (args) => (
|
|
90
|
+
<SearchResult isPromoted href="#foo" title="Application incomplete" {...args}>
|
|
91
|
+
<SearchResult.Content>
|
|
92
|
+
<SearchResult.Media>
|
|
93
|
+
<img src={coo} alt="" />
|
|
94
|
+
</SearchResult.Media>
|
|
95
|
+
Official statistics showing emissions of greenhouse gases in Scotland over the period 1990 to 2022.
|
|
96
|
+
</SearchResult.Content>
|
|
97
|
+
<SearchResult.Meta>
|
|
98
|
+
<SearchResult.MetaItem name="Publication type">
|
|
99
|
+
Statistics
|
|
100
|
+
</SearchResult.MetaItem>
|
|
101
|
+
<SearchResult.MetaItem name="Date">
|
|
102
|
+
18 June 2024
|
|
103
|
+
</SearchResult.MetaItem>
|
|
104
|
+
</SearchResult.Meta>
|
|
105
|
+
<SearchResult.Context title="Part of">
|
|
106
|
+
<SearchResult.ContextItem><a href="#">Environment statistics</a></SearchResult.ContextItem>
|
|
107
|
+
<SearchResult.ContextItem><a href="#">Energy statistics</a></SearchResult.ContextItem>
|
|
108
|
+
</SearchResult.Context>
|
|
109
|
+
</SearchResult>
|
|
110
|
+
)
|
|
111
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import SearchResult from './SearchResult';
|
|
4
|
+
|
|
5
|
+
const RESULT_TITLE = 'My title';
|
|
6
|
+
const RESULT_HREF = '#foo';
|
|
7
|
+
const RESULT_CONTENT = 'My content';
|
|
8
|
+
const META_KEY = 'My meta key';
|
|
9
|
+
const META_VALUE = 'My meta value';
|
|
10
|
+
const CONTEXT_VALUE = 'My context value';
|
|
11
|
+
|
|
12
|
+
test('search result renders correctly', () => {
|
|
13
|
+
render(
|
|
14
|
+
<SearchResult href={RESULT_HREF} title={RESULT_TITLE} data-testid="searchresult">
|
|
15
|
+
<SearchResult.Content>
|
|
16
|
+
{RESULT_CONTENT}
|
|
17
|
+
</SearchResult.Content>
|
|
18
|
+
<SearchResult.Meta>
|
|
19
|
+
<SearchResult.MetaItem name={META_KEY}>
|
|
20
|
+
{META_VALUE}
|
|
21
|
+
</SearchResult.MetaItem>
|
|
22
|
+
</SearchResult.Meta>
|
|
23
|
+
<SearchResult.Context>
|
|
24
|
+
<SearchResult.ContextItem>{CONTEXT_VALUE}</SearchResult.ContextItem>
|
|
25
|
+
</SearchResult.Context>
|
|
26
|
+
</SearchResult>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const searchResult = screen.getByTestId('searchresult');
|
|
30
|
+
expect(searchResult).toHaveClass('ds_search-result');
|
|
31
|
+
expect(searchResult).not.toHaveClass('ds_search-result--promoted');
|
|
32
|
+
|
|
33
|
+
const title = screen.getByRole('heading');
|
|
34
|
+
expect(title).toHaveClass('ds_search-result__title');
|
|
35
|
+
expect(title).toHaveTextContent(RESULT_TITLE);
|
|
36
|
+
expect(title?.parentElement).toEqual(searchResult);
|
|
37
|
+
|
|
38
|
+
const link = within(title).getByRole('link');
|
|
39
|
+
expect(link).toHaveClass('ds_search-result__link');
|
|
40
|
+
expect(link).toHaveAttribute('href', RESULT_HREF);
|
|
41
|
+
expect(link.tagName).toBe('A');
|
|
42
|
+
|
|
43
|
+
const content = screen.getByText(RESULT_CONTENT);
|
|
44
|
+
expect(content).toBeInTheDocument();
|
|
45
|
+
expect(content).toHaveClass('ds_search-result__summary');
|
|
46
|
+
expect(content?.previousElementSibling).toEqual(title);
|
|
47
|
+
|
|
48
|
+
const meta = searchResult.querySelector('.ds_metadata');
|
|
49
|
+
expect(meta).toBeInTheDocument();
|
|
50
|
+
expect(meta).toHaveClass('ds_metadata--inline', 'ds_search-result__metadata');
|
|
51
|
+
expect(meta?.previousElementSibling).toEqual(content);
|
|
52
|
+
expect(meta?.tagName).toBe('DL');
|
|
53
|
+
|
|
54
|
+
const metaItem = meta.querySelector('.ds_metadata__item');
|
|
55
|
+
expect(metaItem).toBeInTheDocument();
|
|
56
|
+
expect(metaItem).toHaveClass('ds_metadata__item');
|
|
57
|
+
expect(metaItem?.parentElement).toEqual(meta);
|
|
58
|
+
|
|
59
|
+
const metaTerm = meta.querySelector('dt');
|
|
60
|
+
expect(metaTerm).toBeInTheDocument();
|
|
61
|
+
expect(metaTerm).toHaveClass('ds_metadata__key');
|
|
62
|
+
expect(metaTerm?.textContent).toBe(META_KEY);
|
|
63
|
+
expect(metaTerm?.parentElement).toEqual(metaItem);
|
|
64
|
+
|
|
65
|
+
const metaDesc = meta.querySelector('dd');
|
|
66
|
+
expect(metaDesc).toBeInTheDocument();
|
|
67
|
+
expect(metaDesc).toHaveClass('ds_metadata__value');
|
|
68
|
+
expect(metaDesc?.textContent.trim()).toBe(META_VALUE);
|
|
69
|
+
expect(metaDesc?.parentElement).toEqual(metaItem);
|
|
70
|
+
expect(metaDesc?.previousElementSibling).toEqual(metaTerm);
|
|
71
|
+
|
|
72
|
+
const context = searchResult.querySelector('.ds_search-result__context');
|
|
73
|
+
expect(context).toBeInTheDocument();
|
|
74
|
+
expect(context).toHaveClass('ds_search-result__context');
|
|
75
|
+
expect(context?.tagName).toBe('DL');
|
|
76
|
+
expect(context?.previousElementSibling).toEqual(meta);
|
|
77
|
+
|
|
78
|
+
const contextTitle = context?.querySelector('dt');
|
|
79
|
+
expect(contextTitle).toBeInTheDocument();
|
|
80
|
+
expect(contextTitle).toHaveClass('ds_search-result__context-key');
|
|
81
|
+
expect(contextTitle?.textContent).toBe('Part of:');
|
|
82
|
+
expect(contextTitle?.parentElement).toEqual(context);
|
|
83
|
+
|
|
84
|
+
const contextValue = context?.querySelector('dd');
|
|
85
|
+
expect(contextValue).toBeInTheDocument();
|
|
86
|
+
expect(contextValue).toHaveClass('ds_search-result__context-value');
|
|
87
|
+
expect(contextValue?.textContent).toBe(CONTEXT_VALUE);
|
|
88
|
+
expect(contextValue?.previousElementSibling).toEqual(contextTitle);
|
|
89
|
+
expect(contextValue?.parentElement).toEqual(context);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('promoted search result renders correctly', () => {
|
|
93
|
+
render(
|
|
94
|
+
<SearchResult isPromoted href="#foo" title={RESULT_TITLE} data-testid="searchresult">
|
|
95
|
+
<SearchResult.Content>
|
|
96
|
+
{RESULT_CONTENT}
|
|
97
|
+
</SearchResult.Content>
|
|
98
|
+
</SearchResult>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const searchResult = screen.getByTestId('searchresult');
|
|
102
|
+
expect(searchResult).toHaveClass('ds_search-result', 'ds_search-result--promoted');
|
|
103
|
+
|
|
104
|
+
const promotedContent = searchResult.querySelector('.ds_search-result--promoted-content');
|
|
105
|
+
expect(promotedContent).toBeInTheDocument();
|
|
106
|
+
expect(promotedContent?.parentElement).toEqual(searchResult);
|
|
107
|
+
expect(promotedContent?.tagName).toBe('DIV');
|
|
108
|
+
|
|
109
|
+
const promotedTitle = promotedContent?.querySelector('.ds_search-result--promoted-title');
|
|
110
|
+
expect(promotedTitle).toBeInTheDocument();
|
|
111
|
+
expect(promotedTitle?.textContent).toBe('Recommended');
|
|
112
|
+
expect(promotedTitle?.parentElement).toEqual(promotedContent);
|
|
113
|
+
|
|
114
|
+
const title = screen.getByRole('heading');
|
|
115
|
+
expect(title).toHaveClass('ds_search-result__title');
|
|
116
|
+
expect(title).toHaveTextContent(RESULT_TITLE);
|
|
117
|
+
expect(title?.parentElement).toEqual(promotedContent);
|
|
118
|
+
|
|
119
|
+
const content = screen.getByText(RESULT_CONTENT);
|
|
120
|
+
expect(content).toBeInTheDocument();
|
|
121
|
+
expect(content?.previousElementSibling).toEqual(title);
|
|
122
|
+
expect(content?.parentElement).toEqual(promotedContent);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('search result with media renders correctly', () => {
|
|
126
|
+
const IMAGE = <img alt=""/>;
|
|
127
|
+
|
|
128
|
+
render(
|
|
129
|
+
<SearchResult href={RESULT_HREF} title={RESULT_TITLE} data-testid="searchresult">
|
|
130
|
+
<SearchResult.Content>
|
|
131
|
+
<SearchResult.Media>
|
|
132
|
+
{IMAGE}
|
|
133
|
+
</SearchResult.Media>
|
|
134
|
+
{RESULT_CONTENT}
|
|
135
|
+
</SearchResult.Content>
|
|
136
|
+
</SearchResult>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const searchResult = screen.getByTestId('searchresult');
|
|
140
|
+
const title = screen.getByRole('heading');
|
|
141
|
+
|
|
142
|
+
const summary = searchResult.querySelector('.ds_search-result__has-media');
|
|
143
|
+
expect(summary).toBeInTheDocument();
|
|
144
|
+
expect(summary?.tagName).toBe('DIV');
|
|
145
|
+
expect(summary?.parentElement).toEqual(searchResult);
|
|
146
|
+
expect(summary?.previousElementSibling).toEqual(title);
|
|
147
|
+
|
|
148
|
+
const mediaWrapper = summary?.querySelector('.ds_search-result__media-wrapper');
|
|
149
|
+
expect(mediaWrapper).toBeInTheDocument();
|
|
150
|
+
expect(mediaWrapper?.parentElement).toEqual(summary);
|
|
151
|
+
|
|
152
|
+
const mediaLink = mediaWrapper?.querySelector('.ds_search-result__media-link');
|
|
153
|
+
expect(mediaLink).toBeInTheDocument();
|
|
154
|
+
expect(mediaLink).toHaveAttribute('aria-hidden', 'true');
|
|
155
|
+
expect(mediaLink).toHaveAttribute('href', RESULT_HREF);
|
|
156
|
+
expect(mediaLink).toHaveAttribute('tabindex', '-1');
|
|
157
|
+
expect(mediaLink?.parentElement).toEqual(mediaWrapper);
|
|
158
|
+
expect(mediaLink?.tagName).toBe('A');
|
|
159
|
+
|
|
160
|
+
const media = summary?.querySelector('.ds_search-result__media');
|
|
161
|
+
expect(media).toBeInTheDocument();
|
|
162
|
+
expect(media).toHaveClass('ds_aspect-box', 'ds_aspect-box--square');
|
|
163
|
+
expect(media?.parentElement).toEqual(mediaLink);
|
|
164
|
+
expect(media?.tagName).toBe('DIV');
|
|
165
|
+
|
|
166
|
+
const img = media?.querySelector('img');
|
|
167
|
+
expect(img).toBeInTheDocument();
|
|
168
|
+
expect(img?.parentElement).toEqual(media);
|
|
169
|
+
|
|
170
|
+
const content = screen.getByText(RESULT_CONTENT);
|
|
171
|
+
expect(content).toBeInTheDocument();
|
|
172
|
+
expect(content).toHaveClass('ds_search-result__summary');
|
|
173
|
+
expect(content?.parentElement).toEqual(summary);
|
|
174
|
+
expect(content?.previousElementSibling).toEqual(mediaWrapper);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('linkComponent is used for title link', () => {
|
|
178
|
+
render(
|
|
179
|
+
<SearchResult href={RESULT_HREF} title={RESULT_TITLE} data-testid="searchresult" linkComponent={
|
|
180
|
+
({ className, ...props }) => (
|
|
181
|
+
<span role="link" className={className} {...props}/>
|
|
182
|
+
)}>
|
|
183
|
+
<SearchResult.Content>
|
|
184
|
+
{RESULT_CONTENT}
|
|
185
|
+
</SearchResult.Content>
|
|
186
|
+
</SearchResult>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const title = screen.getByRole('heading');
|
|
190
|
+
|
|
191
|
+
const link = within(title).getByRole('link');
|
|
192
|
+
expect(link).toHaveClass('ds_search-result__link');
|
|
193
|
+
expect(link).toHaveAttribute('href', RESULT_HREF);
|
|
194
|
+
expect(link.tagName).toBe('SPAN');
|
|
195
|
+
expect(link?.parentElement).toEqual(title);
|
|
196
|
+
expect(link?.previousElementSibling).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('passing additional props', () => {
|
|
200
|
+
render(
|
|
201
|
+
<SearchResult data-test="foo" data-testid="searchresult"/>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const searchResult = screen.getByTestId('searchresult');
|
|
205
|
+
expect(searchResult?.dataset.test).toEqual('foo');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('passing additional CSS classes', () => {
|
|
209
|
+
render(
|
|
210
|
+
<SearchResult className="foo" data-testid="searchresult"/>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const searchResult = screen.getByTestId('searchresult');
|
|
214
|
+
expect(searchResult).toHaveClass('foo');
|
|
215
|
+
});
|