@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.
Files changed (175) hide show
  1. package/@types/components/Accordion.d.ts +3 -2
  2. package/@types/components/ButtonGroup.d.ts +5 -0
  3. package/@types/components/PageHeader.d.ts +2 -1
  4. package/@types/components/RadioButton.d.ts +2 -2
  5. package/@types/components/SearchFacets.d.ts +18 -0
  6. package/@types/components/SearchFilters.d.ts +14 -0
  7. package/@types/components/SearchResult.d.ts +30 -0
  8. package/@types/components/SearchSort.d.ts +9 -0
  9. package/@types/components/SideNavigation.d.ts +1 -1
  10. package/CHANGELOG.md +39 -5
  11. package/dist/common/AbstractNotificationBanner.d.ts +9 -0
  12. package/dist/common/ActionLink.d.ts +5 -0
  13. package/dist/common/ConditionalWrapper.d.ts +8 -0
  14. package/dist/common/FileIcon.d.ts +6 -0
  15. package/dist/common/HintText.d.ts +5 -0
  16. package/dist/common/Icon.d.ts +6 -0
  17. package/dist/common/ScreenReaderText.d.ts +5 -0
  18. package/dist/common/WrapperTag.d.ts +8 -0
  19. package/dist/components/Accordion/Accordion.d.ts +10 -0
  20. package/dist/components/Accordion/Accordion.jsx +8 -3
  21. package/dist/components/AspectBox/AspectBox.d.ts +6 -0
  22. package/dist/components/BackToTop/BackToTop.d.ts +5 -0
  23. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +9 -0
  24. package/dist/components/Button/Button.d.ts +5 -0
  25. package/dist/components/ButtonGroup/ButtonGroup.d.ts +5 -0
  26. package/dist/components/ButtonGroup/ButtonGroup.jsx +13 -0
  27. package/dist/components/CategoryItem/CategoryItem.d.ts +5 -0
  28. package/dist/components/CategoryList/CategoryList.d.ts +6 -0
  29. package/dist/components/Checkbox/Checkbox.d.ts +5 -0
  30. package/dist/components/Checkbox/CheckboxGroup.d.ts +6 -0
  31. package/dist/components/ConfirmationMessage/ConfirmationMessage.d.ts +5 -0
  32. package/dist/components/ContentsNav/ContentsNav.d.ts +10 -0
  33. package/dist/components/CookieBanner/CookieBanner.d.ts +9 -0
  34. package/dist/components/DatePicker/DatePicker.d.ts +5 -0
  35. package/dist/components/Details/Details.d.ts +5 -0
  36. package/dist/components/ErrorMessage/ErrorMessage.d.ts +5 -0
  37. package/dist/components/ErrorSummary/ErrorSummary.d.ts +9 -0
  38. package/dist/components/FileDownload/FileDownload.d.ts +5 -0
  39. package/dist/components/HideThisPage/HideThisPage.d.ts +6 -0
  40. package/dist/components/InsetText/InsetText.d.ts +5 -0
  41. package/dist/components/NotificationBanner/NotificationBanner.d.ts +9 -0
  42. package/dist/components/NotificationPanel/NotificationPanel.d.ts +5 -0
  43. package/dist/components/PageHeader/PageHeader.d.ts +5 -0
  44. package/dist/components/PageHeader/PageHeader.jsx +2 -2
  45. package/dist/components/PageMetadata/PageMetadata.d.ts +9 -0
  46. package/dist/components/Pagination/Pagination.d.ts +7 -0
  47. package/dist/components/PhaseBanner/PhaseBanner.d.ts +5 -0
  48. package/dist/components/Question/Question.d.ts +5 -0
  49. package/dist/components/RadioButton/RadioButton.d.ts +5 -0
  50. package/dist/components/RadioButton/RadioGroup.d.ts +6 -0
  51. package/dist/components/RadioButton/RadioGroup.jsx +1 -1
  52. package/dist/components/SearchFacets/SearchFacets.d.ts +14 -0
  53. package/dist/components/SearchFacets/SearchFacets.jsx +101 -0
  54. package/dist/components/SearchFilters/SearchFilters.d.ts +16 -0
  55. package/dist/components/SearchFilters/SearchFilters.jsx +63 -0
  56. package/dist/components/SearchResult/SearchResult.d.ts +28 -0
  57. package/dist/components/SearchResult/SearchResult.jsx +93 -0
  58. package/dist/components/SearchSort/SearchSort.d.ts +10 -0
  59. package/dist/components/SearchSort/SearchSort.jsx +28 -0
  60. package/dist/components/Select/Select.d.ts +5 -0
  61. package/dist/components/SequentialNavigation/SequentialNavigation.d.ts +13 -0
  62. package/dist/components/SequentialNavigation/SequentialNavigation.jsx +0 -1
  63. package/dist/components/SideNavigation/SideNavigation.d.ts +13 -0
  64. package/dist/components/SideNavigation/SideNavigation.jsx +2 -2
  65. package/dist/components/SiteFooter/SiteFooter.d.ts +22 -0
  66. package/dist/components/SiteHeader/SiteHeader.d.ts +22 -0
  67. package/dist/components/SiteHeader/SiteHeader.jsx +0 -1
  68. package/dist/components/SiteNavigation/SiteNavigation.d.ts +9 -0
  69. package/dist/components/SiteSearch/SiteSearch.d.ts +5 -0
  70. package/dist/components/SkipLinks/SkipLinks.d.ts +10 -0
  71. package/dist/components/SkipLinks/SkipLinks.jsx +42 -0
  72. package/dist/components/SummaryCard/SummaryCard.d.ts +10 -0
  73. package/dist/components/SummaryList/SummaryList.d.ts +18 -0
  74. package/dist/components/Table/Table.d.ts +5 -0
  75. package/dist/components/Tabs/Tabs.d.ts +10 -0
  76. package/dist/components/Tag/Tag.d.ts +5 -0
  77. package/dist/components/TaskList/TaskList.d.ts +13 -0
  78. package/dist/components/TextInput/TextInput.d.ts +5 -0
  79. package/dist/components/Textarea/Textarea.d.ts +5 -0
  80. package/dist/components/WarningText/WarningText.d.ts +5 -0
  81. package/dist/hooks/useTracking.d.ts +1 -0
  82. package/dist/images/documents/audio.d.ts +4 -0
  83. package/dist/images/documents/csv.d.ts +4 -0
  84. package/dist/images/documents/excel.d.ts +4 -0
  85. package/dist/images/documents/file.d.ts +4 -0
  86. package/dist/images/documents/generic.d.ts +4 -0
  87. package/dist/images/documents/geodata.d.ts +4 -0
  88. package/dist/images/documents/ical.d.ts +4 -0
  89. package/dist/images/documents/ico.d.ts +4 -0
  90. package/dist/images/documents/image.d.ts +4 -0
  91. package/dist/images/documents/index.d.ts +22 -0
  92. package/dist/images/documents/odf.d.ts +4 -0
  93. package/dist/images/documents/odg.d.ts +4 -0
  94. package/dist/images/documents/odp.d.ts +4 -0
  95. package/dist/images/documents/ods.d.ts +4 -0
  96. package/dist/images/documents/odt.d.ts +4 -0
  97. package/dist/images/documents/pdf.d.ts +4 -0
  98. package/dist/images/documents/ppt.d.ts +4 -0
  99. package/dist/images/documents/rtf.d.ts +4 -0
  100. package/dist/images/documents/text.d.ts +4 -0
  101. package/dist/images/documents/video.d.ts +4 -0
  102. package/dist/images/documents/word.d.ts +4 -0
  103. package/dist/images/documents/xml.d.ts +4 -0
  104. package/dist/images/documents/zip.d.ts +4 -0
  105. package/dist/images/icons/arrow_upward.d.ts +4 -0
  106. package/dist/images/icons/calendar_today.d.ts +4 -0
  107. package/dist/images/icons/cancel.d.ts +4 -0
  108. package/dist/images/icons/check_circle.d.ts +4 -0
  109. package/dist/images/icons/chevron_left.d.ts +4 -0
  110. package/dist/images/icons/chevron_right.d.ts +4 -0
  111. package/dist/images/icons/close.d.ts +4 -0
  112. package/dist/images/icons/description.d.ts +4 -0
  113. package/dist/images/icons/double_chevron_left.d.ts +4 -0
  114. package/dist/images/icons/double_chevron_right.d.ts +4 -0
  115. package/dist/images/icons/error.d.ts +4 -0
  116. package/dist/images/icons/expand_less.d.ts +4 -0
  117. package/dist/images/icons/expand_more.d.ts +4 -0
  118. package/dist/images/icons/index.d.ts +17 -0
  119. package/dist/images/icons/list.d.ts +4 -0
  120. package/dist/images/icons/menu.d.ts +4 -0
  121. package/dist/images/icons/priority_high.d.ts +4 -0
  122. package/dist/images/icons/search.d.ts +4 -0
  123. package/dist/tsconfig.tsbuildinfo +1 -1
  124. package/dist/utils/context.d.ts +4 -0
  125. package/package.json +6 -6
  126. package/src/components/Accordion/Accordion.Item.stories.tsx +10 -9
  127. package/src/components/Accordion/Accordion.stories.tsx +4 -4
  128. package/src/components/Accordion/Accordion.test.tsx +48 -14
  129. package/src/components/Accordion/Accordion.tsx +12 -1
  130. package/src/components/Breadcrumbs/Breadcrumbs.Item.stories.tsx +8 -1
  131. package/src/components/Button/Button.stories.tsx +1 -1
  132. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +41 -0
  133. package/src/components/ButtonGroup/ButtonGroup.test.tsx +45 -0
  134. package/src/components/ButtonGroup/ButtonGroup.tsx +20 -0
  135. package/src/components/ContentsNav/ContentsNav.Item.stories.tsx +8 -0
  136. package/src/components/ErrorSummary/ErrorSummary.Item.stories.tsx +7 -0
  137. package/src/components/PageHeader/PageHeader.tsx +2 -1
  138. package/src/components/PageMetadata/PageMetadata.Item.stories.tsx +9 -0
  139. package/src/components/RadioButton/RadioGroup.tsx +2 -2
  140. package/src/components/SearchFacets/SearchFacets.Group.stories.tsx +56 -0
  141. package/src/components/SearchFacets/SearchFacets.Item.stories.tsx +53 -0
  142. package/src/components/SearchFacets/SearchFacets.stories.tsx +38 -0
  143. package/src/components/SearchFacets/SearchFacets.test.tsx +214 -0
  144. package/src/components/SearchFacets/SearchFacets.tsx +99 -0
  145. package/src/components/SearchFilters/SearchFilters.Panel.stories.tsx +201 -0
  146. package/src/components/SearchFilters/SearchFilters.stories.tsx +137 -0
  147. package/src/components/SearchFilters/SearchFilters.test.tsx +161 -0
  148. package/src/components/SearchFilters/SearchFilters.tsx +89 -0
  149. package/src/components/SearchResult/SearchResult.stories.tsx +111 -0
  150. package/src/components/SearchResult/SearchResult.test.tsx +215 -0
  151. package/src/components/SearchResult/SearchResult.tsx +137 -0
  152. package/src/components/SearchSort/SearchSort.stories.tsx +32 -0
  153. package/src/components/SearchSort/SearchSort.test.tsx +129 -0
  154. package/src/components/SearchSort/SearchSort.tsx +45 -0
  155. package/src/components/SequentialNavigation/SequentialNavigation.Next.stories.tsx +1 -1
  156. package/src/components/SequentialNavigation/SequentialNavigation.Previous.stories.tsx +1 -1
  157. package/src/components/SequentialNavigation/SequentialNavigation.tsx +0 -1
  158. package/src/components/SideNavigation/SideNavigation.Item.stories.tsx +9 -0
  159. package/src/components/SideNavigation/SideNavigation.List.stories.tsx +7 -0
  160. package/src/components/SideNavigation/SideNavigation.tsx +2 -1
  161. package/src/components/SiteFooter/SiteFooter.License.stories.tsx +7 -0
  162. package/src/components/SiteFooter/SiteFooter.Link.stories.tsx +9 -0
  163. package/src/components/SiteFooter/SiteFooter.Org.stories.tsx +7 -0
  164. package/src/components/SiteHeader/SiteHeader.tsx +0 -2
  165. package/src/components/SiteNavigation/SiteNavigation.Item.stories.tsx +10 -0
  166. package/src/components/SkipLinks/SkipLinks.Item.stories.tsx +11 -1
  167. package/src/components/SkipLinks/SkipLinks.tsx +10 -0
  168. package/src/components/SummaryCard/SummaryCard.Action.stories.tsx +7 -0
  169. package/src/components/SummaryCard/SummaryCard.stories.tsx +7 -0
  170. package/src/components/SummaryList/SummaryList.Item.stories.tsx +15 -0
  171. package/src/components/SummaryList/SummaryList.Value.stories.tsx +5 -2
  172. package/src/components/Tabs/Tabs.Item.stories.tsx +4 -1
  173. package/src/components/TaskList/TaskList.Group.stories.tsx +9 -0
  174. package/src/components/TaskList/TaskList.Item.stories.tsx +7 -0
  175. package/tsconfig.json +14 -14
@@ -0,0 +1,214 @@
1
+ import { test, expect, vi } from 'vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import Facets from './SearchFacets';
4
+
5
+ test('search facets boilerplate renders correctly', () => {
6
+ render(
7
+ <Facets data-testid="searchfacets"/>
8
+ );
9
+
10
+ const facets = screen.getByTestId('searchfacets');
11
+
12
+ expect(facets).toBeInTheDocument();
13
+ expect(facets).toHaveClass('ds_facets');
14
+ expect(facets.tagName).toEqual('DIV');
15
+
16
+ const status = facets.querySelector('p');
17
+ expect(status).toBeInTheDocument();
18
+ expect(status).toHaveClass('visually-hidden');
19
+ expect(status?.textContent).toEqual('There are 0 search filters applied');
20
+ expect(status?.parentElement).toEqual(facets);
21
+
22
+ const list = facets.querySelector('dl');
23
+ expect(list).toBeInTheDocument();
24
+ expect(list).toHaveClass('ds_facets__list');
25
+ expect(list?.parentElement).toEqual(facets);
26
+ expect(list?.previousElementSibling).toEqual(status);
27
+
28
+ // clear button is not present if there are no facets
29
+ const clearButton = within(facets).queryByRole('button', { name: /clear all filters/i });
30
+ expect(clearButton).not.toBeInTheDocument();
31
+ });
32
+
33
+ test('search facets count with one facet', () => {
34
+ render(
35
+ <Facets data-testid="searchfacets">
36
+ <Facets.Item title="Facet 1" />
37
+ </Facets>
38
+ );
39
+
40
+ const facets = screen.getByTestId('searchfacets');
41
+ const status = within(facets).getByText('There is 1 search filter applied');
42
+ expect(status).toBeInTheDocument();
43
+
44
+ const clearButton = within(facets).getByRole('button', { name: /clear all filters/i });
45
+ expect(clearButton).toBeInTheDocument();
46
+ });
47
+
48
+ test('search facets count with multiple facets', () => {
49
+ render(
50
+ <Facets data-testid="searchfacets">
51
+ <Facets.Item title="Facet 1" />
52
+ <Facets.Item title="Facet 2" />
53
+ </Facets>
54
+ );
55
+
56
+ const facets = screen.getByTestId('searchfacets');
57
+ const facetItems = facets.querySelectorAll('.ds_facet');
58
+ const status = within(facets).getByText(`There are ${facetItems.length} search filters applied`);
59
+ expect(status).toBeInTheDocument();
60
+
61
+ const clearButton = within(facets).getByRole('button', { name: /clear all filters/i });
62
+ expect(clearButton).toBeInTheDocument();
63
+ });
64
+
65
+ test('search filters count with facet groups', () => {
66
+ render(
67
+ <Facets data-testid="searchfacets">
68
+ <Facets.Group title="Group 1">
69
+ <Facets.Item title="Facet 1" />
70
+ <Facets.Item title="Facet 2" />
71
+ </Facets.Group>
72
+ <Facets.Group title="Group 2">
73
+ <Facets.Item title="Facet 3" />
74
+ </Facets.Group>
75
+ </Facets>
76
+ );
77
+
78
+ const facets = screen.getByTestId('searchfacets');
79
+ const facetItems = facets.querySelectorAll('.ds_facet');
80
+ const status = within(facets).getByText(`There are ${facetItems.length} search filters applied`);
81
+ expect(status).toBeInTheDocument();
82
+
83
+ const clearButton = within(facets).getByRole('button', { name: /clear all filters/i });
84
+ expect(clearButton).toBeInTheDocument();
85
+ });
86
+
87
+ test('search facet renders correctly', () => {
88
+ const FACET_TITLE = 'Facet 1';
89
+
90
+ render(
91
+ <Facets.Item title={FACET_TITLE} data-testid="searchfacet" />
92
+ );
93
+
94
+ const facetWrapper = screen.getByTestId('searchfacet');
95
+ expect(facetWrapper).toBeInTheDocument();
96
+ expect(facetWrapper).toHaveClass('ds_facet-wrapper');
97
+ expect(facetWrapper.tagName).toEqual('DD');
98
+
99
+ const facetButton = within(facetWrapper).getByRole('button');
100
+ expect(facetButton).toHaveClass('ds_facet__button');
101
+ expect(facetButton).toHaveAttribute('aria-label', `Remove '${FACET_TITLE}' filter`);
102
+
103
+ const facet = facetButton.parentElement;
104
+ expect(facet).toHaveClass('ds_facet');
105
+ expect(facet?.textContent).toEqual(FACET_TITLE);
106
+
107
+ const facetIcon = within(facetButton).getByRole('img', { hidden: true });
108
+ expect(facetIcon).toHaveClass('ds_facet__button-icon');
109
+ expect(facetIcon).toHaveAttribute('aria-hidden', 'true');
110
+ });
111
+
112
+ test('search facet with accessible name', () => {
113
+ const FACET_TITLE = 'Facet 1';
114
+ const ACCESSIBLE_NAME = 'Custom facet name';
115
+
116
+ render(
117
+ <Facets.Item title={FACET_TITLE} accessibleName={ACCESSIBLE_NAME} data-testid="searchfacet" />
118
+ );
119
+
120
+ const facetWrapper = screen.getByTestId('searchfacet');
121
+ const facetButton = within(facetWrapper).getByRole('button');
122
+ expect(facetButton).toHaveAttribute('aria-label', `Remove '${ACCESSIBLE_NAME}' filter`);
123
+
124
+ const facet = facetButton.parentElement;
125
+ expect(facet).toHaveClass('ds_facet');
126
+ expect(facet?.textContent).toEqual(FACET_TITLE);
127
+ });
128
+
129
+ test('facet button onClick works', async () => {
130
+ const FACET_TITLE = 'Facet 1';
131
+ const handleClick = vi.fn();
132
+
133
+ render(
134
+ <Facets.Item title={FACET_TITLE} onClick={handleClick} data-testid="searchfacet" />
135
+ );
136
+
137
+ const facetWrapper = screen.getByTestId('searchfacet');
138
+ const facetButton = within(facetWrapper).getByRole('button');
139
+
140
+ await facetButton.click();
141
+ expect(handleClick).toHaveBeenCalledTimes(1);
142
+ });
143
+
144
+ test('facet group renders correctly', () => {
145
+ const GROUP_TITLE = 'Group 1';
146
+
147
+ render(
148
+ <Facets.Group title={GROUP_TITLE} data-testid="searchfacetgroup">
149
+ <Facets.Item title="Facet 1" />
150
+ <Facets.Item title="Facet 2" />
151
+ </Facets.Group>
152
+ );
153
+
154
+ const facetGroup = screen.getByTestId('searchfacetgroup');
155
+ expect(facetGroup).toBeInTheDocument();
156
+ expect(facetGroup).toHaveClass('ds_facet-group');
157
+ expect(facetGroup.tagName).toEqual('DIV');
158
+
159
+ const groupTitle = within(facetGroup).getByText(`${GROUP_TITLE}:`);
160
+ expect(groupTitle).toBeInTheDocument();
161
+ expect(groupTitle).toHaveClass('ds_facet__group-title');
162
+
163
+ const facetWrappers = facetGroup.querySelectorAll('.ds_facet-wrapper');
164
+ expect(facetWrappers.length).toEqual(2);
165
+
166
+ const firstFacet = facetWrappers[0];
167
+ expect(firstFacet?.textContent).toContain('Facet 1');
168
+ expect(firstFacet?.textContent).not.toContain('or');
169
+
170
+ const secondFacet = facetWrappers[1];
171
+ expect(secondFacet?.textContent).toContain('Facet 2');
172
+ expect(secondFacet?.textContent).toContain('or');
173
+ });
174
+
175
+ test('facet group with custom join content', () => {
176
+ const GROUP_TITLE = 'Group 1';
177
+ const JOIN_CONTENT = 'and';
178
+
179
+ render(
180
+ <Facets.Group title={GROUP_TITLE} joinContent={JOIN_CONTENT} data-testid="searchfacetgroup">
181
+ <Facets.Item title="Facet 1" />
182
+ <Facets.Item title="Facet 2" />
183
+ </Facets.Group>
184
+ );
185
+
186
+ const facetGroup = screen.getByTestId('searchfacetgroup');
187
+ const facetWrappers = facetGroup.querySelectorAll('.ds_facet-wrapper');
188
+
189
+ const firstFacet = facetWrappers[0];
190
+ expect(firstFacet?.textContent).toContain('Facet 1');
191
+ expect(firstFacet?.textContent).not.toContain(JOIN_CONTENT);
192
+
193
+ const secondFacet = facetWrappers[1];
194
+ expect(secondFacet?.textContent).toContain('Facet 2');
195
+ expect(secondFacet?.textContent).toContain(JOIN_CONTENT);
196
+ });
197
+
198
+ test('passing additional props', () => {
199
+ render(
200
+ <Facets data-test="foo" data-testid="searchfacets"/>
201
+ );
202
+
203
+ const searchFacets = screen.getByTestId('searchfacets');
204
+ expect(searchFacets?.dataset.test).toEqual('foo');
205
+ });
206
+
207
+ test('passing additional CSS classes', () => {
208
+ render(
209
+ <Facets className="foo" data-testid="searchfacets"/>
210
+ );
211
+
212
+ const searchFacets = screen.getByTestId('searchfacets');
213
+ expect(searchFacets).toHaveClass('foo');
214
+ });
@@ -0,0 +1,99 @@
1
+ import React, { Children } from 'react';
2
+ import Icon from "../../common/Icon";
3
+ import { Cancel } from '../../../src/images/icons';
4
+
5
+ const FacetsItem = ({
6
+ accessibleName,
7
+ joinContent,
8
+ onClick,
9
+ title,
10
+ ...props
11
+ }: SGDS.Component.SearchFacets.Item) => {
12
+ accessibleName = accessibleName ? accessibleName : title;
13
+
14
+ return (
15
+ <dd className="ds_facet-wrapper" {...props}>
16
+ {joinContent &&
17
+ <span aria-hidden="true">{joinContent}</span>
18
+ }
19
+ <span className="ds_facet">
20
+ {title}
21
+ <button type="button" onClick={onClick} aria-label={`Remove '${accessibleName}' filter`} className="ds_facet__button">
22
+ <Icon className="ds_facet__button-icon" aria-hidden="true" role="img" icon="Cancel"/>
23
+ </button>
24
+ </span>
25
+ </dd>
26
+ );
27
+ }
28
+
29
+ const FacetsGroup = ({
30
+ children,
31
+ joinContent = 'or',
32
+ title,
33
+ ...props
34
+ }: SGDS.Component.SearchFacets.Group) => {
35
+ return (
36
+ <div className="ds_facet-group" {...props}>
37
+ <dt className="ds_facet__group-title">
38
+ {title.trim()}:
39
+ </dt>
40
+ {
41
+ Children.map(children, (child, index) => {
42
+ const thisChild = child as React.ReactElement<SGDS.Component.SearchFacets.Item>
43
+ return React.cloneElement(thisChild, { joinContent: index > 0 ? joinContent : undefined, key: 'facet' + index });
44
+ })
45
+ }
46
+ </div>
47
+ )
48
+ }
49
+
50
+ const Facets = ({
51
+ children,
52
+ className,
53
+ ...props
54
+ }: SGDS.Component.SearchFacets) => {
55
+ let facetCount = 0;
56
+
57
+ function processChild(item: any) {
58
+ if (item.type.displayName === 'Facets.Item') {
59
+ facetCount = facetCount + 1;
60
+
61
+ } else if (item.type.displayName === 'Facets.Group') {
62
+ Children.forEach(item.props.children, child => {
63
+ processChild(child);
64
+ });
65
+ }
66
+ }
67
+
68
+ Children.forEach(children, child => {
69
+ processChild(child);
70
+ });
71
+
72
+ return (
73
+ <div className={[
74
+ "ds_facets",
75
+ className
76
+ ].join(' ')}
77
+ {...props}
78
+ >
79
+ <p className="visually-hidden">There {facetCount === 1 ? 'is' : 'are'} {facetCount} search {facetCount === 1 ? 'filter' : 'filters'} applied</p>
80
+ <dl className="ds_facets__list">
81
+ {children}
82
+ </dl>
83
+ {facetCount > 0 &&
84
+ <button className="ds_button ds_button--secondary ds_button--has-icon ds_facets__clear-button" type="button">
85
+ Clear all filters
86
+ <Cancel className="ds_facet__button-icon"/>
87
+ </button>
88
+ }
89
+ </div>
90
+ );
91
+ }
92
+
93
+ Facets.displayName = 'Facets';
94
+ FacetsItem.displayName = 'Facets.Item';
95
+ FacetsGroup.displayName = 'Facets.Group';
96
+ Facets.Item = FacetsItem;
97
+ Facets.Group = FacetsGroup;
98
+
99
+ export default Facets;
@@ -0,0 +1,201 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import argTypes from '../../../.storybook/sgdsArgTypes';
3
+
4
+ import Filters from './SearchFilters';
5
+ import DatePicker from '../DatePicker/DatePicker';
6
+ import Checkbox from '../Checkbox/Checkbox';
7
+
8
+ const meta = {
9
+ title: 'Components/Search results/Filters/Filter panel',
10
+ component: Filters.Panel,
11
+ decorators: [(Story) => (
12
+ <div className="ds_accordion ds_accordion--small ds_!_margin-top--0">
13
+ <Story />
14
+ </div>
15
+ )],
16
+ argTypes: {
17
+ children: argTypes.children(),
18
+ isScrollable: {
19
+ description: 'Puts internal scrollbars around the filter fields',
20
+ control: 'boolean',
21
+ },
22
+ legend: {
23
+ description: 'Content for the (visually hidden) legend element',
24
+ type: {
25
+ name: 'string',
26
+ required: true
27
+ }
28
+ },
29
+ heading: {
30
+ description: 'Heading of the filter panel item',
31
+ type: {
32
+ name: 'string',
33
+ required: true
34
+ }
35
+ },
36
+ activeFilterCount: {
37
+ description: 'Number of active filter fields in this panel',
38
+ type: {
39
+ name: 'number'
40
+ }
41
+ }
42
+ }
43
+ } satisfies Meta<typeof Filters.Panel>;
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof meta>;
47
+
48
+ const CONTENT_TYPES = [
49
+ {
50
+ label: 'Advice and guidance',
51
+ value: 'advice-and-guidance'
52
+ },
53
+ {
54
+ label: 'Agreement',
55
+ value: 'agreement'
56
+ },
57
+ {
58
+ label: 'Consultation analysis',
59
+ value: 'consultation-analysis'
60
+ },
61
+ {
62
+ label: 'Consultation paper',
63
+ value: 'consultation-paper'
64
+ },
65
+ {
66
+ label: 'Corporate report',
67
+ value: 'corporate-report'
68
+ },
69
+ {
70
+ label: 'Correspondence',
71
+ value: 'correspondence'
72
+ },
73
+ {
74
+ label: 'FOI/EIR release',
75
+ value: 'foi-eir-release'
76
+ },
77
+ {
78
+ label: 'Factsheet',
79
+ value: 'factsheet'
80
+ },
81
+ {
82
+ label: 'Form',
83
+ value: 'form'
84
+ },
85
+ {
86
+ label: 'Impact assessment',
87
+ value: 'impact-assessment'
88
+ },
89
+ {
90
+ label: 'Independent report',
91
+ value: 'independent-report'
92
+ },
93
+ {
94
+ label: 'Map',
95
+ value: 'map'
96
+ },
97
+ {
98
+ label: 'Minutes',
99
+ value: 'minutes'
100
+ },
101
+ {
102
+ label: 'Progress report',
103
+ value: 'progress-report'
104
+ },
105
+ {
106
+ label: 'Regulation/directive/order',
107
+ value: 'regulation-directive-order'
108
+ },
109
+ {
110
+ label: 'Research and analysis',
111
+ value: 'research-and-analysis'
112
+ },
113
+ {
114
+ label: 'Speech/statement',
115
+ value: 'speech-statement'
116
+ },
117
+ {
118
+ label: 'Statistics',
119
+ value: 'statistics'
120
+ },
121
+ {
122
+ label: 'Strategy/plan',
123
+ value: 'strategy-plan'
124
+ },
125
+ {
126
+ label: 'Transparency data',
127
+ value: 'transparency-data'
128
+ }
129
+ ];
130
+
131
+ const CONTENT_TYPES_WITH_SELECTED = JSON.parse(JSON.stringify(CONTENT_TYPES));
132
+ CONTENT_TYPES_WITH_SELECTED[1].checked = true;
133
+ CONTENT_TYPES_WITH_SELECTED[4].checked = true;
134
+ CONTENT_TYPES_WITH_SELECTED[7].checked = true;
135
+
136
+ export const Default: Story = {
137
+ render: (args: any) => (
138
+ <Filters.Panel
139
+ heading="Filter by date"
140
+ legend="Filter by date"
141
+ >
142
+ <DatePicker
143
+ hintText="For example, 21/01/2022"
144
+ id="date-from"
145
+ label="Updated after"
146
+ />
147
+
148
+ <DatePicker
149
+ hintText="For example, 21/01/2022"
150
+ id="date-to"
151
+ label="Updated before"
152
+ />
153
+ </Filters.Panel>
154
+ )
155
+ };
156
+
157
+ export const Scrollable: Story = {
158
+ render: (args: any) => (
159
+ <Filters.Panel
160
+ heading="Content type"
161
+ isScrollable
162
+ legend="Select which publication types you would like to see"
163
+ {...args}
164
+ >
165
+ <Filters.CheckboxGroup>
166
+ {CONTENT_TYPES.map((type) => (
167
+ <Checkbox
168
+ key={type.value}
169
+ label={type.label}
170
+ value={type.value}
171
+ id={type.value}
172
+ />
173
+ ))}
174
+ </Filters.CheckboxGroup>
175
+ </Filters.Panel>
176
+ )
177
+ };
178
+
179
+ export const WithActiveFilterCount: Story = {
180
+ render: (args: any) => (
181
+ <Filters.Panel
182
+ activeFilterCount={CONTENT_TYPES_WITH_SELECTED.filter((item: any) => item.checked).length}
183
+ heading="Content type"
184
+ isScrollable
185
+ legend="Select which publication types you would like to see"
186
+ {...args}
187
+ >
188
+ <Filters.CheckboxGroup>
189
+ {CONTENT_TYPES_WITH_SELECTED.map((type: any) => (
190
+ <Checkbox
191
+ checked={type.checked || false}
192
+ key={type.value}
193
+ label={type.label}
194
+ value={type.value}
195
+ id={type.value}
196
+ />
197
+ ))}
198
+ </Filters.CheckboxGroup>
199
+ </Filters.Panel>
200
+ )
201
+ };
@@ -0,0 +1,137 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import argTypes from '../../../.storybook/sgdsArgTypes';
3
+
4
+ import Filters from './SearchFilters';
5
+ import DatePicker from '../DatePicker/DatePicker';
6
+ import Checkbox from '../Checkbox/Checkbox';
7
+
8
+
9
+ const meta = {
10
+ title: 'Components/Search results/Filters',
11
+ component: Filters,
12
+ argTypes: {
13
+ children: argTypes.children()
14
+ },
15
+ args: {
16
+ title: 'Content type'
17
+ }
18
+ } satisfies Meta<typeof Filters>;
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ const CONTENT_TYPES = [
24
+ {
25
+ label: 'Advice and guidance',
26
+ value: 'advice-and-guidance'
27
+ },
28
+ {
29
+ label: 'Agreement',
30
+ value: 'agreement'
31
+ },
32
+ {
33
+ label: 'Consultation analysis',
34
+ value: 'consultation-analysis'
35
+ },
36
+ {
37
+ label: 'Consultation paper',
38
+ value: 'consultation-paper'
39
+ },
40
+ {
41
+ label: 'Corporate report',
42
+ value: 'corporate-report'
43
+ },
44
+ {
45
+ label: 'Correspondence',
46
+ value: 'correspondence'
47
+ },
48
+ {
49
+ label: 'FOI/EIR release',
50
+ value: 'foi-eir-release'
51
+ },
52
+ {
53
+ label: 'Factsheet',
54
+ value: 'factsheet'
55
+ },
56
+ {
57
+ label: 'Form',
58
+ value: 'form'
59
+ },
60
+ {
61
+ label: 'Impact assessment',
62
+ value: 'impact-assessment'
63
+ },
64
+ {
65
+ label: 'Independent report',
66
+ value: 'independent-report'
67
+ },
68
+ {
69
+ label: 'Map',
70
+ value: 'map'
71
+ },
72
+ {
73
+ label: 'Minutes',
74
+ value: 'minutes'
75
+ },
76
+ {
77
+ label: 'Progress report',
78
+ value: 'progress-report'
79
+ },
80
+ {
81
+ label: 'Regulation/directive/order',
82
+ value: 'regulation-directive-order'
83
+ },
84
+ {
85
+ label: 'Research and analysis',
86
+ value: 'research-and-analysis'
87
+ },
88
+ {
89
+ label: 'Speech/statement',
90
+ value: 'speech-statement'
91
+ },
92
+ {
93
+ label: 'Statistics',
94
+ value: 'statistics'
95
+ },
96
+ {
97
+ label: 'Strategy/plan',
98
+ value: 'strategy-plan'
99
+ },
100
+ {
101
+ label: 'Transparency data',
102
+ value: 'transparency-data'
103
+ }
104
+ ];
105
+
106
+ export const Default: Story = {
107
+ render: (args: any) => (
108
+ <Filters {...args}>
109
+ <Filters.Panel legend="Select which publication types you would like to see" heading="Content type">
110
+ <Filters.CheckboxGroup>
111
+ {CONTENT_TYPES.map((type) => (
112
+ <Checkbox
113
+ key={type.value}
114
+ label={type.label}
115
+ value={type.value}
116
+ id={type.value}
117
+ />
118
+ ))}
119
+ </Filters.CheckboxGroup>
120
+ </Filters.Panel>
121
+
122
+ <Filters.Panel heading="Filter by date" legend="Filter by date">
123
+ <DatePicker
124
+ hintText="For example, 21/01/2022"
125
+ id="date-from"
126
+ label="Updated after"
127
+ />
128
+
129
+ <DatePicker
130
+ hintText="For example, 21/01/2022"
131
+ id="date-to"
132
+ label="Updated before"
133
+ />
134
+ </Filters.Panel>
135
+ </Filters>
136
+ )
137
+ };