@scottish-government/designsystem-react 0.7.0 → 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 (211) 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/{aspect-box/aspect-box.jsx → Checkbox/CheckboxGroup.jsx} +14 -30
  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
  148. package/dist/common/abstract-notification-banner.jsx +0 -63
  149. package/dist/common/conditional-wrapper.jsx +0 -8
  150. package/dist/common/file-icon.jsx +0 -51
  151. package/dist/common/hint-text.jsx +0 -9
  152. package/dist/common/icon.jsx +0 -57
  153. package/dist/common/screen-reader-text.jsx +0 -9
  154. package/dist/common/wrapper-tag.jsx +0 -11
  155. package/dist/components/accordion/accordion.jsx +0 -102
  156. package/dist/components/back-to-top/back-to-top.jsx +0 -27
  157. package/dist/components/breadcrumbs/breadcrumbs.jsx +0 -28
  158. package/dist/components/button/button.jsx +0 -30
  159. package/dist/components/checkbox/checkbox.jsx +0 -62
  160. package/dist/components/confirmation-message/confirmation-message.jsx +0 -24
  161. package/dist/components/contents-nav/contents-nav.jsx +0 -33
  162. package/dist/components/cookie-banner/cookie-banner.jsx +0 -21
  163. package/dist/components/date-picker/date-picker.jsx +0 -54
  164. package/dist/components/details/details.jsx +0 -17
  165. package/dist/components/error-message/error-message.jsx +0 -12
  166. package/dist/components/error-summary/error-summary.jsx +0 -27
  167. package/dist/components/file-download/file-download.jsx +0 -50
  168. package/dist/components/hide-this-page/hide-this-page.jsx +0 -71
  169. package/dist/components/inset-text/inset-text.jsx +0 -14
  170. package/dist/components/notification-banner/notification-banner.jsx +0 -26
  171. package/dist/components/notification-panel/notification-panel.jsx +0 -21
  172. package/dist/components/page-header/page-header.jsx +0 -15
  173. package/dist/components/page-metadata/page-metadata.jsx +0 -26
  174. package/dist/components/pagination/pagination.jsx +0 -97
  175. package/dist/components/phase-banner/phase-banner.jsx +0 -23
  176. package/dist/components/question/question.jsx +0 -22
  177. package/dist/components/radio-button/radio-button.jsx +0 -43
  178. package/dist/components/select/select.jsx +0 -52
  179. package/dist/components/sequential-navigation/sequential-navigation.jsx +0 -31
  180. package/dist/components/side-navigation/side-navigation.jsx +0 -52
  181. package/dist/components/site-header/site-header.jsx +0 -68
  182. package/dist/components/site-navigation/site-navigation.jsx +0 -22
  183. package/dist/components/site-search/site-search.jsx +0 -55
  184. package/dist/components/skip-links/skip-links.jsx +0 -21
  185. package/dist/components/summary-card/summary-card.jsx +0 -67
  186. package/dist/components/summary-list/summary-list.jsx +0 -75
  187. package/dist/components/table/table.jsx +0 -24
  188. package/dist/components/tabs/tabs.jsx +0 -99
  189. package/dist/components/tag/tag.jsx +0 -13
  190. package/dist/components/task-list/task-list.jsx +0 -95
  191. package/dist/components/text-input/text-input.jsx +0 -58
  192. package/dist/components/textarea/textarea.jsx +0 -54
  193. package/dist/components/warning-text/warning-text.jsx +0 -16
  194. package/dist/icons/ArrowUpward.jsx +0 -41
  195. package/dist/icons/CalendarToday.jsx +0 -41
  196. package/dist/icons/Cancel.jsx +0 -40
  197. package/dist/icons/CheckCircle.jsx +0 -41
  198. package/dist/icons/ChevronLeft.jsx +0 -41
  199. package/dist/icons/ChevronRight.jsx +0 -41
  200. package/dist/icons/Close.jsx +0 -41
  201. package/dist/icons/Description.jsx +0 -41
  202. package/dist/icons/DoubleChevronLeft.jsx +0 -40
  203. package/dist/icons/DoubleChevronRight.jsx +0 -40
  204. package/dist/icons/Error.jsx +0 -41
  205. package/dist/icons/ExpandLess.jsx +0 -41
  206. package/dist/icons/ExpandMore.jsx +0 -41
  207. package/dist/icons/List.jsx +0 -44
  208. package/dist/icons/Menu.jsx +0 -41
  209. package/dist/icons/PriorityHigh.jsx +0 -42
  210. package/dist/icons/Search.jsx +0 -41
  211. package/dist/icons/index.js +0 -40
@@ -1,25 +1,94 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef } from 'react';
4
-
3
+ import React, { Children, useEffect, useRef } from 'react';
5
4
  import Icon from '../../common/Icon';
6
- import PhaseBanner from '../PhaseBanner/PhaseBanner';
7
- import SiteNavigation from "../SiteNavigation/SiteNavigation";
8
- import SiteSearch from "../SiteSearch/SiteSearch";
5
+ import SiteNavigation from '../SiteNavigation/SiteNavigation';
9
6
 
10
7
  // @ts-ignore
11
8
  import DSMobileMenu from '@scottish-government/design-system/src/components/site-navigation/site-navigation';
12
9
 
13
- const SiteHeader: React.FC<SGDS.Component.SiteHeader> = ({
10
+ const Brand = ({
11
+ children,
12
+ homeUrl = '/',
13
+ linkComponent,
14
+ siteTitle
15
+ }: SGDS.Component.SiteHeader.Brand) => {
16
+ function processChildren(children: React.ReactNode) {
17
+ const image = React.cloneElement(children as React.ReactElement<HTMLImageElement>, { className: 'ds_site-branding__logo-image' });
18
+
19
+ if (linkComponent) {
20
+ return linkComponent({ className: 'ds_site-branding__logo ds_site-branding__link', href: homeUrl, children: image });
21
+ } else if (homeUrl) {
22
+ return <a href={homeUrl} className="ds_site-branding__logo ds_site-branding__link">{image}</a>;
23
+ }
24
+ }
25
+
26
+ return (
27
+ <>
28
+ {processChildren(children)}
29
+
30
+ {siteTitle &&
31
+ <div className="ds_site-branding__title">
32
+ {siteTitle}
33
+ </div>
34
+ }
35
+ </>
36
+ );
37
+ }
38
+
39
+ const Navigation = ({
40
+ children
41
+ }: any) => {
42
+ return children;
43
+ }
44
+
45
+ const Phase = ({
46
+ children
47
+ }: any) => {
48
+ return children;
49
+ }
50
+
51
+ const Search = ({
52
+ children
53
+ }: any) => {
54
+ return children;
55
+ }
56
+
57
+ const SiteHeader = ({
58
+ children,
14
59
  logo = {},
15
60
  navigationItems,
16
61
  phaseBanner,
17
62
  siteSearch,
18
63
  siteTitle,
19
64
  ...props
20
- }) => {
65
+ }: SGDS.Component.SiteHeader) => {
21
66
  const mobileMenuRef = useRef(null);
22
67
 
68
+ let branding: React.ReactNode;
69
+ let navigation: React.ReactNode;
70
+ let mobileNavigation: React.ReactNode;
71
+ let phase: React.ReactNode;
72
+ let search: React.ReactNode;
73
+
74
+ // assign to slots
75
+ Children.forEach(children, (child: React.ReactNode) => {
76
+ const thisChild = child as React.ReactElement<any>;
77
+ if (thisChild && thisChild.type === Brand) {
78
+ branding = thisChild;
79
+ } else if (thisChild && thisChild.type === Navigation) {
80
+ navigation = thisChild;
81
+
82
+ if (thisChild.props.children.type === SiteNavigation) {
83
+ mobileNavigation = React.cloneElement(thisChild.props.children as React.ReactElement<SGDS.Component.SiteNavigation>, { className: 'ds_site-navigation--mobile', id: 'mobile-navigation', ref: mobileMenuRef});
84
+ }
85
+ } else if (thisChild && thisChild.type === Phase) {
86
+ phase = thisChild;
87
+ } else if (thisChild && thisChild.type === Search) {
88
+ search = thisChild;
89
+ }
90
+ });
91
+
23
92
  useEffect(() => {
24
93
  if (mobileMenuRef.current) {
25
94
  new DSMobileMenu(mobileMenuRef.current).init();
@@ -31,50 +100,35 @@ const SiteHeader: React.FC<SGDS.Component.SiteHeader> = ({
31
100
  <div className="ds_wrapper">
32
101
  <div className="ds_site-header__content">
33
102
  <div className="ds_site-branding">
34
- {logo &&
35
- <a className="ds_site-branding__logo ds_site-branding__link" href={logo.href ? logo.href : '/'}>
36
- <img className="ds_site-branding__logo-image" src={logo.src} alt={logo.alt} />
37
- </a>
38
- }
39
-
40
- {siteTitle && <div className="ds_site-branding__title">
41
- {siteTitle}
42
- </div>}
103
+ {branding}
43
104
  </div>
44
105
 
45
- {navigationItems &&
46
- <div className="ds_site-header__controls">
47
- <label aria-controls="mobile-navigation" className="ds_site-header__control js-toggle-menu" htmlFor="menu">
48
- <span className="ds_site-header__control-text">Menu</span>
49
- <Icon fill className="ds_site-header__control-icon" icon="Menu" aria-hidden="true" />
50
- <Icon fill className="ds_site-header__control-icon ds_site-header__control-icon--active-icon" icon="Close" aria-hidden="true" />
51
- </label>
52
- </div>
53
- }
106
+ {mobileNavigation &&
107
+ <>
108
+ <div className="ds_site-header__controls">
109
+ <label aria-controls="mobile-navigation" className="ds_site-header__control js-toggle-menu" htmlFor="menu">
110
+ <span className="ds_site-header__control-text">Menu</span>
111
+ <Icon fill className="ds_site-header__control-icon" icon="Menu" aria-hidden="true" />
112
+ <Icon fill className="ds_site-header__control-icon ds_site-header__control-icon--active-icon" icon="Close" aria-hidden="true" />
113
+ </label>
114
+ </div>
54
115
 
55
- {navigationItems &&
56
- <input className="ds_site-navigation__toggle" id="menu" type="checkbox" />
57
- }
58
- {navigationItems &&
59
- <SiteNavigation id="mobile-navigation" className="ds_site-navigation--mobile" items={navigationItems} ref={mobileMenuRef} />
116
+ <input className="ds_site-navigation__toggle" id="menu" type="checkbox" />
117
+ </>
60
118
  }
61
119
 
62
- {siteSearch &&
63
- <SiteSearch className="ds_site-header__search" {...siteSearch} />
64
- }
120
+ {mobileNavigation}
121
+
122
+ <div className="ds_site-header__search">{search}</div>
65
123
  </div>
66
124
  </div>
67
125
 
68
- {phaseBanner &&
69
- <PhaseBanner phaseName={phaseBanner.phaseName}>
70
- {phaseBanner.content}
71
- </PhaseBanner>
72
- }
126
+ {phase}
73
127
 
74
- {navigationItems &&
128
+ {navigation &&
75
129
  <div className="ds_site-header__navigation">
76
130
  <div className="ds_wrapper">
77
- <SiteNavigation items={navigationItems} />
131
+ {navigation}
78
132
  </div>
79
133
  </div>
80
134
  }
@@ -82,6 +136,15 @@ const SiteHeader: React.FC<SGDS.Component.SiteHeader> = ({
82
136
  );
83
137
  };
84
138
 
139
+ SiteHeader.Brand = Brand;
140
+ SiteHeader.Navigation = Navigation;
141
+ SiteHeader.Phase = Phase;
142
+ SiteHeader.Search = Search;
143
+
85
144
  SiteHeader.displayName = 'SiteHeader';
145
+ Brand.displayName = 'SiteHeader.Brand';
146
+ Navigation.displayName = 'SiteHeader.Navigation';
147
+ Phase.displayName = 'SiteHeader.Phase';
148
+ Search.displayName = 'SiteHeader.Search';
86
149
 
87
150
  export default SiteHeader;
@@ -2,23 +2,23 @@ import { test, expect } from 'vitest';
2
2
  import { render, screen, within } from '@testing-library/react';
3
3
  import SiteNavigation from './SiteNavigation';
4
4
 
5
- const ITEMS = [
6
- {title: 'About', href: '#about'},
7
- {title: 'Get started', href: '#get-started'},
8
- {title: 'Styles', href: '#styles'},
9
- {title: 'Components', href: '#components'},
10
- {title: 'Patterns', href: '#patterns'},
11
- {title: 'Guidance', href: '#guidance'},
12
- ]
5
+ const LINK_HREF = '#about';
6
+ const LINK_TEXT = 'About';
13
7
 
14
8
  test('renders correctly', () => {
15
9
  render(
16
- <SiteNavigation items={ITEMS}/>
10
+ <SiteNavigation>
11
+ <SiteNavigation.Item href="#about">About</SiteNavigation.Item>
12
+ <SiteNavigation.Item href="#get-started">Get started</SiteNavigation.Item>
13
+ <SiteNavigation.Item href="#styles">Styles</SiteNavigation.Item>
14
+ <SiteNavigation.Item href="#components">Components</SiteNavigation.Item>
15
+ <SiteNavigation.Item href="#patterns">Patterns</SiteNavigation.Item>
16
+ <SiteNavigation.Item href="#guidance">Guidance</SiteNavigation.Item>
17
+ </SiteNavigation>
17
18
  );
18
19
 
19
20
  const nav = screen.getByRole('navigation');
20
21
  const list = within(nav).getByRole('list');
21
- const listItems = within(list).getAllByRole('listitem');
22
22
 
23
23
  // check nav
24
24
  expect(nav).toHaveClass('ds_site-navigation');
@@ -27,25 +27,44 @@ test('renders correctly', () => {
27
27
  // check list
28
28
  expect(list.tagName).toEqual('UL');
29
29
  expect(list).toHaveClass('ds_site-navigation__list');
30
+ });
31
+
32
+ test('site navigation link renders correctly', () => {
33
+ render(
34
+ <SiteNavigation.Item href={LINK_HREF}>{LINK_TEXT}</SiteNavigation.Item>
35
+ );
36
+
37
+ const listItem =screen.getByRole('listitem');
38
+ const link = within(listItem).getByRole('link');
30
39
 
31
- // check items
32
- expect(listItems.length).toEqual(ITEMS.length);
40
+ expect(listItem).toHaveClass('ds_site-navigation__item');
33
41
 
34
- listItems.forEach((item, index) => {
35
- expect(item).toHaveClass('ds_site-navigation__item');
42
+ expect(link).toHaveClass('ds_site-navigation__link');
43
+ expect(link).not.toHaveClass('ds_current');
44
+ expect(link.textContent).toEqual(LINK_TEXT);
45
+ expect(link).toHaveAttribute('href', LINK_HREF)
46
+ });
47
+
48
+ test('site navigation link with custom element', () => {
49
+ render(
50
+ <SiteNavigation.Item href={LINK_HREF} linkComponent={
51
+ ({ className, ...props }) => (
52
+ <strong role="link" className={className} {...props}/>
53
+ )}>
54
+ {LINK_TEXT}
55
+ </SiteNavigation.Item>
56
+ );
36
57
 
37
- const link = within(item).getByRole('link');
58
+ const item = screen.getByRole('listitem');
59
+ const link = within(item).queryByRole('link');
38
60
 
39
- expect(link).toHaveClass('ds_site-navigation__link');
40
- expect(link).not.toHaveClass('ds_current');
41
- expect(link.textContent).toEqual(ITEMS[index].title);
42
- expect(link).toHaveAttribute('href', ITEMS[index].href)
43
- });
61
+ expect(link?.tagName).toEqual('STRONG');
62
+ expect(link?.textContent).toEqual(LINK_TEXT);
44
63
  });
45
64
 
46
65
  test('highlights current item', () => {
47
66
  render(
48
- <SiteNavigation data-test="foo" items={[{title: 'About', href: '#about', current: true}]}/>
67
+ <SiteNavigation.Item href={LINK_HREF} current>{LINK_TEXT}</SiteNavigation.Item>
49
68
  );
50
69
 
51
70
  const link = screen.getByRole('link');
@@ -55,7 +74,7 @@ test('highlights current item', () => {
55
74
 
56
75
  test('passing additional props', () => {
57
76
  render(
58
- <SiteNavigation data-test="foo" items={ITEMS}/>
77
+ <SiteNavigation data-test="foo"/>
59
78
  );
60
79
 
61
80
  const nav = screen.getByRole('navigation');
@@ -64,7 +83,7 @@ test('passing additional props', () => {
64
83
 
65
84
  test('passing additional CSS classes', () => {
66
85
  render(
67
- <SiteNavigation className="foo" items={ITEMS}/>
86
+ <SiteNavigation className="foo"/>
68
87
  );
69
88
 
70
89
  const nav = screen.getByRole('navigation');
@@ -1,27 +1,39 @@
1
- const SiteNavLink: React.FC<SGDS.Component.SiteNavigation.Link> = ({
2
- current=false,
1
+ const Item = ({
2
+ children,
3
+ current = false,
3
4
  href,
4
- title
5
- }) => {
5
+ linkComponent
6
+ }: SGDS.Component.SiteNavigation.Item) => {
7
+ const classNames = ['ds_site-navigation__link'];
8
+ let ariaCurrent: React.AriaAttributes["aria-current"];
9
+
10
+ if (current) {
11
+ classNames.push('ds_current');
12
+ ariaCurrent = 'page';
13
+ }
14
+
15
+ function processChildren(children: React.ReactNode) {
16
+ if (linkComponent) {
17
+ return linkComponent({ className: classNames.join(' '), href, children });
18
+ } else if (href) {
19
+ return <a href={href} aria-current={ariaCurrent ? ariaCurrent : undefined} className={classNames.join(' ')}>{children}</a>;
20
+ }
21
+ }
22
+
6
23
  return (
7
24
  <li
8
25
  className="ds_site-navigation__item"
9
26
  >
10
- <a
11
- href={href}
12
- className={[
13
- 'ds_site-navigation__link',
14
- current ? 'ds_current' : undefined
15
- ].join(' ')}>{title}</a>
27
+ {processChildren(children)}
16
28
  </li>
17
29
  );
18
30
  };
19
31
 
20
- const SiteNavigation: React.FC<SGDS.Component.SiteNavigation> = ({
32
+ const SiteNavigation = ({
33
+ children,
21
34
  className,
22
- items,
23
35
  ...props
24
- }) => {
36
+ }: SGDS.Component.SiteNavigation) => {
25
37
  return (
26
38
  <nav
27
39
  className={[
@@ -31,14 +43,14 @@ const SiteNavigation: React.FC<SGDS.Component.SiteNavigation> = ({
31
43
  {...props}
32
44
  >
33
45
  <ul className="ds_site-navigation__list">
34
- {items && items.map((item, index: number) => (
35
- <SiteNavLink current={item.current} href={item.href} title={item.title} key={`link-${index}`} />
36
- ))}
46
+ {children}
37
47
  </ul>
38
48
  </nav>
39
49
  );
40
50
  };
41
51
 
42
52
  SiteNavigation.displayName = 'SiteNavigation';
53
+ SiteNavigation.Item = Item;
54
+ Item.displayName = 'SiteNavigation.Item';
43
55
 
44
56
  export default SiteNavigation;
@@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
3
3
  import DSAutocomplete from '@scottish-government/design-system/src/components/autocomplete/autocomplete';
4
4
  import Button from '../Button/Button';
5
5
 
6
- const SiteSearch: React.FC<SGDS.Component.SiteSearch> = function ({
6
+ const SiteSearch = function ({
7
7
  action = '/search',
8
8
  autocompleteEndpoint,
9
9
  autocompleteSuggestionMappingFunction,
@@ -14,7 +14,7 @@ const SiteSearch: React.FC<SGDS.Component.SiteSearch> = function ({
14
14
  name = 'q',
15
15
  placeholder = 'Search',
16
16
  ...props
17
- }) {
17
+ }: SGDS.Component.SiteSearch) {
18
18
  const ref = useRef(null);
19
19
  const hasAutocomplete = !!autocompleteEndpoint;
20
20
  let autocompleteId = hasAutocomplete ? id + '-autocomplete' : '';
@@ -54,23 +54,35 @@ test('custom link target', () => {
54
54
  expect(skipLinksLink).toHaveAttribute('href', `#${CUSTOM_ID}`)
55
55
  });
56
56
 
57
- test('additional links', () => {
58
- const ITEMS = [
59
- { title: 'foo', targetId: 'bar' }
60
- ];
57
+ test('explicit links', () => {
58
+ const LINK_ID = 'foo';
59
+ const LINK_TEXT = 'bar';
61
60
 
62
61
  render(
63
- <SkipLinks items={ITEMS} />
62
+ <SkipLinks>
63
+ <SkipLinks.Link fragmentId={LINK_ID}>{LINK_TEXT}</SkipLinks.Link>
64
+ </SkipLinks>
64
65
  );
65
66
 
66
67
  const skipLinksList = screen.getByRole('list');
67
68
  const skipLinksListItems = within(skipLinksList).getAllByRole('listitem');
68
- const skipLinksSecondLink = within(skipLinksList).getAllByRole('link')[1];
69
+ const skipLinksSecondLink = within(skipLinksList).getByRole('link');
69
70
 
70
- expect(skipLinksListItems.length).toEqual(2);
71
- expect(skipLinksSecondLink).toHaveAttribute('href', `#${ITEMS[0].targetId}`);
72
- expect(skipLinksSecondLink.textContent).toEqual(ITEMS[0].title);
73
- })
71
+ expect(skipLinksListItems.length).toEqual(1);
72
+ expect(skipLinksSecondLink).toHaveAttribute('href', `#${LINK_ID}`);
73
+ expect(skipLinksSecondLink.textContent).toEqual(LINK_TEXT);
74
+ });
75
+
76
+ test('static skip links', () => {
77
+ render(
78
+ <SkipLinks isStatic />
79
+ );
80
+
81
+ const skipLinksList = screen.getByRole('list');
82
+ const skipLinksContainer = skipLinksList.parentElement;
83
+
84
+ expect(skipLinksContainer).toHaveClass('ds_skip-links--static');
85
+ });
74
86
 
75
87
  test('passing additional props', () => {
76
88
  render(
@@ -1,39 +1,40 @@
1
- export const SkipLink: React.FC<SGDS.Component.SkipLinks.Link> = ({
2
- targetId,
3
- title
4
- }) => {
1
+ const Link = ({
2
+ children,
3
+ fragmentId
4
+ }: SGDS.Component.SkipLinks.Link) => {
5
5
  return (
6
6
  <li
7
7
  className="ds_skip-links__item"
8
8
  >
9
- <a href={`#${targetId}`} className="ds_skip-links__link">{ title }</a>
9
+ <a href={`#${fragmentId}`} className="ds_skip-links__link">{ children }</a>
10
10
  </li>
11
11
  );
12
12
  };
13
13
 
14
- const SkipLinks: React.FC<SGDS.Component.SkipLinks> = ({
15
- items,
14
+ const SkipLinks = ({
15
+ children,
16
16
  mainContentId = 'main-content',
17
17
  mainLinkText = 'Skip to main content',
18
+ isStatic,
18
19
  ...props
19
- }) => {
20
+ }: SGDS.Component.SkipLinks) => {
20
21
  return (
21
22
  <div
22
- className="ds_skip-links"
23
+ className={[
24
+ 'ds_skip-links',
25
+ isStatic && 'ds_skip-links--static',
26
+ ].join(' ')}
23
27
  {...props}
24
28
  >
25
29
  <ul className="ds_skip-links__list">
26
- <SkipLink title={mainLinkText} targetId={mainContentId}/>
27
-
28
- {items && items.map((item, index: number) => (
29
- <SkipLink title={item.title} targetId={item.targetId} key={`skiplink-${index}`}/>
30
- ))}
30
+ {children ? children : <Link fragmentId={mainContentId}>{mainLinkText}</Link>}
31
31
  </ul>
32
32
  </div>
33
33
  );
34
34
  };
35
35
 
36
36
  SkipLinks.displayName = 'SkipLinks';
37
- SkipLink.displayName = 'SkipLink';
37
+ Link.displayName = 'SkipLink';
38
+ SkipLinks.Link = Link;
38
39
 
39
40
  export default SkipLinks;
@@ -30,29 +30,21 @@ const ITEMS = [
30
30
  const TITLE_TEXT = 'Joe Bloggs';
31
31
 
32
32
  test('summary card renders correctly', () => {
33
- const DESCRIBEDBY_ID = 'summary-card-joe-bloggs';
34
-
35
33
  render(
36
- <>
37
- <SummaryCard
38
- data-testid="foo"
39
- actions={ACTIONS}
40
- items={ITEMS}
41
- title={TITLE_TEXT}
42
- />
43
-
44
- <SummaryList data-testid="bar" items={ITEMS}/>
45
- </>
34
+ <SummaryCard
35
+ data-testid="foo"
36
+ title={TITLE_TEXT}
37
+ >
38
+ <SummaryCard.Action href={ACTIONS[0].href}>{ACTIONS[0].title}</SummaryCard.Action>
39
+ <SummaryList data-testid="bar" />
40
+ </SummaryCard>
46
41
  );
47
42
 
48
43
  const summaryCard = screen.getByTestId('foo');
49
44
  const title = within(summaryCard).getByRole('heading');
50
45
  const header = title.parentElement as HTMLElement;
51
46
  const content = header?.nextElementSibling as HTMLElement;
52
- const actionsList = within(header).getByRole('list')
53
-
54
- const thisList = within(content).getByRole('list');
55
- const comparisonList = screen.getByTestId('bar');
47
+ const action = within(header).getByRole('link');
56
48
 
57
49
  expect(summaryCard).toHaveClass('ds_summary-card');
58
50
 
@@ -60,34 +52,20 @@ test('summary card renders correctly', () => {
60
52
  expect(header?.tagName).toEqual('DIV');
61
53
 
62
54
  expect(title).toHaveClass('ds_summary-card__header-title');
63
- expect(title).toHaveAttribute('id', DESCRIBEDBY_ID);
64
55
  expect(title?.tagName).toEqual('H3');
65
56
  expect(title.textContent).toEqual(TITLE_TEXT);
66
57
 
67
- expect(actionsList?.tagName).toEqual('UL');
68
- expect(actionsList.children.length).toEqual(ACTIONS.length);
69
- expect(actionsList.children[0]).toHaveClass('ds_summary-card__actions-list-item');
70
- expect(actionsList.children[0].tagName).toEqual('LI');
71
- expect(actionsList.children[0].innerHTML).toEqual('<a aria-describedby="summary-card-joe-bloggs" class="ds_link" href="#foo">Change</a>');
72
- expect(actionsList.children[1].innerHTML).toEqual('<button aria-describedby="summary-card-joe-bloggs" class="ds_link" type="button">Delete</button>');
73
-
74
- fireEvent.click(actionsList.children[1].children[0]);
75
-
76
- expect(ONCLICK_FUNCTION).toHaveBeenCalled();
58
+ expect(action).toHaveAttribute('aria-describedby', title.id);
77
59
 
78
60
  expect(content).toHaveClass('ds_summary-card__content');
79
61
  expect(content?.tagName).toEqual('DIV');
80
-
81
- expect(thisList?.innerHTML).toEqual(comparisonList?.innerHTML);
82
62
  });
83
63
 
84
64
  test('custom heading level', () => {
85
65
  render(
86
66
  <SummaryCard
87
67
  data-testid="foo"
88
- actions={ACTIONS}
89
68
  headingLevel="h2"
90
- items={ITEMS}
91
69
  title={TITLE_TEXT}
92
70
  />
93
71
  );
@@ -98,12 +76,32 @@ test('custom heading level', () => {
98
76
  expect(title?.tagName).toEqual('H2');
99
77
  });
100
78
 
79
+ test('summary card with multiple actions renders actions as a list', () => {
80
+ render(
81
+ <SummaryCard
82
+ data-testid="foo"
83
+ title={TITLE_TEXT}
84
+ >
85
+ <SummaryCard.Action href={ACTIONS[0].href}>{ACTIONS[0].title}</SummaryCard.Action>
86
+ <SummaryCard.Action onclick={ACTIONS[1].onclick}>{ACTIONS[1].title}</SummaryCard.Action>
87
+ <SummaryList data-testid="bar" />
88
+ </SummaryCard>
89
+ );
90
+
91
+ const summaryCard = screen.getByTestId('foo');
92
+ const title = within(summaryCard).getByRole('heading');
93
+ const header = title.parentElement as HTMLElement;
94
+ const actionsList = within(header).getByRole('list')
95
+
96
+ expect(actionsList?.tagName).toEqual('UL');
97
+ expect(actionsList.children[0]).toHaveClass('ds_summary-card__actions-list-item');
98
+ expect(actionsList.children[0].tagName).toEqual('LI');
99
+ });
100
+
101
101
  test('passing additional props', () => {
102
102
  render(
103
103
  <SummaryCard
104
104
  data-testid="foo"
105
- actions={ACTIONS}
106
- items={ITEMS}
107
105
  title={TITLE_TEXT}
108
106
  data-test="foo"
109
107
  />
@@ -117,8 +115,6 @@ test('passing additional CSS classes', () => {
117
115
  render(
118
116
  <SummaryCard
119
117
  data-testid="foo"
120
- actions={ACTIONS}
121
- items={ITEMS}
122
118
  title={TITLE_TEXT}
123
119
  className="foo"
124
120
  />