@scottish-government/designsystem-react 0.7.1 → 0.8.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/@types/common/AbstractNotificationBanner.d.ts +2 -2
  2. package/@types/common/ActionLink.d.ts +8 -0
  3. package/@types/common/FileIcon.d.ts +1 -1
  4. package/@types/common/Icon.d.ts +1 -1
  5. package/@types/components/Accordion.d.ts +0 -1
  6. package/@types/components/Breadcrumbs.d.ts +2 -5
  7. package/@types/components/Checkbox.d.ts +0 -2
  8. package/@types/components/ConfirmationMessage.d.ts +1 -1
  9. package/@types/components/ContentsNav.d.ts +4 -6
  10. package/@types/components/DatePicker.d.ts +1 -1
  11. package/@types/components/ErrorSummary.d.ts +3 -4
  12. package/@types/components/NotificationPanel.d.ts +1 -1
  13. package/@types/components/Pagination.d.ts +5 -4
  14. package/@types/components/PhaseBanner.d.ts +0 -1
  15. package/@types/components/Question.d.ts +1 -1
  16. package/@types/components/RadioButton.d.ts +0 -1
  17. package/@types/components/Select.d.ts +0 -7
  18. package/@types/components/SequentialNavigation.d.ts +4 -4
  19. package/@types/components/SideNavigation.d.ts +4 -5
  20. package/@types/components/SiteFooter.d.ts +25 -0
  21. package/@types/components/SiteHeader.d.ts +10 -3
  22. package/@types/components/SiteNavigation.d.ts +2 -3
  23. package/@types/components/SkipLinks.d.ts +3 -4
  24. package/@types/components/SummaryCard.d.ts +0 -2
  25. package/@types/components/SummaryList.d.ts +0 -13
  26. package/@types/components/Tabs.d.ts +0 -1
  27. package/@types/components/Tag.d.ts +1 -3
  28. package/@types/components/TaskList.d.ts +1 -0
  29. package/@types/sgds.d.ts +13 -2
  30. package/CHANGELOG.md +63 -1
  31. package/dist/common/AbstractNotificationBanner.jsx +8 -6
  32. package/dist/common/ActionLink.jsx +19 -0
  33. package/dist/common/FileIcon.jsx +2 -7
  34. package/dist/common/Icon.jsx +3 -9
  35. package/dist/components/Accordion/Accordion.jsx +12 -7
  36. package/dist/components/Breadcrumbs/Breadcrumbs.jsx +20 -15
  37. package/dist/components/Checkbox/Checkbox.jsx +4 -29
  38. package/dist/components/Checkbox/CheckboxGroup.jsx +63 -0
  39. package/dist/components/ContentsNav/ContentsNav.jsx +27 -16
  40. package/dist/components/CookieBanner/CookieBanner.jsx +1 -0
  41. package/dist/components/DatePicker/DatePicker.jsx +5 -5
  42. package/dist/components/ErrorSummary/ErrorSummary.jsx +28 -18
  43. package/dist/components/NotificationBanner/NotificationBanner.jsx +2 -2
  44. package/dist/components/Pagination/Pagination.jsx +42 -22
  45. package/dist/components/PhaseBanner/PhaseBanner.jsx +3 -3
  46. package/dist/components/Question/Question.jsx +3 -3
  47. package/dist/components/RadioButton/RadioButton.jsx +7 -17
  48. package/dist/components/RadioButton/RadioGroup.jsx +21 -0
  49. package/dist/components/Select/Select.jsx +4 -7
  50. package/dist/components/SequentialNavigation/SequentialNavigation.jsx +31 -18
  51. package/dist/components/SideNavigation/SideNavigation.jsx +17 -16
  52. package/dist/components/SiteFooter/SiteFooter.jsx +104 -0
  53. package/dist/components/SiteHeader/SiteHeader.jsx +113 -32
  54. package/dist/components/SiteNavigation/SiteNavigation.jsx +20 -7
  55. package/dist/components/SkipLinks/SkipLinks.jsx +10 -10
  56. package/dist/components/SummaryCard/SummaryCard.jsx +25 -14
  57. package/dist/components/SummaryList/SummaryList.jsx +65 -47
  58. package/dist/components/Tabs/Tabs.jsx +6 -6
  59. package/dist/components/Tag/Tag.jsx +2 -2
  60. package/dist/components/TaskList/TaskList.jsx +14 -3
  61. package/dist/components/TextInput/TextInput.jsx +3 -3
  62. package/dist/components/Textarea/Textarea.jsx +3 -3
  63. package/dist/hooks/useTracking.js +21 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/utils/context.js +5 -0
  66. package/package.json +2 -2
  67. package/src/common/AbstractNotificationBanner.test.tsx +1 -1
  68. package/src/common/AbstractNotificationBanner.tsx +14 -13
  69. package/src/common/ActionLink.test.tsx +80 -0
  70. package/src/common/ActionLink.tsx +27 -0
  71. package/src/common/ConditionalWrapper.tsx +1 -1
  72. package/src/common/FileIcon.tsx +7 -11
  73. package/src/common/HintText.tsx +2 -2
  74. package/src/common/Icon.tsx +13 -17
  75. package/src/common/ScreenReaderText.tsx +2 -2
  76. package/src/common/WrapperTag.tsx +2 -2
  77. package/src/components/Accordion/Accordion.test.tsx +17 -4
  78. package/src/components/Accordion/Accordion.tsx +19 -14
  79. package/src/components/AspectBox/AspectBox.tsx +2 -2
  80. package/src/components/BackToTop/BackToTop.tsx +2 -2
  81. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +79 -48
  82. package/src/components/Breadcrumbs/Breadcrumbs.tsx +31 -31
  83. package/src/components/Button/Button.tsx +2 -2
  84. package/src/components/Checkbox/Checkbox.test.tsx +1 -96
  85. package/src/components/Checkbox/Checkbox.tsx +8 -55
  86. package/src/components/Checkbox/CheckboxGroup.test.tsx +37 -0
  87. package/src/components/Checkbox/CheckboxGroup.tsx +41 -0
  88. package/src/components/ConfirmationMessage/ConfirmationMessage.tsx +2 -2
  89. package/src/components/ContentsNav/ContentsNav.test.tsx +40 -51
  90. package/src/components/ContentsNav/ContentsNav.tsx +32 -25
  91. package/src/components/CookieBanner/CookieBanner.tsx +3 -3
  92. package/src/components/DatePicker/DatePicker.test.tsx +1 -1
  93. package/src/components/DatePicker/DatePicker.tsx +7 -7
  94. package/src/components/Details/Details.tsx +2 -2
  95. package/src/components/ErrorMessage/ErrorMessage.tsx +2 -2
  96. package/src/components/ErrorSummary/ErrorSummary.test.tsx +40 -34
  97. package/src/components/ErrorSummary/ErrorSummary.tsx +40 -32
  98. package/src/components/FileDownload/FileDownload.tsx +2 -2
  99. package/src/components/HideThisPage/HideThisPage.tsx +2 -2
  100. package/src/components/InsetText/InsetText.tsx +2 -2
  101. package/src/components/NotificationBanner/NotificationBanner.tsx +6 -7
  102. package/src/components/NotificationPanel/NotificationPanel.tsx +2 -2
  103. package/src/components/PageHeader/PageHeader.tsx +2 -2
  104. package/src/components/PageMetadata/PageMetadata.tsx +4 -5
  105. package/src/components/Pagination/Pagination.test.tsx +26 -7
  106. package/src/components/Pagination/Pagination.tsx +70 -36
  107. package/src/components/PhaseBanner/PhaseBanner.tsx +4 -5
  108. package/src/components/Question/Question.test.tsx +1 -1
  109. package/src/components/Question/Question.tsx +5 -5
  110. package/src/components/RadioButton/RadioButton.test.tsx +7 -126
  111. package/src/components/RadioButton/RadioButton.tsx +10 -41
  112. package/src/components/RadioButton/RadioGroup.test.tsx +65 -0
  113. package/src/components/RadioButton/RadioGroup.tsx +31 -0
  114. package/src/components/Select/Select.test.tsx +39 -37
  115. package/src/components/Select/Select.tsx +7 -22
  116. package/src/components/SequentialNavigation/SequentialNavigation.test.tsx +32 -21
  117. package/src/components/SequentialNavigation/SequentialNavigation.tsx +52 -30
  118. package/src/components/SideNavigation/SideNavigation.test.tsx +39 -85
  119. package/src/components/SideNavigation/SideNavigation.tsx +27 -29
  120. package/src/components/SiteFooter/SiteFooter.test.tsx +153 -0
  121. package/src/components/SiteFooter/SiteFooter.tsx +107 -0
  122. package/src/components/SiteHeader/SiteHeader.test.tsx +87 -79
  123. package/src/components/SiteHeader/SiteHeader.tsx +103 -40
  124. package/src/components/SiteNavigation/SiteNavigation.test.tsx +42 -23
  125. package/src/components/SiteNavigation/SiteNavigation.tsx +28 -16
  126. package/src/components/SiteSearch/SiteSearch.tsx +2 -2
  127. package/src/components/SkipLinks/SkipLinks.test.tsx +22 -10
  128. package/src/components/SkipLinks/SkipLinks.tsx +16 -15
  129. package/src/components/SummaryCard/SummaryCard.test.tsx +31 -35
  130. package/src/components/SummaryCard/SummaryCard.tsx +39 -28
  131. package/src/components/SummaryList/SummaryList.test.tsx +49 -148
  132. package/src/components/SummaryList/SummaryList.tsx +54 -92
  133. package/src/components/Table/Table.tsx +2 -2
  134. package/src/components/Tabs/Tabs.tsx +14 -15
  135. package/src/components/Tag/Tag.test.tsx +4 -4
  136. package/src/components/Tag/Tag.tsx +4 -4
  137. package/src/components/TaskList/TaskList.test.tsx +26 -0
  138. package/src/components/TaskList/TaskList.tsx +21 -11
  139. package/src/components/TextInput/TextInput.test.tsx +1 -1
  140. package/src/components/TextInput/TextInput.tsx +5 -5
  141. package/src/components/Textarea/Textarea.test.tsx +1 -1
  142. package/src/components/Textarea/Textarea.tsx +5 -5
  143. package/src/components/WarningText/WarningText.tsx +2 -2
  144. package/src/hooks/useTracking.test.tsx +64 -0
  145. package/src/hooks/useTracking.ts +19 -0
  146. package/src/utils/context.ts +3 -0
  147. package/tsconfig.json +1 -1
@@ -4,28 +4,20 @@ import Select from './Select';
4
4
 
5
5
  const SELECT_ID = 'select-component';
6
6
  const LABEL_TEXT = 'choose a component';
7
- const OPTIONS = [
8
- {
9
- text: 'Accordion',
10
- value: 'accordion'
11
- },
12
- {
13
- text: 'Breadcrumbs',
14
- value: 'breadcrumbs'
15
- },
16
- {
17
- text: 'Button',
18
- value: 'button'
19
- }
20
- ];
7
+ const OPTIONS = <>
8
+ <option value="accordion">Accordion</option>
9
+ <option value="breadcrumbs">Breadcrumbs</option>
10
+ <option value="button">Button</option>
11
+ </>;
21
12
 
22
13
  test('select renders correctly', () => {
23
14
  render(
24
15
  <Select
25
16
  id={SELECT_ID}
26
17
  label={LABEL_TEXT}
27
- options={OPTIONS}
28
- />
18
+ >
19
+ {OPTIONS}
20
+ </Select>
29
21
  );
30
22
 
31
23
  const select = screen.getByRole('combobox');
@@ -55,9 +47,10 @@ test('select with width', () => {
55
47
  <Select
56
48
  id={SELECT_ID}
57
49
  label={LABEL_TEXT}
58
- options={OPTIONS}
59
50
  width={SELECTWIDTH}
60
- />
51
+ >
52
+ {OPTIONS}
53
+ </Select>
61
54
  );
62
55
 
63
56
  const selectWrapper = screen.getByRole('combobox').parentElement;
@@ -71,9 +64,10 @@ test('select with hint text', () => {
71
64
  <Select
72
65
  id={SELECT_ID}
73
66
  label={LABEL_TEXT}
74
- options={OPTIONS}
75
67
  hintText={HINT_TEXT}
76
- />
68
+ >
69
+ {OPTIONS}
70
+ </Select>
77
71
  );
78
72
 
79
73
  const hintTextEl = screen.getByText(HINT_TEXT);
@@ -90,9 +84,10 @@ test('select with custom name', () => {
90
84
  <Select
91
85
  id={SELECT_ID}
92
86
  label={LABEL_TEXT}
93
- options={OPTIONS}
94
87
  name={SELECT_NAME}
95
- />
88
+ >
89
+ {OPTIONS}
90
+ </Select>
96
91
  );
97
92
 
98
93
  const select = screen.getByRole('combobox');
@@ -106,9 +101,10 @@ test('select with blur function', () => {
106
101
  <Select
107
102
  id={SELECT_ID}
108
103
  label={LABEL_TEXT}
109
- options={OPTIONS}
110
104
  onBlur={ONBLUR_FUNCTION}
111
- />
105
+ >
106
+ {OPTIONS}
107
+ </Select>
112
108
  );
113
109
 
114
110
  const select = screen.getByRole('combobox');
@@ -125,9 +121,10 @@ test('select with change function', () => {
125
121
  <Select
126
122
  id={SELECT_ID}
127
123
  label={LABEL_TEXT}
128
- options={OPTIONS}
129
124
  onChange={ONCHANGE_FUNCTION}
130
- />
125
+ >
126
+ {OPTIONS}
127
+ </Select>
131
128
  );
132
129
 
133
130
  const select = screen.getByRole('combobox');
@@ -144,9 +141,10 @@ test('select with placeholder option', () => {
144
141
  <Select
145
142
  id={SELECT_ID}
146
143
  label={LABEL_TEXT}
147
- options={OPTIONS}
148
144
  placeholder={PLACEHOLDER_TEXT}
149
- />
145
+ >
146
+ {OPTIONS}
147
+ </Select>
150
148
  );
151
149
 
152
150
  const select = screen.getByRole('combobox');
@@ -162,9 +160,10 @@ test('select with initial value', () => {
162
160
  <Select
163
161
  id={SELECT_ID}
164
162
  label={LABEL_TEXT}
165
- options={OPTIONS}
166
163
  defaultValue={INITIAL_VALUE}
167
- />
164
+ >
165
+ {OPTIONS}
166
+ </Select>
168
167
  );
169
168
 
170
169
  const select = screen.getByRole('combobox');
@@ -179,10 +178,11 @@ test('select with error message', () => {
179
178
  <Select
180
179
  id={SELECT_ID}
181
180
  label={LABEL_TEXT}
182
- options={OPTIONS}
183
- error
184
181
  errorMessage={ERROR_MESSAGE_TEXT}
185
- />
182
+ hasError
183
+ >
184
+ {OPTIONS}
185
+ </Select>
186
186
  );
187
187
 
188
188
  const select = screen.getByRole('combobox');
@@ -201,9 +201,10 @@ test('passing additional props', () => {
201
201
  <Select
202
202
  id={SELECT_ID}
203
203
  label={LABEL_TEXT}
204
- options={OPTIONS}
205
204
  data-test="foo"
206
- />
205
+ >
206
+ {OPTIONS}
207
+ </Select>
207
208
  );
208
209
 
209
210
  const select = screen.getByRole('combobox');
@@ -216,9 +217,10 @@ test('passing additional CSS classes', () => {
216
217
  <Select
217
218
  id={SELECT_ID}
218
219
  label={LABEL_TEXT}
219
- options={OPTIONS}
220
220
  className="foo"
221
- />
221
+ >
222
+ {OPTIONS}
223
+ </Select>
222
224
  );
223
225
 
224
226
  const select = screen.getByRole('combobox');
@@ -1,31 +1,22 @@
1
1
  import ErrorMessage from '../ErrorMessage/ErrorMessage';
2
2
  import HintText from '../../common/HintText';
3
3
 
4
- const Option: React.FC<SGDS.Component.Select.Option> = function ({
5
- text,
6
- value
7
- }) {
8
- return (
9
- <option value={value}>{text}</option>
10
- );
11
- };
12
-
13
- const Select: React.FC<SGDS.Component.Select> = function ({
4
+ const Select = function ({
5
+ children,
14
6
  className,
15
7
  defaultValue,
16
- error,
17
8
  errorMessage,
9
+ hasError,
18
10
  hintText,
19
11
  id,
20
12
  label,
21
13
  name,
22
14
  onBlur,
23
15
  onChange,
24
- options,
25
16
  placeholder,
26
17
  width,
27
18
  ...props
28
- }) {
19
+ }: SGDS.Component.Select) {
29
20
  const errorMessageId = `error-message-${id}`;
30
21
  const hintTextId = `hint-text-${id}`;
31
22
  const describedbys: string[] = [];
@@ -53,7 +44,7 @@ const Select: React.FC<SGDS.Component.Select> = function ({
53
44
  <div
54
45
  className={[
55
46
  "ds_select-wrapper",
56
- error && 'ds_input--error',
47
+ hasError && 'ds_input--error',
57
48
  width && `ds_input--${width}`,
58
49
  className
59
50
  ].join(' ')}
@@ -61,7 +52,7 @@ const Select: React.FC<SGDS.Component.Select> = function ({
61
52
  >
62
53
  <select
63
54
  aria-describedby={describedbys.join(' ')}
64
- aria-invalid={error}
55
+ aria-invalid={hasError}
65
56
  className="ds_select"
66
57
  defaultValue={defaultValue}
67
58
  id={id}
@@ -70,13 +61,7 @@ const Select: React.FC<SGDS.Component.Select> = function ({
70
61
  onChange={handleChange}
71
62
  >
72
63
  <option value="">{placeholder}</option>
73
- {options && options.map((option, index: number) => (
74
- <Option
75
- value={option.value}
76
- text={option.text}
77
- key={`option-${index}`}
78
- />
79
- ))}
64
+ {children}
80
65
  </select>
81
66
  <span className="ds_select-arrow" aria-hidden="true"></span>
82
67
  </div>
@@ -2,18 +2,23 @@ import { test, expect } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import SequentialNavigation from './SequentialNavigation';
4
4
 
5
- const NEXT_LINK_OBJECT = { title: 'Apply for or renew a Blue Badge?', href: '#prev' }
5
+ const NEXT_LINK_OBJECT = { title: 'Apply for or renew a Blue Badge?', href: '#next' }
6
6
  const PREVIOUS_LINK_OBJECT = { title: 'Apply for or renew a Blue Badge?', href: '#prev' }
7
7
 
8
8
  test('sequential navigation renders correctly', () => {
9
9
  render(
10
- <SequentialNavigation
11
- next={NEXT_LINK_OBJECT}
12
- previous={PREVIOUS_LINK_OBJECT}
13
- />
10
+ <SequentialNavigation>
11
+ <SequentialNavigation.Previous href={PREVIOUS_LINK_OBJECT.href}>
12
+ {PREVIOUS_LINK_OBJECT.title}
13
+ </SequentialNavigation.Previous>
14
+ <SequentialNavigation.Next href={NEXT_LINK_OBJECT.href}>
15
+ {NEXT_LINK_OBJECT.title}
16
+ </SequentialNavigation.Next>
17
+ </SequentialNavigation>
14
18
  );
15
19
 
16
20
  const sequentialNavigation = screen.getByRole('navigation');
21
+
17
22
  const prevLink = screen.getAllByRole('link')[0];
18
23
  const prevLinkWrapper = prevLink.parentElement;
19
24
  const nextLink = screen.getAllByRole('link')[1];
@@ -34,18 +39,14 @@ test('sequential navigation renders correctly', () => {
34
39
  expect(nextLink.textContent).toEqual(NEXT_LINK_OBJECT.title);
35
40
  expect(nextLinkWrapper).toHaveClass('ds_sequential-nav__item', 'ds_sequential-nav__item--next');
36
41
  expect(nextLinkWrapper?.tagName).toEqual('DIV');
37
- expect(nextLink.childNodes[0]).toHaveAttribute('data-label', 'Next')
42
+ expect(nextLink.childNodes[0]).toHaveAttribute('data-label', 'Next');
38
43
  });
39
44
 
40
45
  test('with custom aria label', () => {
41
46
  const ARIA_LABEL = 'My label';
42
47
 
43
48
  render(
44
- <SequentialNavigation
45
- ariaLabel={ARIA_LABEL}
46
- next={NEXT_LINK_OBJECT}
47
- previous={PREVIOUS_LINK_OBJECT}
48
- />
49
+ <SequentialNavigation aria-label={ARIA_LABEL}/>
49
50
  );
50
51
 
51
52
  const sequentialNavigation = screen.getByRole('navigation');
@@ -53,13 +54,27 @@ test('with custom aria label', () => {
53
54
  expect(sequentialNavigation).toHaveAttribute('aria-label', ARIA_LABEL);
54
55
  });
55
56
 
57
+ test('sequential nav links with custom element', () => {
58
+ render(
59
+ <SequentialNavigation>
60
+ <SequentialNavigation.Previous href={PREVIOUS_LINK_OBJECT.href} linkComponent={
61
+ ({ className, ...props }) => (
62
+ <span role="link" className={className} {...props}/>
63
+ )}>
64
+ {PREVIOUS_LINK_OBJECT.title}
65
+ </SequentialNavigation.Previous>
66
+ </SequentialNavigation>
67
+ );
68
+
69
+ const link = screen.queryByRole('link');
70
+
71
+ expect(link?.tagName).toEqual('SPAN');
72
+ expect(link?.textContent).toEqual(PREVIOUS_LINK_OBJECT.title);
73
+ });
74
+
56
75
  test('passing additional props', () => {
57
76
  render(
58
- <SequentialNavigation
59
- data-test="foo"
60
- next={NEXT_LINK_OBJECT}
61
- previous={PREVIOUS_LINK_OBJECT}
62
- />
77
+ <SequentialNavigation data-test="foo" />
63
78
  );
64
79
 
65
80
  const sequentialNavigation = screen.getByRole('navigation');
@@ -68,11 +83,7 @@ test('passing additional props', () => {
68
83
 
69
84
  test('passing additional CSS classes', () => {
70
85
  render(
71
- <SequentialNavigation
72
- className="foo"
73
- next={NEXT_LINK_OBJECT}
74
- previous={PREVIOUS_LINK_OBJECT}
75
- />
86
+ <SequentialNavigation className="foo" />
76
87
  );
77
88
 
78
89
  const sequentialNavigation = screen.getByRole('navigation');
@@ -1,44 +1,63 @@
1
- const NextLink: React.FC<SGDS.Component.SequentialNavigation.Link> = ({
1
+ const SeqNavLink = ({
2
+ children,
2
3
  href,
3
- title
4
- }) => {
4
+ isPrev,
5
+ linkComponent,
6
+ textLabel
7
+ }: SGDS.Component.SequentialNavigation.Link) => {
8
+ const LINK_CLASSES = [
9
+ 'ds_sequential-nav__button',
10
+ isPrev ? 'ds_sequential-nav__button--left' : 'ds_sequential-nav__button--right'
11
+ ].join(' ');
12
+
13
+ const ITEM_CLASSES = [
14
+ 'ds_sequential-nav__item',
15
+ isPrev ? 'ds_sequential-nav__item--prev' : 'ds_sequential-nav__item--next'
16
+ ].join(' ');
17
+
18
+ function processChildren(children: React.ReactNode) {
19
+ const linkInner = <span className="ds_sequential-nav__text" data-label={textLabel}>{children}</span>
20
+
21
+ if (linkComponent) {
22
+ return linkComponent({ className: LINK_CLASSES, href, children: linkInner });
23
+ } else {
24
+ return <a href={href} className={LINK_CLASSES}>{linkInner}</a>;
25
+ }
26
+ }
27
+
5
28
  return (
6
29
  <div
7
- className="ds_sequential-nav__item ds_sequential-nav__item--next"
30
+ className={ITEM_CLASSES}
8
31
  >
9
- <a href={href} className="ds_sequential-nav__button ds_sequential-nav__button--right">
10
- <span className="ds_sequential-nav__text" data-label="Next">
11
- {title}
12
- </span>
13
- </a>
32
+ {processChildren(children)}
14
33
  </div>
15
- );
34
+ )
16
35
  };
17
36
 
18
- const PrevLink: React.FC<SGDS.Component.SequentialNavigation.Link> = ({
37
+ const NextLink = ({
38
+ children,
19
39
  href,
20
- title,
21
- }) => {
22
- return (
23
- <div
24
- className="ds_sequential-nav__item ds_sequential-nav__item--prev"
25
- >
26
- <a href={href} className="ds_sequential-nav__button ds_sequential-nav__button--left">
27
- <span className="ds_sequential-nav__text" data-label="Previous">
28
- {title}
29
- </span>
30
- </a>
31
- </div>
32
- );
40
+ linkComponent,
41
+ textLabel = 'Next'
42
+ }: SGDS.Component.SequentialNavigation.Link) => {
43
+ return <SeqNavLink href={href} linkComponent={linkComponent} textLabel={textLabel}>{children}</SeqNavLink>
44
+ };
45
+
46
+ const PreviousLink = ({
47
+ children,
48
+ href,
49
+ linkComponent,
50
+ textLabel = 'Previous'
51
+ }: SGDS.Component.SequentialNavigation.Link) => {
52
+ return <SeqNavLink href={href} linkComponent={linkComponent} textLabel={textLabel} isPrev>{children}</SeqNavLink>
33
53
  };
34
54
 
35
- const SequentialNavigation: React.FC<SGDS.Component.SequentialNavigation> = ({
55
+ const SequentialNavigation = ({
36
56
  ariaLabel = 'Article navigation',
57
+ children,
37
58
  className,
38
- next,
39
- previous,
40
59
  ...props
41
- }) => {
60
+ }: SGDS.Component.SequentialNavigation) => {
42
61
  return (
43
62
  <nav
44
63
  className={[
@@ -48,12 +67,15 @@ const SequentialNavigation: React.FC<SGDS.Component.SequentialNavigation> = ({
48
67
  aria-label={ariaLabel}
49
68
  {...props}
50
69
  >
51
- {previous && <PrevLink href={previous.href} title={previous.title}></PrevLink>}
52
- {next && <NextLink href={next.href} title={next.title}></NextLink>}
70
+ {children}
53
71
  </nav>
54
72
  );
55
73
  };
56
74
 
57
75
  SequentialNavigation.displayName = 'SequentialNavigation';
76
+ SequentialNavigation.Next = NextLink;
77
+ SequentialNavigation.Previous = PreviousLink;
78
+ NextLink.displayName = 'SequentialNavigation.Next';
79
+ PreviousLink.displayName = 'SequentialNavigation.Previous';
58
80
 
59
81
  export default SequentialNavigation;
@@ -1,49 +1,27 @@
1
1
  import { test, expect } from 'vitest';
2
2
  import { render, screen, within } from '@testing-library/react';
3
- import SideNavigation, { List, Link } from './SideNavigation';
4
-
5
- const ITEMS = [
6
- {
7
- title: 'Apples',
8
- href: '#apples',
9
- items: [
10
- {
11
- title: 'Green apples',
12
- href: '#green-apples',
13
- items: [
14
- {
15
- title: 'Bramley',
16
- current: true
17
- },
18
- {
19
- title: 'Granny Smith',
20
- href: '#granny-smith'
21
- }
22
- ]
23
- },
24
- {
25
- title: 'Red apples',
26
- href: '#red-apples'
27
- }
28
- ]
29
- },
30
- {
31
- title: 'Bananas',
32
- href: '#bananas'
33
- },
34
- {
35
- title: 'Cherries',
36
- href: '#cherries'
37
- },
38
- {
39
- title: 'Dates',
40
- href: '#dates'
41
- }
42
- ];
3
+ import SideNavigation from './SideNavigation';
43
4
 
44
5
  test('side navigation renders correctly', () => {
45
6
  render(
46
- <SideNavigation items={ITEMS} />
7
+ <SideNavigation>
8
+ <SideNavigation.List isRoot>
9
+ <SideNavigation.Item href="#apples" title="Apples">
10
+ <SideNavigation.List>
11
+ <SideNavigation.Item href="#green-apples" title="Green apples">
12
+ <SideNavigation.List>
13
+ <SideNavigation.Item href="#bramley" title="Bramley" current/>
14
+ <SideNavigation.Item href="#granny-smith" title="Granny smith"/>
15
+ </SideNavigation.List>
16
+ </SideNavigation.Item>
17
+ <SideNavigation.Item href="#red-apples" title="Red apples"/>
18
+ </SideNavigation.List>
19
+ </SideNavigation.Item>
20
+ <SideNavigation.Item href="#bananas" title="Bananas" />
21
+ <SideNavigation.Item href="#cherries" title="Cherries" />
22
+ <SideNavigation.Item href="#dates" title="Dates"/>
23
+ </SideNavigation.List>
24
+ </SideNavigation>
47
25
  );
48
26
 
49
27
  const sideNavigation = screen.getByRole('navigation');
@@ -63,16 +41,11 @@ test('side navigation renders correctly', () => {
63
41
  expect(label?.textContent).toEqual('Show all Pages in this section');
64
42
 
65
43
  expect(rootList).toHaveAttribute('id', 'side-navigation-root');
66
-
67
- // some specifics of this case
68
- expect(rootList.children.length).toEqual(4);
69
- expect(document.querySelectorAll('.ds_side-navigation__link').length).toEqual(8);
70
- expect(document.querySelectorAll('.ds_side-navigation__list').length).toEqual(3);
71
44
  });
72
45
 
73
46
  test('side nav link renders correctly', () => {
74
47
  render(
75
- <Link title="Green apples" href="#green-apples" />
48
+ <SideNavigation.Item href="#green-apples" title="Green apples" />
76
49
  );
77
50
 
78
51
  const item = screen.getByRole('listitem');
@@ -87,7 +60,7 @@ test('side nav link renders correctly', () => {
87
60
 
88
61
  test('current side nav item without link renders correctly', () => {
89
62
  render(
90
- <Link title="Green apples" href="#green-apples" current />
63
+ <SideNavigation.Item href="#green-apples" title="Green apples" current />
91
64
  );
92
65
 
93
66
  const item = screen.getByRole('listitem');
@@ -99,22 +72,18 @@ test('current side nav item without link renders correctly', () => {
99
72
 
100
73
  test('side nav link with children', () => {
101
74
  render(
102
- <Link title="Green apples" href="#green-apples" items={[{
103
- title: 'Bramley',
104
- href: '#bramley'
105
- },
106
- {
107
- title: 'Granny Smith',
108
- href: '#granny-smith'
109
- }]} />
75
+ <SideNavigation.Item href="#green-apples" title="Green apples">
76
+ <SideNavigation.List>
77
+ <SideNavigation.Item href="#bramley" title="Bramley" />
78
+ </SideNavigation.List>
79
+ </SideNavigation.Item>
110
80
  );
111
81
 
112
82
  const childList = screen.getByRole('list');
113
- const childItem = screen.getAllByRole('listitem')[1];
83
+ const childItem = within(childList).getByRole('listitem');
114
84
  const childLink = within(childItem).getByRole('link');
115
85
 
116
86
  expect(childList).toHaveClass('ds_side-navigation__list');
117
- expect(childList.children.length).toEqual(2);
118
87
 
119
88
  // check properties of first child link
120
89
  expect(childItem).toHaveClass('ds_side-navigation__item');
@@ -123,41 +92,26 @@ test('side nav link with children', () => {
123
92
  expect(childLink.textContent).toEqual('Bramley');
124
93
  });
125
94
 
126
- test('side nav list renders correctly', () => {
127
- const ITEMS = [
128
- {
129
- title: 'Bramley',
130
- href: '#bramley'
131
- },
132
- {
133
- title: 'Granny Smith',
134
- href: '#granny-smith'
135
- }
136
- ];
95
+ test('side nav link with custom element', () => {
96
+ const LINK_TEXT = 'Green apples'
137
97
 
138
98
  render(
139
- <List items={ITEMS}/>
99
+ <SideNavigation.Item href="#green-apples" title={LINK_TEXT} linkComponent={
100
+ ({ className, ...props }) => (
101
+ <span role="link" className={className} {...props}/>
102
+ )}/>
140
103
  );
141
104
 
142
- const list = screen.getByRole('list');
143
- const item = screen.getAllByRole('listitem')[0];
144
- const link = within(item).getByRole('link');
145
-
146
- expect(list).toHaveClass('ds_side-navigation__list');
147
- expect(list.tagName).toEqual('UL');
148
-
149
- expect(list.children.length).toEqual(ITEMS.length);
105
+ const item = screen.getByRole('listitem');
106
+ const link = within(item).queryByRole('link');
150
107
 
151
- // check properties of first link
152
- expect(item).toHaveClass('ds_side-navigation__item');
153
- expect(link).toHaveClass('ds_side-navigation__link');
154
- expect(link).toHaveAttribute('href', '#bramley');
155
- expect(link.textContent).toEqual('Bramley');
108
+ expect(link?.tagName).toEqual('SPAN');
109
+ expect(link?.textContent).toEqual(LINK_TEXT);
156
110
  });
157
111
 
158
112
  test('passing additional props', () => {
159
113
  render(
160
- <SideNavigation data-test="foo" items={ITEMS} />
114
+ <SideNavigation data-test="foo" />
161
115
  );
162
116
 
163
117
  const sideNavigation = screen.getByRole('navigation');
@@ -166,7 +120,7 @@ test('passing additional props', () => {
166
120
 
167
121
  test('passing additional CSS classes', () => {
168
122
  render(
169
- <SideNavigation className="foo" items={ITEMS} />
123
+ <SideNavigation className="foo" />
170
124
  );
171
125
 
172
126
  const sideNavigation = screen.getByRole('navigation');