@scottish-government/designsystem-react 0.7.0 → 0.8.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 (203) 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/Breadcrumbs.d.ts +2 -5
  6. package/@types/components/Checkbox.d.ts +0 -2
  7. package/@types/components/ConfirmationMessage.d.ts +1 -1
  8. package/@types/components/ContentsNav.d.ts +4 -6
  9. package/@types/components/DatePicker.d.ts +1 -1
  10. package/@types/components/ErrorSummary.d.ts +3 -4
  11. package/@types/components/NotificationPanel.d.ts +1 -1
  12. package/@types/components/Pagination.d.ts +5 -4
  13. package/@types/components/PhaseBanner.d.ts +0 -1
  14. package/@types/components/Question.d.ts +1 -1
  15. package/@types/components/RadioButton.d.ts +0 -1
  16. package/@types/components/Select.d.ts +0 -7
  17. package/@types/components/SequentialNavigation.d.ts +4 -4
  18. package/@types/components/SideNavigation.d.ts +4 -5
  19. package/@types/components/SiteFooter.d.ts +25 -0
  20. package/@types/components/SiteHeader.d.ts +10 -3
  21. package/@types/components/SiteNavigation.d.ts +2 -3
  22. package/@types/components/SkipLinks.d.ts +3 -4
  23. package/@types/components/SummaryCard.d.ts +0 -2
  24. package/@types/components/SummaryList.d.ts +0 -13
  25. package/@types/components/Tabs.d.ts +0 -1
  26. package/@types/components/Tag.d.ts +1 -3
  27. package/@types/components/TaskList.d.ts +1 -0
  28. package/@types/sgds.d.ts +13 -2
  29. package/CHANGELOG.md +63 -1
  30. package/dist/common/AbstractNotificationBanner.jsx +8 -6
  31. package/dist/common/ActionLink.jsx +19 -0
  32. package/dist/common/FileIcon.jsx +2 -7
  33. package/dist/common/Icon.jsx +3 -9
  34. package/dist/components/Accordion/Accordion.jsx +2 -2
  35. package/dist/components/Breadcrumbs/Breadcrumbs.jsx +20 -15
  36. package/dist/components/Checkbox/Checkbox.jsx +2 -30
  37. package/dist/components/{aspect-box/aspect-box.jsx → Checkbox/CheckboxGroup.jsx} +19 -29
  38. package/dist/components/ContentsNav/ContentsNav.jsx +27 -16
  39. package/dist/components/CookieBanner/CookieBanner.jsx +1 -0
  40. package/dist/components/DatePicker/DatePicker.jsx +5 -5
  41. package/dist/components/ErrorSummary/ErrorSummary.jsx +28 -18
  42. package/dist/components/NotificationBanner/NotificationBanner.jsx +2 -2
  43. package/dist/components/Pagination/Pagination.jsx +42 -22
  44. package/dist/components/PhaseBanner/PhaseBanner.jsx +3 -3
  45. package/dist/components/Question/Question.jsx +3 -3
  46. package/dist/components/RadioButton/RadioButton.jsx +3 -17
  47. package/dist/{common/icon.jsx → components/RadioButton/RadioGroup.jsx} +22 -18
  48. package/dist/components/Select/Select.jsx +4 -7
  49. package/dist/components/SequentialNavigation/SequentialNavigation.jsx +31 -18
  50. package/dist/components/SideNavigation/SideNavigation.jsx +17 -16
  51. package/dist/components/SiteFooter/SiteFooter.jsx +104 -0
  52. package/dist/components/SiteHeader/SiteHeader.jsx +113 -32
  53. package/dist/components/SiteNavigation/SiteNavigation.jsx +20 -7
  54. package/dist/components/SkipLinks/SkipLinks.jsx +10 -10
  55. package/dist/components/SummaryCard/SummaryCard.jsx +25 -14
  56. package/dist/components/SummaryList/SummaryList.jsx +65 -47
  57. package/dist/components/Tabs/Tabs.jsx +6 -6
  58. package/dist/components/Tag/Tag.jsx +2 -2
  59. package/dist/components/TaskList/TaskList.jsx +14 -3
  60. package/dist/components/TextInput/TextInput.jsx +3 -3
  61. package/dist/components/Textarea/Textarea.jsx +3 -3
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +1 -1
  64. package/src/common/AbstractNotificationBanner.test.tsx +1 -1
  65. package/src/common/AbstractNotificationBanner.tsx +14 -13
  66. package/src/common/ActionLink.test.tsx +80 -0
  67. package/src/common/ActionLink.tsx +27 -0
  68. package/src/common/ConditionalWrapper.tsx +1 -1
  69. package/src/common/FileIcon.tsx +7 -11
  70. package/src/common/HintText.tsx +2 -2
  71. package/src/common/Icon.tsx +13 -17
  72. package/src/common/ScreenReaderText.tsx +2 -2
  73. package/src/common/WrapperTag.tsx +2 -2
  74. package/src/components/Accordion/Accordion.test.tsx +1 -1
  75. package/src/components/Accordion/Accordion.tsx +6 -7
  76. package/src/components/AspectBox/AspectBox.tsx +2 -2
  77. package/src/components/BackToTop/BackToTop.tsx +2 -2
  78. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +79 -47
  79. package/src/components/Breadcrumbs/Breadcrumbs.tsx +31 -31
  80. package/src/components/Button/Button.tsx +2 -2
  81. package/src/components/Checkbox/Checkbox.test.tsx +1 -96
  82. package/src/components/Checkbox/Checkbox.tsx +3 -55
  83. package/src/components/Checkbox/CheckboxGroup.test.tsx +37 -0
  84. package/src/components/Checkbox/CheckboxGroup.tsx +46 -0
  85. package/src/components/ConfirmationMessage/ConfirmationMessage.tsx +2 -2
  86. package/src/components/ContentsNav/ContentsNav.test.tsx +40 -51
  87. package/src/components/ContentsNav/ContentsNav.tsx +32 -25
  88. package/src/components/CookieBanner/CookieBanner.tsx +3 -3
  89. package/src/components/DatePicker/DatePicker.test.tsx +1 -1
  90. package/src/components/DatePicker/DatePicker.tsx +7 -7
  91. package/src/components/Details/Details.tsx +2 -2
  92. package/src/components/ErrorMessage/ErrorMessage.tsx +2 -2
  93. package/src/components/ErrorSummary/ErrorSummary.test.tsx +40 -34
  94. package/src/components/ErrorSummary/ErrorSummary.tsx +40 -32
  95. package/src/components/FileDownload/FileDownload.tsx +2 -2
  96. package/src/components/HideThisPage/HideThisPage.tsx +2 -2
  97. package/src/components/InsetText/InsetText.tsx +2 -2
  98. package/src/components/NotificationBanner/NotificationBanner.tsx +6 -7
  99. package/src/components/NotificationPanel/NotificationPanel.tsx +2 -2
  100. package/src/components/PageHeader/PageHeader.tsx +2 -2
  101. package/src/components/PageMetadata/PageMetadata.tsx +4 -5
  102. package/src/components/Pagination/Pagination.test.tsx +26 -7
  103. package/src/components/Pagination/Pagination.tsx +70 -36
  104. package/src/components/PhaseBanner/PhaseBanner.tsx +4 -5
  105. package/src/components/Question/Question.test.tsx +1 -1
  106. package/src/components/Question/Question.tsx +5 -5
  107. package/src/components/RadioButton/RadioButton.test.tsx +7 -126
  108. package/src/components/RadioButton/RadioButton.tsx +4 -41
  109. package/src/components/RadioButton/RadioGroup.test.tsx +65 -0
  110. package/src/components/RadioButton/RadioGroup.tsx +38 -0
  111. package/src/components/Select/Select.test.tsx +39 -37
  112. package/src/components/Select/Select.tsx +7 -22
  113. package/src/components/SequentialNavigation/SequentialNavigation.test.tsx +32 -21
  114. package/src/components/SequentialNavigation/SequentialNavigation.tsx +52 -30
  115. package/src/components/SideNavigation/SideNavigation.test.tsx +39 -85
  116. package/src/components/SideNavigation/SideNavigation.tsx +27 -29
  117. package/src/components/SiteFooter/SiteFooter.test.tsx +153 -0
  118. package/src/components/SiteFooter/SiteFooter.tsx +107 -0
  119. package/src/components/SiteHeader/SiteHeader.test.tsx +87 -79
  120. package/src/components/SiteHeader/SiteHeader.tsx +103 -40
  121. package/src/components/SiteNavigation/SiteNavigation.test.tsx +42 -23
  122. package/src/components/SiteNavigation/SiteNavigation.tsx +28 -16
  123. package/src/components/SiteSearch/SiteSearch.tsx +2 -2
  124. package/src/components/SkipLinks/SkipLinks.test.tsx +22 -10
  125. package/src/components/SkipLinks/SkipLinks.tsx +16 -15
  126. package/src/components/SummaryCard/SummaryCard.test.tsx +31 -35
  127. package/src/components/SummaryCard/SummaryCard.tsx +39 -28
  128. package/src/components/SummaryList/SummaryList.test.tsx +49 -148
  129. package/src/components/SummaryList/SummaryList.tsx +54 -92
  130. package/src/components/Table/Table.tsx +2 -2
  131. package/src/components/Tabs/Tabs.tsx +14 -15
  132. package/src/components/Tag/Tag.test.tsx +4 -4
  133. package/src/components/Tag/Tag.tsx +4 -4
  134. package/src/components/TaskList/TaskList.test.tsx +26 -0
  135. package/src/components/TaskList/TaskList.tsx +21 -11
  136. package/src/components/TextInput/TextInput.test.tsx +1 -1
  137. package/src/components/TextInput/TextInput.tsx +5 -5
  138. package/src/components/Textarea/Textarea.test.tsx +1 -1
  139. package/src/components/Textarea/Textarea.tsx +5 -5
  140. package/src/components/WarningText/WarningText.tsx +2 -2
  141. package/dist/common/abstract-notification-banner.jsx +0 -63
  142. package/dist/common/conditional-wrapper.jsx +0 -8
  143. package/dist/common/file-icon.jsx +0 -51
  144. package/dist/common/hint-text.jsx +0 -9
  145. package/dist/common/screen-reader-text.jsx +0 -9
  146. package/dist/common/wrapper-tag.jsx +0 -11
  147. package/dist/components/accordion/accordion.jsx +0 -102
  148. package/dist/components/back-to-top/back-to-top.jsx +0 -27
  149. package/dist/components/breadcrumbs/breadcrumbs.jsx +0 -28
  150. package/dist/components/button/button.jsx +0 -30
  151. package/dist/components/checkbox/checkbox.jsx +0 -62
  152. package/dist/components/confirmation-message/confirmation-message.jsx +0 -24
  153. package/dist/components/contents-nav/contents-nav.jsx +0 -33
  154. package/dist/components/cookie-banner/cookie-banner.jsx +0 -21
  155. package/dist/components/date-picker/date-picker.jsx +0 -54
  156. package/dist/components/details/details.jsx +0 -17
  157. package/dist/components/error-message/error-message.jsx +0 -12
  158. package/dist/components/error-summary/error-summary.jsx +0 -27
  159. package/dist/components/file-download/file-download.jsx +0 -50
  160. package/dist/components/hide-this-page/hide-this-page.jsx +0 -71
  161. package/dist/components/inset-text/inset-text.jsx +0 -14
  162. package/dist/components/notification-banner/notification-banner.jsx +0 -26
  163. package/dist/components/notification-panel/notification-panel.jsx +0 -21
  164. package/dist/components/page-header/page-header.jsx +0 -15
  165. package/dist/components/page-metadata/page-metadata.jsx +0 -26
  166. package/dist/components/pagination/pagination.jsx +0 -97
  167. package/dist/components/phase-banner/phase-banner.jsx +0 -23
  168. package/dist/components/question/question.jsx +0 -22
  169. package/dist/components/radio-button/radio-button.jsx +0 -43
  170. package/dist/components/select/select.jsx +0 -52
  171. package/dist/components/sequential-navigation/sequential-navigation.jsx +0 -31
  172. package/dist/components/side-navigation/side-navigation.jsx +0 -52
  173. package/dist/components/site-header/site-header.jsx +0 -68
  174. package/dist/components/site-navigation/site-navigation.jsx +0 -22
  175. package/dist/components/site-search/site-search.jsx +0 -55
  176. package/dist/components/skip-links/skip-links.jsx +0 -21
  177. package/dist/components/summary-card/summary-card.jsx +0 -67
  178. package/dist/components/summary-list/summary-list.jsx +0 -75
  179. package/dist/components/table/table.jsx +0 -24
  180. package/dist/components/tabs/tabs.jsx +0 -99
  181. package/dist/components/tag/tag.jsx +0 -13
  182. package/dist/components/task-list/task-list.jsx +0 -95
  183. package/dist/components/text-input/text-input.jsx +0 -58
  184. package/dist/components/textarea/textarea.jsx +0 -54
  185. package/dist/components/warning-text/warning-text.jsx +0 -16
  186. package/dist/icons/ArrowUpward.jsx +0 -41
  187. package/dist/icons/CalendarToday.jsx +0 -41
  188. package/dist/icons/Cancel.jsx +0 -40
  189. package/dist/icons/CheckCircle.jsx +0 -41
  190. package/dist/icons/ChevronLeft.jsx +0 -41
  191. package/dist/icons/ChevronRight.jsx +0 -41
  192. package/dist/icons/Close.jsx +0 -41
  193. package/dist/icons/Description.jsx +0 -41
  194. package/dist/icons/DoubleChevronLeft.jsx +0 -40
  195. package/dist/icons/DoubleChevronRight.jsx +0 -40
  196. package/dist/icons/Error.jsx +0 -41
  197. package/dist/icons/ExpandLess.jsx +0 -41
  198. package/dist/icons/ExpandMore.jsx +0 -41
  199. package/dist/icons/List.jsx +0 -44
  200. package/dist/icons/Menu.jsx +0 -41
  201. package/dist/icons/PriorityHigh.jsx +0 -42
  202. package/dist/icons/Search.jsx +0 -41
  203. package/dist/icons/index.js +0 -40
@@ -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');
@@ -2,60 +2,55 @@ import { useEffect, useRef } from 'react';
2
2
  // @ts-ignore
3
3
  import DSSideNavigation from '@scottish-government/design-system/src/components/side-navigation/side-navigation';
4
4
 
5
- export const List: React.FC<SGDS.Component.SideNavigation.List> = function ({
6
- items,
7
- root
8
- }) {
5
+ const SideNavigationList = function ({
6
+ children,
7
+ isRoot
8
+ }: SGDS.Component.SideNavigation.List) {
9
9
  return (
10
10
  <ul className="ds_side-navigation__list"
11
- id={root ? 'side-navigation-root' : undefined }
11
+ id={isRoot ? 'side-navigation-root' : undefined }
12
12
  >
13
- {items && items.map((item, index: number) => (
14
- <Link
15
- title={item.title}
16
- href={item.href}
17
- items={item.items}
18
- current={item.current}
19
- key={'sidenavitem' + index}
20
- />
21
- ))}
13
+ {children}
22
14
  </ul>
23
15
  );
24
16
  };
25
17
 
26
- export const Link: React.FC<SGDS.Component.SideNavigation.Link> = function ({
27
- current = false,
18
+ const SideNavigationItem = function ({
19
+ children,
28
20
  href,
29
- items,
21
+ current = false,
22
+ linkComponent,
30
23
  title
31
- }) {
24
+ }: SGDS.Component.SideNavigation.Item) {
25
+ const LINK_CLASS = 'ds_side-navigation__link';
26
+
32
27
  return (
33
28
  <li
34
29
  className={[
35
30
  'ds_side-navigation__item',
36
- items && 'ds_side-navigation__item--has-children'
31
+ children && 'ds_side-navigation__item--has-children'
37
32
  ].join(' ')}
38
33
  >
39
34
  {current ?
40
- <span className="ds_side-navigation__link ds_current">{title}</span> :
41
- <a href={href} className="ds_side-navigation__link">{title}</a>
35
+ <span className={LINK_CLASS + ' ds_current'}>{title}</span> :
36
+ linkComponent ? linkComponent({ className: LINK_CLASS, href, children: title }) :
37
+ <a href={href} className={LINK_CLASS}>{title}</a>
42
38
  }
43
39
 
44
- {items && <List items={items} />}
40
+ {children}
45
41
  </li>
46
42
  );
47
43
  };
48
44
 
49
- const SideNavigation: React.FC<SGDS.Component.SideNavigation> = function ({
45
+ const SideNavigation = function ({
50
46
  children,
51
47
  className,
52
- items,
53
48
  ...props
54
- }) {
49
+ }: SGDS.Component.SideNavigation) {
55
50
  const ref = useRef(null);
56
51
 
57
52
  useEffect(() => {
58
- if (ref.current) {
53
+ if (ref.current && children) {
59
54
  new DSSideNavigation(ref.current).init();
60
55
  }
61
56
  }, [ref]);
@@ -77,13 +72,16 @@ const SideNavigation: React.FC<SGDS.Component.SideNavigation> = function ({
77
72
  <span className="ds_side-navigation__expand-indicator"></span>
78
73
  </label>
79
74
 
80
- {items && <List root items={items} />}
75
+ {children}
81
76
  </nav>
82
77
  );
83
78
  };
84
79
 
85
80
  SideNavigation.displayName = 'SideNavigation';
86
- Link.displayName = 'SideNavLink';
87
- List.displayName = 'SideNavList';
81
+ SideNavigationItem.displayName = 'SideNavigation.Item';
82
+ SideNavigationList.displayName = 'SideNavigation.List';
83
+
84
+ SideNavigation.Item = SideNavigationItem;
85
+ SideNavigation.List = SideNavigationList;
88
86
 
89
87
  export default SideNavigation;
@@ -0,0 +1,153 @@
1
+ import { test, expect } from 'vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import SiteFooter from './SiteFooter';
4
+
5
+ const LINK_HREF = '#cookies';
6
+ const LINK_TEXT = 'Cookies';
7
+
8
+ test('site footer renders correctly', () => {
9
+ render(
10
+ <SiteFooter data-testid="sitefooter">
11
+ <SiteFooter.Links data-testid="sitefooterlinks" />
12
+ <SiteFooter.License data-testid="sitefooterlicense" />
13
+ <SiteFooter.Org data-testid="sitefooterorg" />
14
+ </SiteFooter>
15
+ );
16
+
17
+ const siteFooter = screen.getByTestId('sitefooter');
18
+ const siteFooterWrapper = siteFooter.children[0];
19
+ const siteFooterContent = siteFooterWrapper.children[0];
20
+
21
+ const siteFooterLinksContainer = screen.getByTestId('sitefooterlinks');
22
+ const siteFooterLicenseContainer = screen.getByTestId('sitefooterlicense');
23
+ const siteFooterOrgContainer = screen.getByTestId('sitefooterorg');
24
+
25
+ expect(siteFooter).toHaveClass('ds_site-footer');
26
+ expect(siteFooter.tagName).toEqual('FOOTER');
27
+
28
+ expect(siteFooterWrapper).toHaveClass('ds_wrapper');
29
+ expect(siteFooterWrapper.tagName).toEqual('DIV');
30
+
31
+ expect(siteFooterContent).toHaveClass('ds_site-footer__content');
32
+ expect(siteFooterContent.tagName).toEqual('DIV');
33
+
34
+ expect(siteFooterLinksContainer).toHaveClass('ds_site-footer__site-items');
35
+ expect(siteFooterLinksContainer.tagName).toEqual('UL');
36
+
37
+ expect(siteFooterLicenseContainer).toHaveClass('ds_site-footer__copyright');
38
+ expect(siteFooterLicenseContainer.tagName).toEqual('DIV');
39
+
40
+ expect(siteFooterOrgContainer).toHaveClass('ds_site-footer__org');
41
+ expect(siteFooterOrgContainer.tagName).toEqual('DIV');
42
+ });
43
+
44
+ test('site footer links render correctly', () => {
45
+ render(
46
+ <SiteFooter.Link href={LINK_HREF}>{LINK_TEXT}</SiteFooter.Link>
47
+ );
48
+
49
+ const listItem = screen.getByRole('listitem');
50
+ const link = within(listItem).getByRole('link');
51
+
52
+ expect(listItem).toHaveClass('ds_site-items__item');
53
+ expect(link).toHaveAttribute('href', LINK_HREF);
54
+ expect(link.textContent).toEqual(LINK_TEXT);
55
+ });
56
+
57
+ test('site footer links with custom element render correctly', () => {
58
+ render(
59
+ <SiteFooter.Link href={LINK_HREF} linkComponent={
60
+ ({ className, ...props }) => (
61
+ <span role="link" className={className} {...props}/>
62
+ )}>
63
+ {LINK_TEXT}
64
+ </SiteFooter.Link>
65
+ );
66
+
67
+ const listItem = screen.getByRole('listitem');
68
+ const link = within(listItem).getByRole('link');
69
+
70
+ expect(listItem).toHaveClass('ds_site-items__item');
71
+ expect(link).toHaveAttribute('href', LINK_HREF);
72
+ expect(link.textContent).toEqual(LINK_TEXT);
73
+ expect(link.tagName).toEqual('SPAN');
74
+ });
75
+
76
+ test('site footer links with no href render correctly', () => {
77
+ render(
78
+ <SiteFooter.Link>{LINK_TEXT}</SiteFooter.Link>
79
+ );
80
+
81
+ const listItem = screen.getByRole('listitem');
82
+ const link = within(listItem).queryByRole('link');
83
+
84
+ expect(listItem).toHaveClass('ds_site-items__item');
85
+ expect(listItem.textContent).toEqual(LINK_TEXT);
86
+ expect(link).not.toBeInTheDocument();
87
+ });
88
+
89
+ test('site footer org renders correctly', () => {
90
+ const CHILDREN = 'Foo';
91
+
92
+ render(
93
+ <SiteFooter.Org data-testid="sitefooterorg" title="The Scottish Government" href="https://www.gov.scot/">
94
+ {CHILDREN}
95
+ </SiteFooter.Org>
96
+ );
97
+
98
+ const siteFooterOrgContainer = screen.getByTestId('sitefooterorg');
99
+ const siteFooterOrgLink = within(siteFooterOrgContainer).getByRole('link');
100
+
101
+ expect(siteFooterOrgContainer).toHaveClass('ds_site-footer__org');
102
+ expect(siteFooterOrgContainer.tagName).toEqual('DIV');
103
+
104
+ expect(siteFooterOrgLink).toHaveClass('ds_site-footer__org-link');
105
+ expect(siteFooterOrgLink.tagName).toEqual('A');
106
+ });
107
+
108
+ test('site footer org with image renders correctly', () => {
109
+ const CHILDREN = <img src="foo" alt="Foo" />
110
+
111
+ render(
112
+ <SiteFooter.Org data-testid="sitefooterorg" title="The Scottish Government" href="https://www.gov.scot/">
113
+ {CHILDREN}
114
+ </SiteFooter.Org>
115
+ );
116
+
117
+ const siteFooterOrgContainer = screen.getByTestId('sitefooterorg');
118
+ const siteFooterOrgImage = within(siteFooterOrgContainer).getByRole('img');
119
+
120
+ expect(siteFooterOrgImage).toHaveClass('ds_site-footer__org-logo');
121
+ });
122
+
123
+ test('site footer license renders correctly', () => {
124
+ const CHILDREN = 'Hello';
125
+
126
+ render(
127
+ <SiteFooter.License data-testid="sitefooterlicense">
128
+ {CHILDREN}
129
+ </SiteFooter.License>
130
+ );
131
+
132
+ const siteFooterLicenseContainer = screen.getByTestId('sitefooterlicense');
133
+
134
+ expect(siteFooterLicenseContainer.innerHTML).toEqual(CHILDREN);
135
+ });
136
+
137
+ test('passing additional props', () => {
138
+ render(
139
+ <SiteFooter data-testid="sitefooter" data-test="foo" />
140
+ );
141
+
142
+ const siteFooter = screen.getByTestId('sitefooter');
143
+ expect(siteFooter?.dataset.test).toEqual('foo');
144
+ });
145
+
146
+ test('passing additional CSS classes', () => {
147
+ render(
148
+ <SiteFooter data-testid="sitefooter" className="foo" />
149
+ );
150
+
151
+ const siteFooter = screen.getByTestId('sitefooter');
152
+ expect(siteFooter).toHaveClass('foo');
153
+ });