@scottish-government/designsystem-react 0.1.1 → 0.1.3
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/.svgrrc +15 -0
- package/@types/common/Icon.d.ts +3 -5
- package/@types/components/Button.d.ts +1 -1
- package/@types/components/Pagination.d.ts +20 -0
- package/@types/components/TextInput.d.ts +1 -1
- package/@types/sgds.d.ts +1 -0
- package/README.md +29 -0
- package/dist/common/icon.jsx +48 -5
- package/dist/components/back-to-top/back-to-top.jsx +1 -1
- package/dist/components/confirmation-message/confirmation-message.jsx +1 -1
- package/dist/components/date-picker/date-picker.jsx +2 -0
- package/dist/components/notification-banner/notification-banner.jsx +4 -5
- package/dist/components/pagination/pagination.jsx +93 -0
- package/dist/components/select/select.jsx +1 -1
- package/dist/components/site-search/site-search.jsx +1 -1
- package/dist/components/text-input/text-input.jsx +1 -1
- package/dist/components/textarea/textarea.jsx +1 -1
- package/dist/icons/ArrowUpward.jsx +41 -0
- package/dist/icons/CalendarToday.jsx +41 -0
- package/dist/icons/Cancel.jsx +40 -0
- package/dist/icons/CheckCircle.jsx +41 -0
- package/dist/icons/ChevronLeft.jsx +41 -0
- package/dist/icons/ChevronRight.jsx +41 -0
- package/dist/icons/Close.jsx +41 -0
- package/dist/icons/Description.jsx +41 -0
- package/dist/icons/DoubleChevronLeft.jsx +41 -0
- package/dist/icons/DoubleChevronRight.jsx +41 -0
- package/dist/icons/Error.jsx +41 -0
- package/dist/icons/ExpandLess.jsx +41 -0
- package/dist/icons/ExpandMore.jsx +41 -0
- package/dist/icons/List.jsx +44 -0
- package/dist/icons/Menu.jsx +41 -0
- package/dist/icons/PriorityHigh.jsx +42 -0
- package/dist/icons/Search.jsx +41 -0
- package/dist/icons/index.js +40 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
- package/src/common/icon.test.tsx +5 -26
- package/src/common/icon.tsx +25 -14
- package/src/components/back-to-top/back-to-top.tsx +1 -1
- package/src/components/button/button.test.tsx +3 -3
- package/src/components/confirmation-message/confirmation-message.tsx +1 -1
- package/src/components/date-picker/date-picker.test.tsx +21 -0
- package/src/components/date-picker/date-picker.tsx +2 -0
- package/src/components/notification-banner/notification-banner.tsx +5 -5
- package/src/components/pagination/pagination.test.tsx +353 -0
- package/src/components/pagination/pagination.tsx +152 -0
- package/src/components/phase-banner/phase-banner.test.tsx +1 -1
- package/src/components/select/select.test.tsx +1 -0
- package/src/components/select/select.tsx +1 -0
- package/src/components/site-search/site-search.tsx +1 -1
- package/src/components/text-input/text-input.test.tsx +5 -4
- package/src/components/text-input/text-input.tsx +1 -0
- package/src/components/textarea/textarea.test.tsx +1 -0
- package/src/components/textarea/textarea.tsx +1 -0
- package/src/icons/ArrowUpward.tsx +15 -0
- package/src/icons/CalendarToday.tsx +15 -0
- package/src/icons/Cancel.tsx +13 -0
- package/src/icons/CheckCircle.tsx +15 -0
- package/src/icons/ChevronLeft.tsx +15 -0
- package/src/icons/ChevronRight.tsx +15 -0
- package/src/icons/Close.tsx +15 -0
- package/src/icons/Description.tsx +15 -0
- package/src/icons/DoubleChevronLeft.tsx +19 -0
- package/src/icons/DoubleChevronRight.tsx +19 -0
- package/src/icons/Error.tsx +15 -0
- package/src/icons/ExpandLess.tsx +15 -0
- package/src/icons/ExpandMore.tsx +15 -0
- package/src/icons/List.tsx +18 -0
- package/src/icons/Menu.tsx +15 -0
- package/src/icons/PriorityHigh.tsx +16 -0
- package/src/icons/Search.tsx +15 -0
- package/src/icons/index.ts +17 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, within, fireEvent } from '@testing-library/react';
|
|
3
|
+
import Pagination, { Page, Ellipsis } from './pagination';
|
|
4
|
+
|
|
5
|
+
const pageAriaLabel = 'Page 1';
|
|
6
|
+
const pageHref = '#foo';
|
|
7
|
+
const pageText = '1';
|
|
8
|
+
const currentPage = 10;
|
|
9
|
+
const totalPages = 21;
|
|
10
|
+
|
|
11
|
+
test('pagination page renders correctly', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Page
|
|
14
|
+
ariaLabel={pageAriaLabel}
|
|
15
|
+
href={pageHref}
|
|
16
|
+
text={pageText}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const item = screen.getByRole('listitem');
|
|
21
|
+
const link = within(item).getByRole('link');
|
|
22
|
+
const span = within(link).getByText(pageText);
|
|
23
|
+
|
|
24
|
+
expect(item).toHaveClass('ds_pagination__item');
|
|
25
|
+
expect(link).toHaveClass('ds_pagination__link');
|
|
26
|
+
expect(link).toHaveAttribute('aria-label', pageAriaLabel);
|
|
27
|
+
expect(link).toHaveAttribute('href', pageHref);
|
|
28
|
+
expect(span).toHaveClass('ds_pagination__link-label');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('current pagination page', () => {
|
|
32
|
+
render(
|
|
33
|
+
<Page
|
|
34
|
+
ariaLabel={pageAriaLabel}
|
|
35
|
+
href={pageHref}
|
|
36
|
+
text={pageText}
|
|
37
|
+
current
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const item = screen.getByRole('listitem');
|
|
42
|
+
const link = within(item).getByRole('link');
|
|
43
|
+
|
|
44
|
+
expect(link).toHaveClass('ds_current');
|
|
45
|
+
expect(link).toHaveAttribute('aria-current', 'page');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('pagination page with click event', () => {
|
|
49
|
+
const onClickFn = vi.fn();
|
|
50
|
+
|
|
51
|
+
render(
|
|
52
|
+
<Page
|
|
53
|
+
ariaLabel={pageAriaLabel}
|
|
54
|
+
href={pageHref}
|
|
55
|
+
text={pageText}
|
|
56
|
+
onClick={onClickFn}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const item = screen.getByRole('listitem');
|
|
61
|
+
const link = within(item).getByRole('link');
|
|
62
|
+
|
|
63
|
+
fireEvent.click(link);
|
|
64
|
+
|
|
65
|
+
expect(onClickFn).toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('Ellipsis item renders correctly', () => {
|
|
69
|
+
render(
|
|
70
|
+
<Ellipsis/>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const item = screen.getByRole('listitem', {hidden: true});
|
|
74
|
+
const link = within(item).getByText('…');
|
|
75
|
+
|
|
76
|
+
expect(item).toHaveClass('ds_pagination__item');
|
|
77
|
+
expect(item).toHaveAttribute('aria-hidden', 'true');
|
|
78
|
+
expect(link).toHaveClass('ds_pagination__link', 'ds_pagination__link--ellipsis');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('pagination renders correctly', () => {
|
|
82
|
+
const currentPage = 10;
|
|
83
|
+
const totalPages = 21;
|
|
84
|
+
|
|
85
|
+
render(
|
|
86
|
+
<Pagination page={currentPage} totalPages={totalPages} />
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const paginationNav = screen.getByRole('navigation');
|
|
90
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
91
|
+
const prevLabel = within(paginationList).getByText('Previous');
|
|
92
|
+
const prevLink = prevLabel.parentNode;
|
|
93
|
+
const prevIcon = prevLabel.previousSibling;
|
|
94
|
+
const prevItem = prevLink?.parentNode;
|
|
95
|
+
const nextLabel = within(paginationList).getByText('Next');
|
|
96
|
+
const nextLink = nextLabel.parentNode;
|
|
97
|
+
const nextIcon = nextLabel.nextSibling;
|
|
98
|
+
const nextItem = nextLink?.parentNode;
|
|
99
|
+
|
|
100
|
+
const firstPageLabel = within(paginationList).getByText('1');
|
|
101
|
+
const firstPageLink = firstPageLabel.parentNode;
|
|
102
|
+
const firstPageItem = firstPageLink?.parentNode;
|
|
103
|
+
const lastPageLabel = within(paginationList).getByText(totalPages);
|
|
104
|
+
const lastPageLink = lastPageLabel.parentNode;
|
|
105
|
+
const lastPageItem = lastPageLink?.parentNode;
|
|
106
|
+
|
|
107
|
+
const firstPageEllipsisItem = firstPageItem?.nextSibling;
|
|
108
|
+
const firstPageEllipsis = firstPageEllipsisItem?.children[0];
|
|
109
|
+
const lastPageEllipsisItem = lastPageItem?.previousSibling;
|
|
110
|
+
const lastPageEllipsis = lastPageEllipsisItem?.children[0];
|
|
111
|
+
|
|
112
|
+
const currentPageLink = document.querySelector('.ds_current');
|
|
113
|
+
const currentPageItem = currentPageLink?.parentNode;
|
|
114
|
+
|
|
115
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
116
|
+
|
|
117
|
+
expect(paginationNav).toHaveClass('ds_pagination');
|
|
118
|
+
expect(paginationNav).toHaveAttribute('aria-label', 'Pages');
|
|
119
|
+
|
|
120
|
+
expect(paginationList).toHaveClass('ds_pagination__list');
|
|
121
|
+
expect(paginationList.tagName).toEqual('UL');
|
|
122
|
+
|
|
123
|
+
expect(prevItem).toHaveClass('ds_pagination__item');
|
|
124
|
+
expect(prevItem?.tagName).toEqual('LI');
|
|
125
|
+
expect(prevItem?.parentNode).toEqual(paginationList);
|
|
126
|
+
expect(prevLink).toHaveClass('ds_pagination__link', 'ds_pagination__link--text', 'ds_pagination__link--icon')
|
|
127
|
+
expect(prevLink).toHaveAttribute('aria-label', 'Previous page');
|
|
128
|
+
expect(prevLink).toHaveAttribute('href', `/search?page=${currentPage - 1}`);
|
|
129
|
+
expect(prevLink?.tagName).toEqual('A');
|
|
130
|
+
expect(prevIcon).toHaveClass('ds_icon');
|
|
131
|
+
expect(prevIcon).toHaveAttribute('aria-hidden', 'true')
|
|
132
|
+
expect(prevIcon?.tagName).toEqual('svg');
|
|
133
|
+
expect(prevLabel).toHaveClass('ds_pagination__link-label');
|
|
134
|
+
expect(prevLabel?.tagName).toEqual('SPAN');
|
|
135
|
+
|
|
136
|
+
expect(nextItem).toHaveClass('ds_pagination__item');
|
|
137
|
+
expect(nextItem?.tagName).toEqual('LI');
|
|
138
|
+
expect(nextItem?.parentNode).toEqual(paginationList);
|
|
139
|
+
expect(nextLink).toHaveClass('ds_pagination__link', 'ds_pagination__link--text', 'ds_pagination__link--icon')
|
|
140
|
+
expect(nextLink).toHaveAttribute('aria-label', 'Next page');
|
|
141
|
+
expect(nextLink).toHaveAttribute('href', `/search?page=${currentPage + 1}`);
|
|
142
|
+
expect(nextLink?.tagName).toEqual('A');
|
|
143
|
+
expect(nextIcon).toHaveClass('ds_icon');
|
|
144
|
+
expect(nextIcon).toHaveAttribute('aria-hidden', 'true')
|
|
145
|
+
expect(nextIcon?.tagName).toEqual('svg');
|
|
146
|
+
expect(nextLabel).toHaveClass('ds_pagination__link-label');
|
|
147
|
+
expect(nextLabel?.tagName).toEqual('SPAN');
|
|
148
|
+
|
|
149
|
+
expect(firstPageItem).toHaveClass('ds_pagination__item');
|
|
150
|
+
expect(firstPageItem?.tagName).toEqual('LI');
|
|
151
|
+
expect(firstPageItem?.parentNode).toEqual(paginationList);
|
|
152
|
+
expect(firstPageLink).toHaveClass('ds_pagination__link')
|
|
153
|
+
expect(firstPageLink).toHaveAttribute('aria-label', 'Page 1');
|
|
154
|
+
expect(firstPageLink).toHaveAttribute('href', `/search?page=1`);
|
|
155
|
+
expect(firstPageLink?.tagName).toEqual('A');
|
|
156
|
+
expect(firstPageLabel).toHaveClass('ds_pagination__link-label');
|
|
157
|
+
expect(firstPageLabel?.tagName).toEqual('SPAN');
|
|
158
|
+
|
|
159
|
+
expect(lastPageItem).toHaveClass('ds_pagination__item');
|
|
160
|
+
expect(lastPageItem?.tagName).toEqual('LI');
|
|
161
|
+
expect(lastPageItem?.parentNode).toEqual(paginationList);
|
|
162
|
+
expect(lastPageLink).toHaveClass('ds_pagination__link')
|
|
163
|
+
expect(lastPageLink).toHaveAttribute('aria-label', `Page ${totalPages}`);
|
|
164
|
+
expect(lastPageLink).toHaveAttribute('href', `/search?page=${totalPages}`);
|
|
165
|
+
expect(lastPageLink?.tagName).toEqual('A');
|
|
166
|
+
expect(lastPageLabel).toHaveClass('ds_pagination__link-label');
|
|
167
|
+
expect(lastPageLabel?.tagName).toEqual('SPAN');
|
|
168
|
+
|
|
169
|
+
// dev note: by this point I'd started to wonder if it would just be better to do a single string compare of the rendered output versus the expected output
|
|
170
|
+
|
|
171
|
+
expect(firstPageEllipsisItem).toHaveClass('ds_pagination__item');
|
|
172
|
+
expect(firstPageEllipsisItem).toHaveAttribute('aria-hidden', 'true');
|
|
173
|
+
expect(firstPageEllipsisItem.tagName).toEqual('LI');
|
|
174
|
+
expect(firstPageEllipsis).toHaveClass('ds_pagination__link', 'ds_pagination__link--ellipsis');
|
|
175
|
+
expect(firstPageEllipsis.tagName).toEqual('SPAN');
|
|
176
|
+
expect(firstPageEllipsis.textContent).toEqual('…');
|
|
177
|
+
|
|
178
|
+
expect(lastPageEllipsisItem).toHaveClass('ds_pagination__item');
|
|
179
|
+
expect(lastPageEllipsisItem).toHaveAttribute('aria-hidden', 'true');
|
|
180
|
+
expect(lastPageEllipsisItem.tagName).toEqual('LI');
|
|
181
|
+
expect(lastPageEllipsis).toHaveClass('ds_pagination__link', 'ds_pagination__link--ellipsis');
|
|
182
|
+
expect(lastPageEllipsis.tagName).toEqual('SPAN');
|
|
183
|
+
expect(lastPageEllipsis.textContent).toEqual('…');
|
|
184
|
+
|
|
185
|
+
expect(currentPageItem).toHaveClass('ds_pagination__item');
|
|
186
|
+
expect(currentPageLink).toHaveClass('ds_pagination__link', 'ds_current');
|
|
187
|
+
expect(currentPageLink.textContent).toEqual(currentPage.toString());
|
|
188
|
+
|
|
189
|
+
// expect one link either side of the current (default padding)
|
|
190
|
+
expect(currentPageItem.previousSibling.querySelector('a')).toHaveAttribute('aria-label', 'Page 9');
|
|
191
|
+
expect(currentPageItem.previousSibling.previousSibling.querySelector('a')).toBeNull();
|
|
192
|
+
|
|
193
|
+
expect(currentPageItem.nextSibling.querySelector('a')).toHaveAttribute('aria-label', 'Page 11');
|
|
194
|
+
expect(currentPageItem.nextSibling.nextSibling.querySelector('a')).toBeNull();
|
|
195
|
+
|
|
196
|
+
// 9 is: previous, first, ellipsis, current page and 1 padding either side, ellipsis, last, next
|
|
197
|
+
expect(paginationItems.length).toEqual(9);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('pagination with 2 padding', () => {
|
|
201
|
+
const padding = 2;
|
|
202
|
+
|
|
203
|
+
render(
|
|
204
|
+
<Pagination
|
|
205
|
+
page={currentPage}
|
|
206
|
+
totalPages={totalPages}
|
|
207
|
+
padding={padding}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const paginationNav = screen.getByRole('navigation');
|
|
212
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
213
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
214
|
+
|
|
215
|
+
// 11 is: previous, first, ellipsis, current page and 2 padding either side, ellipsis, last, next
|
|
216
|
+
expect(paginationItems.length).toEqual(11);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('pagination with custom aria label', () => {
|
|
220
|
+
const ariaLabel = 'My label';
|
|
221
|
+
|
|
222
|
+
render(
|
|
223
|
+
<Pagination
|
|
224
|
+
page={currentPage}
|
|
225
|
+
totalPages={totalPages}
|
|
226
|
+
ariaLabel={ariaLabel}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const paginationNav = screen.getByRole('navigation');
|
|
231
|
+
|
|
232
|
+
expect(paginationNav).toHaveAttribute('aria-label', ariaLabel);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('pagination passes onclick event to child links', () => {
|
|
236
|
+
const onClickFn = vi.fn();
|
|
237
|
+
|
|
238
|
+
render(
|
|
239
|
+
<Pagination
|
|
240
|
+
page={currentPage}
|
|
241
|
+
totalPages={totalPages}
|
|
242
|
+
onClick={onClickFn}
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const paginationNav = screen.getByRole('navigation');
|
|
247
|
+
|
|
248
|
+
// pick an arbitrary link
|
|
249
|
+
const link = [].slice.call(document.querySelectorAll('.ds_pagination__link'))[4];
|
|
250
|
+
link.setAttribute('href', '#foo');
|
|
251
|
+
|
|
252
|
+
fireEvent.click(link);
|
|
253
|
+
|
|
254
|
+
expect(onClickFn).toHaveBeenCalled();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('pagination modifies an input pattern for its link format', () => {
|
|
258
|
+
render(
|
|
259
|
+
<Pagination
|
|
260
|
+
page={currentPage}
|
|
261
|
+
totalPages={totalPages}
|
|
262
|
+
pattern='My/Link/Format?Page=$1#foo'
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// pick an arbitrary link
|
|
267
|
+
const link = document.querySelector('.ds_pagination__link.ds_current');
|
|
268
|
+
expect(link).toHaveAttribute('href', 'My/Link/Format?Page=10#foo');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
test('pagination at an early link in the list', () => {
|
|
273
|
+
render(
|
|
274
|
+
<Pagination
|
|
275
|
+
page="1"
|
|
276
|
+
totalPages={totalPages}
|
|
277
|
+
/>
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const paginationNav = screen.getByRole('navigation');
|
|
281
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
282
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
283
|
+
|
|
284
|
+
// 7 is: current page and 3 subsequent items (padding plus 2), ellipsis, last, next
|
|
285
|
+
expect(paginationItems.length).toEqual(7);
|
|
286
|
+
expect(paginationNav.textContent).toEqual('1234…21Next');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('pagination at an early link in the list, increased padding', () => {
|
|
290
|
+
render(
|
|
291
|
+
<Pagination
|
|
292
|
+
padding={2}
|
|
293
|
+
page="1"
|
|
294
|
+
totalPages={totalPages}
|
|
295
|
+
/>
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const paginationNav = screen.getByRole('navigation');
|
|
299
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
300
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
301
|
+
|
|
302
|
+
// 8 is: current page and 4 subsequent items (padding plus 2), ellipsis, last, next
|
|
303
|
+
expect(paginationItems.length).toEqual(8);
|
|
304
|
+
expect(paginationNav.textContent).toEqual('12345…21Next');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('pagination at late link in the list', () => {
|
|
308
|
+
render(
|
|
309
|
+
<Pagination
|
|
310
|
+
page={totalPages}
|
|
311
|
+
totalPages={totalPages}
|
|
312
|
+
/>
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const paginationNav = screen.getByRole('navigation');
|
|
316
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
317
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
318
|
+
|
|
319
|
+
// 7 is: current page and 3 subsequent items (padding plus 2), ellipsis, last, next
|
|
320
|
+
expect(paginationItems.length).toEqual(7);
|
|
321
|
+
expect(paginationNav.textContent).toEqual('Previous1…18192021');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('pagination at late link in the list, increased padding', () => {
|
|
325
|
+
render(
|
|
326
|
+
<Pagination
|
|
327
|
+
padding={2}
|
|
328
|
+
page={totalPages}
|
|
329
|
+
totalPages={totalPages}
|
|
330
|
+
/>
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const paginationNav = screen.getByRole('navigation');
|
|
334
|
+
const paginationList = within(paginationNav).getByRole('list');
|
|
335
|
+
const paginationItems = within(paginationList).getAllByRole('listitem', { hidden: true });
|
|
336
|
+
|
|
337
|
+
// 8 is: current page and 4 subsequent items (padding plus 2), ellipsis, last, next
|
|
338
|
+
expect(paginationItems.length).toEqual(8);
|
|
339
|
+
expect(paginationNav.textContent).toEqual('Previous1…1718192021');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('passing additional props', () => {
|
|
343
|
+
render(
|
|
344
|
+
<Pagination
|
|
345
|
+
page={currentPage}
|
|
346
|
+
totalPages={totalPages}
|
|
347
|
+
data-test="foo"
|
|
348
|
+
/>
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const paginationNav = screen.getByRole('navigation');
|
|
352
|
+
expect(paginationNav?.dataset.test).toEqual('foo');
|
|
353
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import Icon from "../../common/icon";
|
|
2
|
+
|
|
3
|
+
export const Page: React.FC<SGDS.Component.Pagination.Page> = ({
|
|
4
|
+
ariaLabel,
|
|
5
|
+
current = false,
|
|
6
|
+
href,
|
|
7
|
+
onClick,
|
|
8
|
+
text
|
|
9
|
+
}) => {
|
|
10
|
+
function handleClick(event: React.MouseEvent) {
|
|
11
|
+
if (typeof onClick === 'function') {
|
|
12
|
+
onClick(event);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<li className="ds_pagination__item">
|
|
18
|
+
<a aria-label={ariaLabel}
|
|
19
|
+
className={[
|
|
20
|
+
'ds_pagination__link',
|
|
21
|
+
current ? 'ds_current' : undefined
|
|
22
|
+
].join(' ')}
|
|
23
|
+
href={href}
|
|
24
|
+
aria-current={current ? "page" : undefined}
|
|
25
|
+
onClick={handleClick}
|
|
26
|
+
>
|
|
27
|
+
<span className="ds_pagination__link-label">{text}</span>
|
|
28
|
+
</a>
|
|
29
|
+
</li>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Ellipsis = () => {
|
|
34
|
+
return (
|
|
35
|
+
<li className="ds_pagination__item" aria-hidden="true">
|
|
36
|
+
<span className="ds_pagination__link ds_pagination__link--ellipsis">…</span>
|
|
37
|
+
</li>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const Pagination: React.FC<SGDS.Component.Pagination> = ({
|
|
42
|
+
ariaLabel = 'Pages',
|
|
43
|
+
onClick,
|
|
44
|
+
padding = 1,
|
|
45
|
+
page = 1,
|
|
46
|
+
pattern = '/search?page=$1',
|
|
47
|
+
totalPages,
|
|
48
|
+
...props
|
|
49
|
+
}) => {
|
|
50
|
+
const minToShow = padding + 2;
|
|
51
|
+
let includeFirst, includeLast;
|
|
52
|
+
let pages = [];
|
|
53
|
+
|
|
54
|
+
page = Number(page);
|
|
55
|
+
|
|
56
|
+
if (page <= minToShow) {
|
|
57
|
+
for (let i = 1; i <= minToShow + 1; i++) {
|
|
58
|
+
pages.push(Number(i));
|
|
59
|
+
}
|
|
60
|
+
} else if (page <= totalPages - minToShow) {
|
|
61
|
+
pages = [page];
|
|
62
|
+
for (let i = 0; i < padding; i++) {
|
|
63
|
+
pages.unshift(page - 1 - i);
|
|
64
|
+
pages.push(Number(page) + 1 + i);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
for (let i = totalPages; i > totalPages - minToShow - 1; i--) {
|
|
68
|
+
pages.push(Number(i));
|
|
69
|
+
}
|
|
70
|
+
pages.reverse();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// filter out pages that are out of bounds
|
|
74
|
+
pages = pages.filter(item => item > 0 && item <= totalPages);
|
|
75
|
+
|
|
76
|
+
if ((page - padding) > 2) {
|
|
77
|
+
includeFirst = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((page + padding < totalPages - 1)) {
|
|
81
|
+
includeLast = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<nav className="ds_pagination"
|
|
86
|
+
aria-label={ariaLabel}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
<ul className="ds_pagination__list">
|
|
90
|
+
{page > 1 && (
|
|
91
|
+
<li className="ds_pagination__item">
|
|
92
|
+
<a aria-label="Previous page" className="ds_pagination__link ds_pagination__link--text ds_pagination__link--icon" href={pattern.replace('$1', String(page - 1))} data-search="pagination-previous" onClick={onClick}>
|
|
93
|
+
<Icon icon="ChevronLeft" />
|
|
94
|
+
<span className="ds_pagination__link-label">Previous</span>
|
|
95
|
+
</a>
|
|
96
|
+
</li>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{includeFirst && (
|
|
100
|
+
<>
|
|
101
|
+
<Page
|
|
102
|
+
ariaLabel="Page 1"
|
|
103
|
+
href={pattern.replace('$1', String(1))}
|
|
104
|
+
onClick={onClick}
|
|
105
|
+
text="1"
|
|
106
|
+
/>
|
|
107
|
+
<Ellipsis/>
|
|
108
|
+
</>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{pages && pages.map((item, index: number) => (
|
|
112
|
+
<Page
|
|
113
|
+
ariaLabel={`Page ${item}`}
|
|
114
|
+
current={item === page}
|
|
115
|
+
href={pattern.replace('$1', String(item))}
|
|
116
|
+
key={`pagination${index}`}
|
|
117
|
+
onClick={onClick}
|
|
118
|
+
pattern={pattern}
|
|
119
|
+
text={item.toString()}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
|
|
123
|
+
{includeLast && (
|
|
124
|
+
<>
|
|
125
|
+
<Ellipsis/>
|
|
126
|
+
<Page
|
|
127
|
+
ariaLabel={`Page ${totalPages}`}
|
|
128
|
+
href={pattern.replace('$1', String(totalPages))}
|
|
129
|
+
onClick={onClick}
|
|
130
|
+
pattern={pattern}
|
|
131
|
+
text={totalPages.toString()}
|
|
132
|
+
/>
|
|
133
|
+
</>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{page < totalPages && (
|
|
137
|
+
<li className="ds_pagination__item">
|
|
138
|
+
<a aria-label="Next page" className="ds_pagination__link ds_pagination__link--text ds_pagination__link--icon" href={pattern.replace('$1', String(page + 1))} data-search="pagination-next" onClick={onClick}>
|
|
139
|
+
<span className="ds_pagination__link-label">Next</span>
|
|
140
|
+
<Icon icon="ChevronRight" />
|
|
141
|
+
</a>
|
|
142
|
+
</li>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
</ul>
|
|
146
|
+
</nav>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Pagination.displayName = 'Pagination';
|
|
151
|
+
|
|
152
|
+
export default Pagination;
|
|
@@ -5,7 +5,7 @@ import PhaseBanner from './phase-banner';
|
|
|
5
5
|
const text = 'This is a new service. Your feedback will help us to improve it.';
|
|
6
6
|
const defaultText = 'This is a new service';
|
|
7
7
|
|
|
8
|
-
test('
|
|
8
|
+
test('phase banner renders correctly', () => {
|
|
9
9
|
render(
|
|
10
10
|
<PhaseBanner>
|
|
11
11
|
{text}
|
|
@@ -188,6 +188,7 @@ test('select with error message', () => {
|
|
|
188
188
|
|
|
189
189
|
expect(selectWrapper).toHaveClass('ds_input--error')
|
|
190
190
|
expect(select).toHaveAttribute('aria-describedby', errorMessageElement.id);
|
|
191
|
+
expect(select).toHaveAttribute('aria-invalid', 'true');
|
|
191
192
|
expect(errorMessageElement).toBeInTheDocument();
|
|
192
193
|
expect(errorMessageElement).toHaveClass('ds_question__error-message');
|
|
193
194
|
});
|
|
@@ -81,7 +81,7 @@ const SiteSearch: React.FC<SGDS.Component.SiteSearch> = function ({
|
|
|
81
81
|
type="search"
|
|
82
82
|
/>
|
|
83
83
|
|
|
84
|
-
<Button type="submit" icon="
|
|
84
|
+
<Button type="submit" icon="Search" iconOnly>Search</Button>
|
|
85
85
|
|
|
86
86
|
{hasAutocomplete && (
|
|
87
87
|
<div id="autocomplete-suggestions" className="ds_autocomplete__suggestions">
|
|
@@ -133,13 +133,13 @@ test('text input with custom currency symbol', () => {
|
|
|
133
133
|
|
|
134
134
|
test('text input with button', () => {
|
|
135
135
|
const buttonText = 'Search';
|
|
136
|
-
const buttonIcon = '
|
|
136
|
+
const buttonIcon = 'Search';
|
|
137
137
|
render(
|
|
138
138
|
<TextInput
|
|
139
139
|
id={id}
|
|
140
140
|
label={labelText}
|
|
141
|
-
buttonIcon=
|
|
142
|
-
buttonText=
|
|
141
|
+
buttonIcon={buttonIcon}
|
|
142
|
+
buttonText={buttonText}
|
|
143
143
|
hasButton
|
|
144
144
|
/>
|
|
145
145
|
);
|
|
@@ -159,7 +159,7 @@ test('text input with button', () => {
|
|
|
159
159
|
expect(buttonTextElement).toHaveClass('visually-hidden');
|
|
160
160
|
expect(buttonTextElement.tagName).toEqual('SPAN');
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
expect(buttonIconElement).toBeInTheDocument();
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
test('text input with hint text', () => {
|
|
@@ -289,6 +289,7 @@ test('text input with error message', () => {
|
|
|
289
289
|
|
|
290
290
|
expect(textInput).toHaveClass('ds_input--error')
|
|
291
291
|
expect(textInput).toHaveAttribute('aria-describedby', errorMessageElement.id);
|
|
292
|
+
expect(textInput).toHaveAttribute('aria-invalid', 'true');
|
|
292
293
|
expect(errorMessageElement).toBeInTheDocument();
|
|
293
294
|
expect(errorMessageElement).toHaveClass('ds_question__error-message');
|
|
294
295
|
});
|
|
@@ -194,6 +194,7 @@ test('textarea with error message', () => {
|
|
|
194
194
|
|
|
195
195
|
expect(textarea).toHaveClass('ds_input--error')
|
|
196
196
|
expect(textarea).toHaveAttribute('aria-describedby', errorMessageElement.id);
|
|
197
|
+
expect(textarea).toHaveAttribute('aria-invalid', 'true');
|
|
197
198
|
expect(errorMessageElement).toBeInTheDocument();
|
|
198
199
|
expect(errorMessageElement).toHaveClass('ds_question__error-message');
|
|
199
200
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
const SvgArrowUpward = (props: SVGProps<SVGSVGElement>) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
fill="#000000"
|
|
8
|
+
role="img"
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
<path d="M0 0h24v24H0V0z" fill="none" />
|
|
12
|
+
<path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
export default SvgArrowUpward;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
const SvgCalendarToday = (props: SVGProps<SVGSVGElement>) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
fill="#000000"
|
|
8
|
+
role="img"
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
<path d="M0 0h24v24H0z" fill="none" />
|
|
12
|
+
<path d="M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
export default SvgCalendarToday;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
const SvgCancel = (props: SVGProps<SVGSVGElement>) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 -960 960 960"
|
|
7
|
+
role="img"
|
|
8
|
+
{...props}
|
|
9
|
+
>
|
|
10
|
+
<path d="m336-280 144-144 144 144 56-56-144-144 144-144-56-56-144 144-144-144-56 56 144 144-144 144 56 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z" />
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
export default SvgCancel;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
const SvgCheckCircle = (props: SVGProps<SVGSVGElement>) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
fill="#000000"
|
|
8
|
+
role="img"
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
<path d="M0 0h24v24H0z" fill="none" />
|
|
12
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
export default SvgCheckCircle;
|