@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
@@ -0,0 +1,80 @@
1
+ import { test, expect, vi } from 'vitest';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import ActionLink from './ActionLink';
4
+
5
+ const ONCLICK_FUNCTION = vi.fn();
6
+ const ACTION_HREF = "#foo"
7
+ const ACTION_ONCLICK = ONCLICK_FUNCTION;
8
+ const ACTION_TEXT = 'Name';
9
+ const DESCRIBEDBY_ID = 'q1-name';
10
+
11
+ test('button action', () => {
12
+ render(
13
+ <ActionLink
14
+ describedby={DESCRIBEDBY_ID}
15
+ href={undefined}
16
+ onclick={ACTION_ONCLICK}
17
+ >
18
+ {ACTION_TEXT}
19
+ </ActionLink>
20
+ );
21
+
22
+ const action = screen.getByRole('button');
23
+
24
+ expect(action).toHaveClass('ds_link');
25
+ expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
26
+ expect(action).toHaveAttribute('type', 'button');
27
+ expect(action).not.toHaveAttribute('href');
28
+ expect(action.tagName).toEqual('BUTTON');
29
+ expect(action.textContent).toEqual(ACTION_TEXT);
30
+
31
+ fireEvent.click(action);
32
+
33
+ expect(ONCLICK_FUNCTION).toHaveBeenCalled();
34
+ });
35
+
36
+ test('link action', () => {
37
+ render(
38
+ <ActionLink
39
+ describedby={DESCRIBEDBY_ID}
40
+ href={ACTION_HREF}
41
+ onclick={ACTION_ONCLICK}
42
+ >
43
+ {ACTION_TEXT}
44
+ </ActionLink >
45
+ );
46
+
47
+ const action = screen.getByRole('link');
48
+
49
+ expect(action).toHaveClass('ds_link');
50
+ expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
51
+ expect(action).toHaveAttribute('href', ACTION_HREF);
52
+ expect(action).not.toHaveAttribute('type');
53
+ expect(action.tagName).toEqual('A');
54
+ expect(action.textContent).toEqual(ACTION_TEXT);
55
+ });
56
+
57
+ test('action with custom element', () => {
58
+ render(
59
+ <ActionLink
60
+ describedby={DESCRIBEDBY_ID}
61
+ href={ACTION_HREF}
62
+ onclick={ACTION_ONCLICK}
63
+ linkComponent={
64
+ ({ className, ...props }) => (
65
+ <strong role="link" className={className} {...props}/>
66
+ )}>
67
+ {ACTION_TEXT}
68
+ </ActionLink>
69
+ );
70
+
71
+ const action = screen.getByRole('link');
72
+
73
+ expect(action).toHaveAttribute('aria-describedby', DESCRIBEDBY_ID);
74
+ expect(action?.tagName).toEqual('STRONG');
75
+ expect(action?.textContent).toEqual(ACTION_TEXT);
76
+
77
+ fireEvent.click(action);
78
+
79
+ expect(ONCLICK_FUNCTION).toHaveBeenCalled();
80
+ });
@@ -0,0 +1,27 @@
1
+ const ActionLink = ({
2
+ children,
3
+ describedby,
4
+ href,
5
+ linkComponent,
6
+ onclick
7
+ }: SGDS.Common.ActionLink) => {
8
+ const CLASSNAME = 'ds_link';
9
+
10
+ function processChildren(children: React.ReactNode) {
11
+ if (linkComponent) {
12
+ return linkComponent({ className: CLASSNAME, href, children, onClick: onclick, 'aria-describedby': describedby });
13
+ } else if (href) {
14
+ return <a aria-describedby={describedby} onClick={onclick} href={href} className={CLASSNAME}>{children}</a>;
15
+ } else {
16
+ return <button type="button" aria-describedby={describedby} onClick={onclick} className={CLASSNAME}>{children}</button>;
17
+ }
18
+ }
19
+
20
+ return (
21
+ processChildren(children)
22
+ );
23
+ };
24
+
25
+ ActionLink.displayName = 'ActionLink';
26
+
27
+ export default ActionLink;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Wraps all children in a specified HTML tag if a condition is met.
3
3
  */
4
- const ConditionalWrapper: React.FC<SGDS.Common.ConditionalWrapper> = ({ condition, wrapper, children }) =>
4
+ const ConditionalWrapper = ({ condition, wrapper, children }:SGDS.Common.ConditionalWrapper) =>
5
5
  condition ? wrapper(children) : children;
6
6
 
7
7
  ConditionalWrapper.displayName = 'ConditionalWrapper';
@@ -1,22 +1,18 @@
1
1
  import React from 'react';
2
2
  import * as FileIcons from '../images/documents';
3
3
 
4
- const FileIcon: React.FC<SGDS.Common.FileIcon> = ({
4
+ const FileIcon = ({
5
5
  ariaLabel = '',
6
6
  className,
7
7
  icon
8
- }) => {
9
- const Component = React.createElement(FileIcons[icon],
10
- {
11
- className: className,
12
- 'aria-label': ariaLabel
13
- }
14
- );
8
+ }: SGDS.Common.FileIcon) => {
9
+ const FileIconComponent = FileIcons[icon];
15
10
 
16
11
  return (
17
- <>
18
- {Component}
19
- </>
12
+ <FileIconComponent
13
+ aria-label={ariaLabel}
14
+ className={className}
15
+ />
20
16
  );
21
17
  };
22
18
 
@@ -1,9 +1,9 @@
1
- const HintText: React.FC<SGDS.Common.HintText> = ({
1
+ const HintText = ({
2
2
  children,
3
3
  id,
4
4
  text,
5
5
  ...props
6
- }) => {
6
+ }: SGDS.Common.HintText) => {
7
7
  return (
8
8
  <p
9
9
  className="ds_hint-text"
@@ -1,30 +1,26 @@
1
1
  import React from 'react';
2
2
  import * as Icons from '../images/icons';
3
3
 
4
- const Icon: React.FC<SGDS.Common.Icon> = ({
4
+ const Icon = ({
5
5
  ariaLabel,
6
6
  className,
7
7
  fill,
8
8
  icon,
9
9
  iconSize
10
- }) => {
11
- const Component = React.createElement(Icons[icon],
12
- {
13
- 'aria-hidden': ariaLabel ? undefined : true,
14
- 'aria-label': ariaLabel,
15
- className: [
16
- 'ds_icon',
17
- className,
18
- fill && 'ds_icon--fill',
19
- iconSize && `ds_icon--${iconSize}`
20
- ].join(' ')
21
- }
22
- );
10
+ }: SGDS.Common.Icon) => {
11
+ const IconComponent = Icons[icon];
23
12
 
24
13
  return (
25
- <>
26
- {Component}
27
- </>
14
+ <IconComponent
15
+ aria-hidden={ariaLabel ? undefined : true}
16
+ aria-label={ariaLabel}
17
+ className={[
18
+ 'ds_icon',
19
+ className,
20
+ fill && 'ds_icon--fill',
21
+ iconSize && `ds_icon--${iconSize}`
22
+ ].join(' ')}
23
+ />
28
24
  );
29
25
  };
30
26
 
@@ -1,7 +1,7 @@
1
- const ScreenReaderText: React.FC<SGDS.Common.ScreenReaderText> = ({
1
+ const ScreenReaderText = ({
2
2
  children,
3
3
  ...props
4
- }) => {
4
+ }: SGDS.Common.ScreenReaderText) => {
5
5
  return (
6
6
  <span
7
7
  className="visually-hidden"
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Wraps all children in a specified HTML tag.
3
3
  */
4
- const WrapperTag: React.FC<SGDS.Common.WrapperTag> = ({
4
+ const WrapperTag = ({
5
5
  children,
6
6
  tagName = 'div',
7
7
  ...props
8
- }) => {
8
+ }: SGDS.Common.WrapperTag) => {
9
9
  const TagName = tagName;
10
10
  return <TagName {...props}>{children}</TagName>;
11
11
  };
@@ -216,7 +216,7 @@ test('passing additional props to accordion item', () => {
216
216
  );
217
217
 
218
218
  const accordionItem = screen.getByTestId(ACCORDION_ITEM_ID);
219
- expect(accordionItem?.dataset.test).toEqual('foo');
219
+ expect(accordionItem.dataset.test).toEqual('foo');
220
220
  });
221
221
 
222
222
  test('passing additional CSS classes', () => {
@@ -5,7 +5,7 @@ import DSAccordion from '@scottish-government/design-system/src/components/accor
5
5
 
6
6
  let accordionItemCounter = 0;
7
7
 
8
- const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
8
+ const AccordionItem = ({
9
9
  children,
10
10
  className,
11
11
  headingLevel = 'h3',
@@ -13,7 +13,7 @@ const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
13
13
  open = false,
14
14
  title,
15
15
  ...props
16
- }) => {
16
+ }: SGDS.Component.Accordion.Item) => {
17
17
  accordionItemCounter = accordionItemCounter + 1;
18
18
  const processedId = rawId || `accordion-item-${accordionItemCounter}`;
19
19
  return (
@@ -43,7 +43,7 @@ const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
43
43
  >
44
44
  {title}
45
45
  </WrapperTag>
46
- <span className='ds_accordion-item__indicator' />
46
+ <span className="ds_accordion-item__indicator" />
47
47
  <label
48
48
  className="ds_accordion-item__label"
49
49
  htmlFor={`${processedId}-control`}
@@ -58,14 +58,13 @@ const AccordionItem: React.FC<SGDS.Component.Accordion.Item> = ({
58
58
  );
59
59
  };
60
60
 
61
- const Accordion: React.FC<SGDS.Component.Accordion>
62
- & { Item: React.FC<SGDS.Component.Accordion.Item> } = ({
61
+ const Accordion = ({
63
62
  children,
64
63
  className,
65
64
  headingLevel = 'h3',
66
65
  hideOpenAll,
67
66
  ...props
68
- }) => {
67
+ }: SGDS.Component.Accordion) => {
69
68
  const ref = useRef(null);
70
69
 
71
70
  useEffect(() => {
@@ -112,7 +111,7 @@ const Accordion: React.FC<SGDS.Component.Accordion>
112
111
  };
113
112
 
114
113
  Accordion.displayName = 'Accordion';
115
- AccordionItem.displayName = 'AccordionItem';
114
+ AccordionItem.displayName = 'Accordion.Item';
116
115
  Accordion.Item = AccordionItem;
117
116
 
118
117
  export default Accordion;
@@ -2,12 +2,12 @@ import React, { Children, useEffect, useRef } from 'react';
2
2
  // @ts-ignore
3
3
  import DSAspectBox from '@scottish-government/design-system/src/components/aspect-box/aspect-box-fallback';
4
4
 
5
- const AspectBox: React.FC<SGDS.Component.AspectBox> = ({
5
+ const AspectBox = ({
6
6
  children,
7
7
  className,
8
8
  ratio,
9
9
  ...props
10
- }) => {
10
+ }: SGDS.Component.AspectBox) => {
11
11
  const ref = useRef(null);
12
12
 
13
13
  useEffect(() => {
@@ -3,11 +3,11 @@ import Icon from '../../common/Icon';
3
3
  // @ts-ignore
4
4
  import DSBackToTop from '@scottish-government/design-system/src/components/back-to-top/back-to-top';
5
5
 
6
- const BackToTop: React.FC<SGDS.Component.BackToTop> = ({
6
+ const BackToTop = ({
7
7
  className,
8
8
  href = '#page-top',
9
9
  ...props
10
- }) => {
10
+ }: SGDS.Component.BackToTop) => {
11
11
  const ref = useRef(null);
12
12
 
13
13
  useEffect(() => {
@@ -2,15 +2,16 @@ import { test, expect } from 'vitest';
2
2
  import { render, screen, within } from '@testing-library/react';
3
3
  import Breadcrumbs from './Breadcrumbs';
4
4
 
5
- const ITEMS = [
6
- { href: 'home', title: 'Home' },
7
- { href: 'category', title: 'Category' },
8
- { title: 'Page' }
9
- ];
5
+ const LINK_HREF = '#home';
6
+ const LINK_TEXT = 'Home';
10
7
 
11
- test('renders correctly', () => {
8
+ test('breadcrumbs render correctly', () => {
12
9
  render(
13
- <Breadcrumbs items={ITEMS} />
10
+ <Breadcrumbs>
11
+ <Breadcrumbs.Item href="home">Home</Breadcrumbs.Item>
12
+ <Breadcrumbs.Item href="category">Category</Breadcrumbs.Item>
13
+ <Breadcrumbs.Item>Page</Breadcrumbs.Item>
14
+ </Breadcrumbs>
14
15
  );
15
16
 
16
17
  const nav = screen.getByRole('navigation');
@@ -23,67 +24,98 @@ test('renders correctly', () => {
23
24
  // check list
24
25
  expect(list.tagName).toEqual('OL');
25
26
  expect(list).toHaveClass('ds_breadcrumbs');
27
+ });
28
+
29
+ test('passing additional props to breadcrumbs', () => {
30
+ render(
31
+ <Breadcrumbs data-test="foo"/>
32
+ );
33
+
34
+ const nav = screen.getByRole('navigation');
35
+ expect(nav.dataset.test).toEqual('foo');
36
+ });
26
37
 
27
- // check items
28
- expect(listItems.length).toEqual(ITEMS.length);
38
+ test('passing additional CSS classes to breadcrumbs', () => {
39
+ render(
40
+ <Breadcrumbs className="foo"/>
41
+ );
42
+
43
+ const nav = screen.getByRole('navigation');
44
+ expect(nav).toHaveClass('foo');
45
+ });
29
46
 
30
- listItems.forEach((item, index) => {
31
- expect(item).toHaveClass('ds_breadcrumbs__item');
47
+ test('breadcrumb item with link', () => {
48
+ render(
49
+ <Breadcrumbs.Item href={LINK_HREF}>{LINK_TEXT}</Breadcrumbs.Item>
50
+ );
32
51
 
33
- const link = within(item).queryByRole('link');
52
+ const item = screen.getByRole('listitem');
53
+ const link = within(item).getByRole('link');
34
54
 
35
- if (index + 1 < listItems.length) {
36
- expect(link).toBeDefined();
37
- expect(link).toHaveClass('ds_breadcrumbs__link');
38
- } else {
39
- expect(link).toBeNull();
40
- }
41
- });
55
+ expect(item).toHaveClass('ds_breadcrumbs__item');
56
+ expect(item?.tagName).toEqual('LI');
42
57
 
43
- // check href matches correct item
44
- const categoryLink = within(list).getByRole('link', { name: 'Category' });
45
- expect(categoryLink).toHaveAttribute('href', 'category');
58
+ expect(link).toHaveClass('ds_breadcrumbs__link');
59
+ expect(link).toHaveAttribute('href', LINK_HREF);
60
+ expect(link?.tagName).toEqual('A');
61
+ expect(link?.textContent).toEqual(LINK_TEXT);
46
62
  });
47
63
 
48
- test('renders with last item hidden', () => {
64
+ test('breadcrumb item without link', () => {
49
65
  render(
50
- <Breadcrumbs
51
- hideLastItem
52
- items={ITEMS}
53
- />
66
+ <Breadcrumbs.Item>{LINK_TEXT}</Breadcrumbs.Item>
54
67
  );
55
68
 
56
- // check still 3 items
57
- const list = screen.getByRole('list');
58
- const listItems = within(list).getAllByRole('listitem');
59
- expect(listItems.length).toEqual(3);
69
+ const item = screen.getByRole('listitem');
70
+ const link = within(item).queryByRole('link');
71
+
72
+ expect(item).toHaveClass('ds_breadcrumbs__item');
73
+ expect(item?.tagName).toEqual('LI');
74
+ expect(item?.textContent).toEqual(LINK_TEXT);
60
75
 
61
- // check last item is hidden
62
- const pageCrumb = within(list).getByText('Page');
63
- expect(pageCrumb).toHaveClass('visually-hidden');
64
- expect(pageCrumb.tagName).toEqual('LI');
76
+ expect(link).not.toBeInTheDocument();
65
77
  });
66
78
 
67
- test('passing additional props', () => {
79
+ test('hidden breadcrumb item', () => {
68
80
  render(
69
- <Breadcrumbs
70
- items={ITEMS}
71
- data-test="foo"
72
- />
81
+ <Breadcrumbs.Item data-testid="Breadcrumbs.Item" isHidden>{LINK_TEXT}</Breadcrumbs.Item>
73
82
  );
74
83
 
75
- const nav = screen.getByRole('navigation');
84
+ const item = screen.getByRole('listitem');
85
+ expect(item).toHaveClass('visually-hidden');
86
+ });
87
+
88
+ test('renders breadcrumb with custom element', () => {
89
+ render(
90
+ <Breadcrumbs.Item href="category" linkComponent={
91
+ ({ className, ...props }) => (
92
+ <span role="link" className={className} {...props}/>
93
+ )}>
94
+ {LINK_TEXT}
95
+ </Breadcrumbs.Item>
96
+ );
97
+
98
+ const item = screen.getByRole('listitem');
99
+ const link = within(item).queryByRole('link');
100
+
101
+ expect(link?.tagName).toEqual('SPAN');
102
+ expect(link?.textContent).toEqual(LINK_TEXT);
103
+ });
104
+
105
+ test('passing additional props to breadcrumb item', () => {
106
+ render(
107
+ <Breadcrumbs.Item data-test="foo"/>
108
+ );
109
+
110
+ const nav = screen.getByRole('listitem');
76
111
  expect(nav.dataset.test).toEqual('foo');
77
112
  });
78
113
 
79
- test('passing additional CSS classes', () => {
114
+ test('passing additional CSS classes to breadcrumb item', () => {
80
115
  render(
81
- <Breadcrumbs
82
- items={ITEMS}
83
- className="foo"
84
- />
116
+ <Breadcrumbs.Item className="foo"/>
85
117
  );
86
118
 
87
- const nav = screen.getByRole('navigation');
119
+ const nav = screen.getByRole('listitem');
88
120
  expect(nav).toHaveClass('foo');
89
121
  });
@@ -1,53 +1,53 @@
1
- const Breadcrumb: React.FC<SGDS.Component.Breadcrumbs.Item> = ({
2
- hidden,
1
+ const BreadcrumbItem = ({
2
+ children,
3
+ isHidden,
3
4
  href,
4
- title
5
- }) => {
5
+ linkComponent,
6
+ ...props
7
+ }: SGDS.Component.Breadcrumbs.Item) => {
8
+ const BREADCRUMB_LINK_CLASSNAME = 'ds_breadcrumbs__link';
9
+
10
+ function processChildren(children: React.ReactNode) {
11
+ if (linkComponent) {
12
+ return linkComponent({ className: BREADCRUMB_LINK_CLASSNAME, href, children });
13
+ } else if (href) {
14
+ return <a href={href} className={BREADCRUMB_LINK_CLASSNAME}>{children}</a>;
15
+ } else {
16
+ return children;
17
+ }
18
+ }
19
+
6
20
  return (
7
- <li
8
- className={[
9
- 'ds_breadcrumbs__item',
10
- hidden && 'visually-hidden'
11
- ].join(' ')}
21
+ <li className={[
22
+ 'ds_breadcrumbs__item',
23
+ isHidden && 'visually-hidden'
24
+ ].join(' ')}
25
+ {...props}
12
26
  >
13
-
14
- {href ? (<a className="ds_breadcrumbs__link" href={href}>
15
- {title}
16
- </a>) : (title)}
27
+ {processChildren(children)}
17
28
  </li>
18
29
  );
19
30
  };
20
31
 
21
- /**
22
- * @param {boolean} hideLastItem
23
- * @param {Array} items
24
- * @param {Object} props - Properties for the element
25
- * @returns {JSX.Element} - The element
26
- */
27
- const Breadcrumbs: React.FC<SGDS.Component.Breadcrumbs> = ({
28
- hideLastItem,
29
- items,
32
+ const Breadcrumbs = ({
33
+ children,
30
34
  ...props
31
- }) => {
35
+ }: SGDS.Component.Breadcrumbs & { Item: SGDS.Component.Breadcrumbs.Item }) => {
32
36
  return (
33
37
  <nav
34
38
  aria-label="Breadcrumb"
35
39
  {...props}
36
40
  >
37
41
  <ol className="ds_breadcrumbs">
38
- {items && items.map((item, index: number) => (
39
- <Breadcrumb
40
- title={item.title}
41
- href={item.href}
42
- hidden={(hideLastItem) && index + 1 === items.length}
43
- key={'breadcrumb' + index}
44
- />
45
- ))}
42
+ {children}
46
43
  </ol>
47
44
  </nav>
48
45
  );
49
46
  };
50
47
 
51
48
  Breadcrumbs.displayName = 'Breadcrumbs';
49
+ BreadcrumbItem.displayName = 'Breadcrumbs.Item';
50
+
51
+ Breadcrumbs.Item = BreadcrumbItem;
52
52
 
53
53
  export default Breadcrumbs;
@@ -2,7 +2,7 @@ import Icon from '../../common/Icon';
2
2
  import ScreenReaderText from '../../common/ScreenReaderText';
3
3
  import WrapperTag from '../../common/WrapperTag';
4
4
 
5
- const Button: React.FC<SGDS.Component.Button> = ({
5
+ const Button = ({
6
6
  buttonStyle,
7
7
  children,
8
8
  className,
@@ -15,7 +15,7 @@ const Button: React.FC<SGDS.Component.Button> = ({
15
15
  type = 'button',
16
16
  width,
17
17
  ...props
18
- }) => {
18
+ }: SGDS.Component.Button) => {
19
19
  // determine which HTML tag to use
20
20
  let tagName = 'button';
21
21
  if (href) {