@openedx/paragon 22.13.0 → 22.15.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 (258) hide show
  1. package/dist/Alert/_variables.scss +2 -1
  2. package/dist/Annotation/index.js.map +1 -1
  3. package/dist/Annotation/index.scss +6 -5
  4. package/dist/Avatar/index.js.map +1 -1
  5. package/dist/AvatarButton/index.js.map +1 -1
  6. package/dist/Breadcrumb/index.js.map +1 -1
  7. package/dist/Bubble/index.js +1 -0
  8. package/dist/Bubble/index.js.map +1 -1
  9. package/dist/Bubble/index.scss +3 -2
  10. package/dist/Button/deprecated/index.js.map +1 -1
  11. package/dist/Button/index.scss +19 -18
  12. package/dist/Card/CardCarousel/CardCarouselHeader.js +2 -2
  13. package/dist/Card/CardCarousel/CardCarouselHeader.js.map +1 -1
  14. package/dist/Card/CardFooter.js.map +1 -1
  15. package/dist/Card/CardHeader.js +1 -1
  16. package/dist/Card/CardHeader.js.map +1 -1
  17. package/dist/Card/CardImageCap.js.map +1 -1
  18. package/dist/Card/CardStatus.js.map +1 -1
  19. package/dist/Card/_variables.scss +3 -2
  20. package/dist/Card/index.js.map +1 -1
  21. package/dist/Card/index.scss +10 -9
  22. package/dist/Chip/ChipIcon.d.ts +1 -1
  23. package/dist/Chip/index.js +1 -0
  24. package/dist/Chip/index.js.map +1 -1
  25. package/dist/ChipCarousel/index.js.map +1 -1
  26. package/dist/Collapsible/index.js.map +1 -1
  27. package/dist/ColorPicker/index.js +1 -1
  28. package/dist/ColorPicker/index.js.map +1 -1
  29. package/dist/ColorPicker/index.scss +2 -1
  30. package/dist/DataTable/CollapsibleButtonGroup.js +2 -2
  31. package/dist/DataTable/CollapsibleButtonGroup.js.map +1 -1
  32. package/dist/DataTable/DropdownFilters.js +1 -1
  33. package/dist/DataTable/DropdownFilters.js.map +1 -1
  34. package/dist/DataTable/TableRow.js.map +1 -1
  35. package/dist/DataTable/filters/CheckboxFilter.js.map +1 -1
  36. package/dist/DataTable/filters/DropdownFilter.js.map +1 -1
  37. package/dist/DataTable/filters/MultiSelectDropdownFilter.js.map +1 -1
  38. package/dist/DataTable/filters/TextFilter.js.map +1 -1
  39. package/dist/DataTable/index.scss +14 -13
  40. package/dist/DataTable/utils/getVisibleColumns.js +1 -1
  41. package/dist/DataTable/utils/getVisibleColumns.js.map +1 -1
  42. package/dist/Dropdown/_variables.scss +2 -1
  43. package/dist/Dropdown/deprecated/DropdownMenu.js +15 -19
  44. package/dist/Dropdown/deprecated/DropdownMenu.js.map +1 -1
  45. package/dist/Dropdown/deprecated/index.js +1 -1
  46. package/dist/Dropdown/deprecated/index.js.map +1 -1
  47. package/dist/Dropdown/index.js.map +1 -1
  48. package/dist/Dropzone/DefaultContent.js.map +1 -1
  49. package/dist/Dropzone/UploadProgress.js.map +1 -1
  50. package/dist/Dropzone/index.scss +3 -2
  51. package/dist/Fieldset/index.js.map +1 -1
  52. package/dist/Form/FormAutosuggest.js +1 -1
  53. package/dist/Form/FormAutosuggest.js.map +1 -1
  54. package/dist/Form/FormControl.js.map +1 -1
  55. package/dist/Form/FormControlDecorator.js.map +1 -1
  56. package/dist/Form/FormGroupContext.d.ts +1 -1
  57. package/dist/Form/FormGroupContext.js.map +1 -1
  58. package/dist/Form/FormText.js.map +1 -1
  59. package/dist/Form/_index.scss +9 -7
  60. package/dist/Form/_variables.scss +4 -2
  61. package/dist/Form/fieldUtils.js.map +1 -1
  62. package/dist/Hyperlink/index.d.ts +10 -5
  63. package/dist/Hyperlink/index.js +57 -25
  64. package/dist/Hyperlink/index.js.map +1 -1
  65. package/dist/Hyperlink/index.scss +3 -1
  66. package/dist/Icon/index.js.map +1 -1
  67. package/dist/IconButton/index.d.ts +13 -8
  68. package/dist/IconButton/index.js.map +1 -1
  69. package/dist/IconButtonToggle/index.js.map +1 -1
  70. package/dist/IconButtonToggle/index.scss +3 -1
  71. package/dist/Input/index.js.map +1 -1
  72. package/dist/InputSelect/index.js.map +1 -1
  73. package/dist/Layout/index.js.map +1 -1
  74. package/dist/ListBox/index.js.map +1 -1
  75. package/dist/ListBoxOption/index.js.map +1 -1
  76. package/dist/Menu/SelectMenu.js +1 -1
  77. package/dist/Menu/SelectMenu.js.map +1 -1
  78. package/dist/Menu/index.js +1 -1
  79. package/dist/Menu/index.js.map +1 -1
  80. package/dist/Modal/ModalContext.d.ts +1 -1
  81. package/dist/Modal/ModalDialog.d.ts +1 -1
  82. package/dist/Modal/ModalDialog.js.map +1 -1
  83. package/dist/Modal/ModalDialogBody.js +1 -1
  84. package/dist/Modal/ModalDialogBody.js.map +1 -1
  85. package/dist/Modal/ModalDialogHeroBackground.js.map +1 -1
  86. package/dist/Modal/ModalLayer.d.ts +3 -3
  87. package/dist/Modal/ModalLayer.js.map +1 -1
  88. package/dist/Modal/ModalPopup.js.map +1 -1
  89. package/dist/Modal/_ModalDialog.scss +3 -1
  90. package/dist/Modal/index.js +3 -1
  91. package/dist/Modal/index.js.map +1 -1
  92. package/dist/Modal/index.scss +3 -5
  93. package/dist/Nav/_mixins.scss +3 -1
  94. package/dist/Overlay/index.d.ts +2 -2
  95. package/dist/PageBanner/index.js.map +1 -1
  96. package/dist/PageBanner/index.scss +2 -1
  97. package/dist/Pagination/PaginationContext.js.map +1 -1
  98. package/dist/Pagination/index.js.map +1 -1
  99. package/dist/Popover/_variables.scss +2 -1
  100. package/dist/Popover/index.js.map +1 -1
  101. package/dist/ProductTour/Checkpoint.scss +9 -8
  102. package/dist/ProductTour/index.js +1 -1
  103. package/dist/ProductTour/index.js.map +1 -1
  104. package/dist/ProgressBar/index.js.map +1 -1
  105. package/dist/Scrollable/index.js +1 -1
  106. package/dist/Scrollable/index.js.map +1 -1
  107. package/dist/SearchField/SearchFieldAdvanced.js.map +1 -1
  108. package/dist/SearchField/index.scss +2 -1
  109. package/dist/SelectableBox/SelectableBoxSet.js.map +1 -1
  110. package/dist/Sheet/index.js.map +1 -1
  111. package/dist/Stack/index.js.map +1 -1
  112. package/dist/StatefulButton/index.js.map +1 -1
  113. package/dist/StatusAlert/index.js.map +1 -1
  114. package/dist/Stepper/StepperHeader.js +1 -1
  115. package/dist/Stepper/StepperHeader.js.map +1 -1
  116. package/dist/Stepper/StepperHeaderStep.js.map +1 -1
  117. package/dist/Sticky/index.js.map +1 -1
  118. package/dist/Table/_variables.scss +2 -1
  119. package/dist/Tabs/deprecated/index.js.map +1 -1
  120. package/dist/Tabs/index.js +1 -1
  121. package/dist/Tabs/index.js.map +1 -1
  122. package/dist/Toast/ToastContainer.scss +1 -1
  123. package/dist/Toast/index.scss +2 -2
  124. package/dist/Truncate/index.js +1 -1
  125. package/dist/Truncate/index.js.map +1 -1
  126. package/dist/ValidationFormGroup/index.js.map +1 -1
  127. package/dist/asInput/index.js.map +1 -1
  128. package/dist/hooks/{useArrowKeyNavigation.js → useArrowKeyNavigationHook.js} +5 -1
  129. package/dist/hooks/useArrowKeyNavigationHook.js.map +1 -0
  130. package/dist/hooks/{useIndexOfLastVisibleChild.js → useIndexOfLastVisibleChildHook.js} +5 -1
  131. package/dist/hooks/useIndexOfLastVisibleChildHook.js.map +1 -0
  132. package/dist/hooks/{useIsVisible.js → useIsVisibleHook.js} +1 -1
  133. package/dist/hooks/useIsVisibleHook.js.map +1 -0
  134. package/dist/hooks/{useToggle.js → useToggleHook.js} +5 -1
  135. package/dist/hooks/useToggleHook.js.map +1 -0
  136. package/dist/hooks/{useWindowSize.js → useWindowSizeHook.js} +1 -1
  137. package/dist/hooks/useWindowSizeHook.js.map +1 -0
  138. package/dist/index.d.ts +6 -6
  139. package/dist/index.js +6 -6
  140. package/dist/paragon.css +1 -45
  141. package/dist/utils/newId.js.map +1 -1
  142. package/dist/withDeprecatedProps.js.map +1 -1
  143. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/CHANGELOG.md +50 -0
  144. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/LICENSE +7 -0
  145. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/README.md +37 -0
  146. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/dist/index.d.ts +20 -0
  147. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/dist/index.js +79 -0
  148. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/dist/index.js.map +1 -0
  149. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/package.json +40 -0
  150. package/icons/node_modules/@svgr/babel-plugin-add-jsx-attribute/tsconfig.json +4 -0
  151. package/icons/package.json +1 -1
  152. package/package.json +12 -19
  153. package/scss/core/_exports.module.scss +7 -6
  154. package/scss/core/_functions.scss +9 -7
  155. package/scss/core/_typography.scss +2 -1
  156. package/scss/core/_utilities.scss +2 -1
  157. package/scss/core/_variables.scss +98 -95
  158. package/src/Alert/_variables.scss +2 -1
  159. package/src/Annotation/index.scss +6 -5
  160. package/src/Breadcrumb/Breadcrumb.test.jsx +3 -2
  161. package/src/Bubble/index.scss +3 -2
  162. package/src/Bubble/index.tsx +1 -0
  163. package/src/Button/Button.test.tsx +6 -1
  164. package/src/Button/deprecated/Button.test.jsx +6 -4
  165. package/src/Button/index.scss +19 -18
  166. package/src/Card/CardCarousel/tests/CardCarouselControls.test.jsx +6 -4
  167. package/src/Card/_variables.scss +3 -2
  168. package/src/Card/index.scss +10 -9
  169. package/src/Chip/index.tsx +1 -0
  170. package/src/Collapsible/Collapsible.test.jsx +15 -7
  171. package/src/ColorPicker/ColorPicker.test.jsx +9 -16
  172. package/src/ColorPicker/index.jsx +1 -1
  173. package/src/ColorPicker/index.scss +2 -1
  174. package/src/DataTable/CollapsibleButtonGroup.jsx +2 -2
  175. package/src/DataTable/DropdownFilters.jsx +1 -1
  176. package/src/DataTable/dataviews.mdx +1 -8
  177. package/src/DataTable/index.scss +14 -13
  178. package/src/DataTable/selection/tests/ControlledSelectHeader.test.jsx +6 -4
  179. package/src/DataTable/tests/BulkActions.test.jsx +2 -4
  180. package/src/DataTable/tests/DataViewToggle.test.jsx +3 -7
  181. package/src/DataTable/tests/DropdownFilters.test.jsx +1 -1
  182. package/src/DataTable/tests/TableActions.test.jsx +1 -1
  183. package/src/Dropdown/_variables.scss +2 -1
  184. package/src/Dropdown/deprecated/Dropdown.test.jsx +43 -27
  185. package/src/Dropzone/README.md +3 -3
  186. package/src/Dropzone/index.scss +3 -2
  187. package/src/Dropzone/tests/__snapshots__/Dropzone.test.jsx.snap +10 -1
  188. package/src/Form/FormAutosuggest.jsx +1 -1
  189. package/src/Form/FormGroupContext.tsx +1 -1
  190. package/src/Form/_index.scss +9 -7
  191. package/src/Form/_variables.scss +4 -2
  192. package/src/Form/tests/FormAutosuggest.test.jsx +76 -57
  193. package/src/Form/tests/FormCheckboxSet.test.jsx +3 -2
  194. package/src/Form/tests/FormControl.test.jsx +9 -6
  195. package/src/Form/tests/FormRadioSet.test.jsx +3 -2
  196. package/src/Hyperlink/Hyperlink.test.tsx +50 -20
  197. package/src/Hyperlink/README.md +14 -1
  198. package/src/Hyperlink/index.scss +3 -1
  199. package/src/Hyperlink/index.tsx +71 -30
  200. package/src/IconButtonToggle/IconButtonToggle.test.jsx +3 -2
  201. package/src/IconButtonToggle/index.scss +3 -1
  202. package/src/ListBox/ListBox.test.jsx +8 -4
  203. package/src/MailtoLink/MailtoLink.test.jsx +12 -3
  204. package/src/Menu/Menu.test.jsx +27 -19
  205. package/src/Menu/SelectMenu.jsx +1 -1
  206. package/src/Menu/SelectMenu.test.jsx +35 -16
  207. package/src/Menu/__snapshots__/Menu.test.jsx.snap +0 -1
  208. package/src/Menu/index.jsx +1 -1
  209. package/src/Modal/ModalDialogBody.jsx +1 -1
  210. package/src/Modal/_ModalDialog.scss +3 -1
  211. package/src/Modal/index.jsx +2 -0
  212. package/src/Modal/index.scss +3 -5
  213. package/src/Modal/tests/ModalLayer.test.tsx +3 -2
  214. package/src/Nav/_mixins.scss +3 -1
  215. package/src/OverflowScroll/data/tests/useOverflowScroll.test.jsx +1 -2
  216. package/src/OverflowScroll/data/tests/useOverflowScrollActions.test.jsx +1 -1
  217. package/src/OverflowScroll/data/tests/useOverflowScrollElementAttributes.test.jsx +1 -1
  218. package/src/OverflowScroll/data/tests/useOverflowScrollEventListeners.test.jsx +1 -2
  219. package/src/PageBanner/index.scss +2 -1
  220. package/src/Pagination/Pagination.test.jsx +36 -28
  221. package/src/Popover/_variables.scss +2 -1
  222. package/src/ProductTour/Checkpoint.scss +9 -8
  223. package/src/ProductTour/Checkpoint.test.jsx +3 -2
  224. package/src/ProductTour/ProductTour.test.jsx +11 -24
  225. package/src/ProductTour/index.jsx +1 -1
  226. package/src/Scrollable/Scrollable.test.jsx +2 -2
  227. package/src/Scrollable/index.jsx +1 -1
  228. package/src/SearchField/index.scss +2 -1
  229. package/src/SelectableBox/tests/SelectableBox.test.jsx +3 -2
  230. package/src/StatusAlert/StatusAlert.test.jsx +6 -2
  231. package/src/Stepper/StepperHeader.jsx +1 -1
  232. package/src/Stepper/tests/Stepper.test.jsx +1 -1
  233. package/src/Table/_variables.scss +2 -1
  234. package/src/Tabs/Tabs.test.jsx +1 -1
  235. package/src/Tabs/deprecated/Tabs.test.jsx +6 -4
  236. package/src/Tabs/index.jsx +1 -1
  237. package/src/Toast/ToastContainer.scss +1 -1
  238. package/src/Toast/index.scss +2 -2
  239. package/src/Truncate/index.jsx +1 -1
  240. package/src/hooks/tests/useToggle.test.tsx +1 -1
  241. package/src/hooks/{useArrowKeyNavigation.tsx → useArrowKeyNavigationHook.tsx} +4 -0
  242. package/src/hooks/{useIndexOfLastVisibleChild.tsx → useIndexOfLastVisibleChildHook.tsx} +4 -0
  243. package/src/hooks/{useToggle.tsx → useToggleHook.tsx} +4 -0
  244. package/src/index.d.ts +6 -6
  245. package/src/index.js +6 -6
  246. package/dist/hooks/useArrowKeyNavigation.js.map +0 -1
  247. package/dist/hooks/useIndexOfLastVisibleChild.js.map +0 -1
  248. package/dist/hooks/useIsVisible.js.map +0 -1
  249. package/dist/hooks/useToggle.js.map +0 -1
  250. package/dist/hooks/useWindowSize.js.map +0 -1
  251. package/src/DataTable/tests/utils.js +0 -9
  252. /package/dist/hooks/{useArrowKeyNavigation.d.ts → useArrowKeyNavigationHook.d.ts} +0 -0
  253. /package/dist/hooks/{useIndexOfLastVisibleChild.d.ts → useIndexOfLastVisibleChildHook.d.ts} +0 -0
  254. /package/dist/hooks/{useIsVisible.d.ts → useIsVisibleHook.d.ts} +0 -0
  255. /package/dist/hooks/{useToggle.d.ts → useToggleHook.d.ts} +0 -0
  256. /package/dist/hooks/{useWindowSize.d.ts → useWindowSizeHook.d.ts} +0 -0
  257. /package/src/hooks/{useIsVisible.tsx → useIsVisibleHook.tsx} +0 -0
  258. /package/src/hooks/{useWindowSize.tsx → useWindowSizeHook.tsx} +0 -0
@@ -1,3 +1,5 @@
1
+ @use "sass:map";
2
+ @use "sass:string";
1
3
  @import "variables";
2
4
  @import "~bootstrap/scss/forms";
3
5
  @import "~bootstrap/scss/input-group";
@@ -60,7 +62,7 @@ $select-icon-padding: .5625rem !default;
60
62
  }
61
63
  }
62
64
 
63
- @media (min-width: map-get($grid-breakpoints, "sm")) {
65
+ @media (min-width: map.get($grid-breakpoints, "sm")) {
64
66
  margin-inline-end: $custom-control-gutter;
65
67
  }
66
68
 
@@ -306,7 +308,7 @@ $select-icon-padding: .5625rem !default;
306
308
  text-align: right;
307
309
  }
308
310
 
309
- &:not(:focus):not(.has-value) {
311
+ &:not(:focus, .has-value) {
310
312
  color: transparent;
311
313
  }
312
314
 
@@ -318,17 +320,17 @@ $select-icon-padding: .5625rem !default;
318
320
  }
319
321
  }
320
322
 
321
- .form-control:not(:focus):not(.has-value) {
323
+ .form-control:not(:focus, .has-value) {
322
324
  &::placeholder,
323
325
  &::-webkit-datetime-edit {
324
326
  opacity: 0;
325
327
  }
326
328
  }
327
329
 
328
- select.form-control:not(.has-value):not(:focus) {
330
+ select.form-control:not(.has-value, :focus) {
329
331
  // color: rgba(0,0,0,0); Force the rgba syntax to appear in the output rather
330
332
  // than transparent. IE11 does not understand color: transparent here.
331
- color: unquote("rgba(0,0,0,0)");
333
+ color: string.unquote("rgba(0,0,0,0)");
332
334
  }
333
335
  }
334
336
 
@@ -545,7 +547,7 @@ select.form-control {
545
547
  .input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text,
546
548
  .input-group.has-validation > .input-group-append:nth-last-child(n+3) > .btn,
547
549
  .input-group.has-validation > .input-group-append:nth-last-child(n+3) > .input-group-text,
548
- .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
550
+ .input-group > .input-group-append:last-child > .btn:not(:last-child, .dropdown-toggle),
549
551
  .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
550
552
  [dir="rtl"] & {
551
553
  border-radius: 0 $input-border-radius $input-border-radius 0;
@@ -588,7 +590,7 @@ select.form-control {
588
590
  .pgn__form-autosuggest__dropdown {
589
591
  @include pgn-box-shadow(1, "centered");
590
592
 
591
- @media (min-width: map-get($grid-breakpoints, "sm")) {
593
+ @media (min-width: map.get($grid-breakpoints, "sm")) {
592
594
  margin-inline-end: $custom-control-gutter;
593
595
  }
594
596
 
@@ -1,3 +1,5 @@
1
+ @use "sass:color";
2
+ @use "sass:map";
1
3
  $input-padding-y: $input-btn-padding-y !default;
2
4
  $input-padding-x: $input-btn-padding-x !default;
3
5
  $input-font-family: $input-btn-font-family !default;
@@ -198,7 +200,7 @@ $custom-range-thumb-border-radius: 1rem !default;
198
200
  $custom-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default;
199
201
  $custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default;
200
202
  $custom-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in IE/Edge
201
- $custom-range-thumb-active-bg: lighten($component-active-bg, 35%) !default;
203
+ $custom-range-thumb-active-bg: color.adjust($component-active-bg, $lightness: 35%) !default;
202
204
  $custom-range-thumb-disabled-bg: theme-color("gray", "default") !default;
203
205
 
204
206
  $custom-file-height: $input-height !default;
@@ -238,7 +240,7 @@ $form-feedback-icon-invalid: str-replace(url("data:image/svg+xml,%3csvg x
238
240
 
239
241
  $form-validation-states: () !default;
240
242
  // stylelint-disable-next-line scss/dollar-variable-default
241
- $form-validation-states: map-merge(
243
+ $form-validation-states: map.merge(
242
244
  (
243
245
  "valid": (
244
246
  "color": $form-feedback-valid-color,
@@ -89,59 +89,64 @@ describe('render behavior', () => {
89
89
  expect(screen.getByDisplayValue('Test Value')).toBeInTheDocument();
90
90
  });
91
91
 
92
- it('renders component with options', () => {
92
+ it('renders component with options', async () => {
93
+ const user = userEvent.setup();
93
94
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
94
95
  const input = getByTestId('autosuggest-textbox-input');
95
- userEvent.click(input);
96
+ await user.click(input);
96
97
  const list = queryAllByTestId('autosuggest-optionitem');
97
98
  expect(list.length).toBe(3);
98
99
  });
99
100
 
100
- it('renders with value required error msg', () => {
101
+ it('renders with value required error msg', async () => {
102
+ const user = userEvent.setup();
101
103
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent isValueRequired />);
102
104
  const input = getByTestId('autosuggest-textbox-input');
103
105
 
104
106
  // if you click into the input and click outside, you should see the error message
105
- userEvent.click(input);
106
- userEvent.click(document.body);
107
+ await user.click(input);
108
+ await user.click(document.body);
107
109
 
108
110
  const formControlFeedback = getByText('Example value required error message');
109
111
 
110
112
  expect(formControlFeedback).toBeInTheDocument();
111
113
  });
112
114
 
113
- it('renders with selection required error msg', () => {
115
+ it('renders with selection required error msg', async () => {
116
+ const user = userEvent.setup();
114
117
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent isSelectionRequired />);
115
118
  const input = getByTestId('autosuggest-textbox-input');
116
119
 
117
120
  // if you click into the input and click outside, you should see the error message
118
- userEvent.click(input);
119
- userEvent.type(input, '1');
120
- userEvent.click(document.body);
121
+ await user.click(input);
122
+ await user.type(input, '1');
123
+ await user.click(document.body);
121
124
 
122
125
  const formControlFeedback = getByText('Example selection required error message');
123
126
 
124
127
  expect(formControlFeedback).toBeInTheDocument();
125
128
  });
126
129
 
127
- it('renders with custom error msg', () => {
130
+ it('renders with custom error msg', async () => {
131
+ const user = userEvent.setup();
128
132
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent hasCustomError />);
129
133
  const input = getByTestId('autosuggest-textbox-input');
130
134
 
131
135
  // if you click into the input and click outside, you should see the error message
132
- userEvent.click(input);
133
- userEvent.click(document.body);
136
+ await user.click(input);
137
+ await user.click(document.body);
134
138
 
135
139
  const formControlFeedback = getByText('Example custom error message');
136
140
 
137
141
  expect(formControlFeedback).toBeInTheDocument();
138
142
  });
139
143
 
140
- it('renders component with options that all have IDs', () => {
144
+ it('renders component with options that all have IDs', async () => {
145
+ const user = userEvent.setup();
141
146
  const { getByTestId, getAllByTestId } = render(<FormAutosuggestTestComponent />);
142
147
  const input = getByTestId('autosuggest-textbox-input');
143
148
 
144
- userEvent.click(input);
149
+ await user.click(input);
145
150
  const optionItemIds = getAllByTestId('autosuggest-optionitem').map(item => item.id);
146
151
 
147
152
  expect(optionItemIds).not.toContain(null);
@@ -154,12 +159,13 @@ describe('render behavior', () => {
154
159
  expect(getByTestId('autosuggest-screen-reader-options-count').getAttribute('aria-live')).toEqual('assertive');
155
160
  });
156
161
 
157
- it('displays correct amount of options found to screen readers', () => {
162
+ it('displays correct amount of options found to screen readers', async () => {
163
+ const user = userEvent.setup();
158
164
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent />);
159
165
  const input = getByTestId('autosuggest-textbox-input');
160
166
 
161
167
  expect(getByText('0 options found')).toBeInTheDocument();
162
- userEvent.click(input);
168
+ await user.click(input);
163
169
 
164
170
  expect(getByText('3 options found')).toBeInTheDocument();
165
171
  });
@@ -172,167 +178,180 @@ describe('render behavior', () => {
172
178
  });
173
179
 
174
180
  describe('controlled behavior', () => {
175
- it('sets input value based on clicked option', () => {
181
+ it('sets input value based on clicked option', async () => {
182
+ const user = userEvent.setup();
176
183
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent />);
177
184
  const input = getByTestId('autosuggest-textbox-input');
178
185
 
179
- userEvent.click(input);
186
+ await user.click(input);
180
187
  const menuItem = getByText('Option 1');
181
- userEvent.click(menuItem);
188
+ await user.click(menuItem);
182
189
 
183
190
  expect(input.value).toEqual('Option 1');
184
191
  });
185
192
 
186
- it('calls onChange based on clicked option', () => {
193
+ it('calls onChange based on clicked option', async () => {
194
+ const user = userEvent.setup();
187
195
  const onChange = jest.fn();
188
196
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent onChange={onChange} />);
189
197
  const input = getByTestId('autosuggest-textbox-input');
190
198
 
191
- userEvent.click(input);
199
+ await user.click(input);
192
200
  const menuItem = getByText('Option 1');
193
- userEvent.click(menuItem);
201
+ await user.click(menuItem);
194
202
 
195
203
  expect(onChange).toHaveBeenCalledWith({ selectionId: 'option-1-id', selectionValue: 'Option 1', userProvidedText: 'Option 1' });
196
204
  expect(onChange).toHaveBeenCalledTimes(1);
197
205
  });
198
206
 
199
- it('calls onChange when the textbox is cleared', () => {
207
+ it('calls onChange when the textbox is cleared', async () => {
208
+ const user = userEvent.setup();
200
209
  const onChange = jest.fn();
201
210
  const { getByTestId } = render(<FormAutosuggestTestComponent onChange={onChange} />);
202
211
  const input = getByTestId('autosuggest-textbox-input');
203
212
 
204
- userEvent.type(input, '1');
205
- userEvent.type(input, '{backspace}');
213
+ await user.type(input, '1');
214
+ await user.type(input, '{backspace}');
206
215
 
207
216
  expect(onChange).toHaveBeenCalledWith({ selectionId: '', selectionValue: '', userProvidedText: '' });
208
217
  });
209
218
 
210
- it('calls the function passed to onClick when an option with it is selected', () => {
219
+ it('calls the function passed to onClick when an option with it is selected', async () => {
220
+ const user = userEvent.setup();
211
221
  const onClick = jest.fn();
212
222
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent onClick={onClick} />);
213
223
  const input = getByTestId('autosuggest-textbox-input');
214
224
 
215
- userEvent.click(input);
225
+ await user.click(input);
216
226
  const menuItem = getByText('Option 2');
217
- userEvent.click(menuItem);
227
+ await user.click(menuItem);
218
228
 
219
229
  expect(onClick).toHaveBeenCalledTimes(1);
220
230
  });
221
231
 
222
- it('does not call onClick when an option without it is selected', () => {
232
+ it('does not call onClick when an option without it is selected', async () => {
233
+ const user = userEvent.setup();
223
234
  const onClick = jest.fn();
224
235
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent onClick={onClick} />);
225
236
  const input = getByTestId('autosuggest-textbox-input');
226
237
 
227
- userEvent.click(input);
238
+ await user.click(input);
228
239
  const menuItem = getByText('Option 1');
229
- userEvent.click(menuItem);
240
+ await user.click(menuItem);
230
241
 
231
242
  expect(onClick).toHaveBeenCalledTimes(0);
232
243
  });
233
244
 
234
- it('should set the correct activedescendant', () => {
245
+ it('should set the correct activedescendant', async () => {
246
+ const user = userEvent.setup();
235
247
  const { getByTestId, getAllByTestId } = render(<FormAutosuggestTestComponent />);
236
248
  const input = getByTestId('autosuggest-textbox-input');
237
249
 
238
- userEvent.click(input);
250
+ await user.click(input);
239
251
  const expectedOptionId = getAllByTestId('autosuggest-optionitem')[0].id;
240
- userEvent.keyboard('{arrowdown}');
252
+ await user.keyboard('{arrowdown}');
241
253
 
242
254
  expect(input.getAttribute('aria-activedescendant')).toEqual(expectedOptionId);
243
255
  });
244
256
 
245
- it('filters dropdown based on typed field value with one match', () => {
257
+ it('filters dropdown based on typed field value with one match', async () => {
258
+ const user = userEvent.setup();
246
259
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
247
260
  const input = getByTestId('autosuggest-textbox-input');
248
261
 
249
- userEvent.click(input);
250
- userEvent.type(input, 'Option 1');
262
+ await user.click(input);
263
+ await user.type(input, 'Option 1');
251
264
 
252
265
  const list = queryAllByTestId('autosuggest-optionitem');
253
266
  expect(list.length).toBe(1);
254
267
  });
255
268
 
256
- it('toggles options list', () => {
269
+ it('toggles options list', async () => {
270
+ const user = userEvent.setup();
257
271
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
258
272
  const dropdownBtn = getByTestId('autosuggest-iconbutton');
259
273
 
260
- userEvent.click(dropdownBtn);
274
+ await user.click(dropdownBtn);
261
275
  const list = queryAllByTestId('autosuggest-optionitem');
262
276
  expect(list.length).toBe(3);
263
277
 
264
- userEvent.click(dropdownBtn);
278
+ await user.click(dropdownBtn);
265
279
  const updatedList = queryAllByTestId('autosuggest-optionitem');
266
280
  expect(updatedList.length).toBe(0);
267
281
 
268
- userEvent.click(dropdownBtn);
282
+ await user.click(dropdownBtn);
269
283
  const reopenedList = queryAllByTestId('autosuggest-optionitem');
270
284
  expect(reopenedList.length).toBe(3);
271
285
  });
272
286
 
273
- it('filters dropdown based on typed field value with multiple matches', () => {
287
+ it('filters dropdown based on typed field value with multiple matches', async () => {
288
+ const user = userEvent.setup();
274
289
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
275
290
  const input = getByTestId('autosuggest-textbox-input');
276
291
 
277
- userEvent.click(input);
278
- userEvent.type(input, '1');
292
+ await user.click(input);
293
+ await user.type(input, '1');
279
294
 
280
295
  const list = queryAllByTestId('autosuggest-optionitem');
281
296
  expect(list.length).toBe(2);
282
297
  });
283
298
 
284
- it('closes options list on click outside', () => {
299
+ it('closes options list on click outside', async () => {
300
+ const user = userEvent.setup();
285
301
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
286
302
  const input = getByTestId('autosuggest-textbox-input');
287
303
 
288
- userEvent.click(input);
304
+ await user.click(input);
289
305
  const list = queryAllByTestId('autosuggest-optionitem');
290
306
  expect(list.length).toBe(3);
291
307
 
292
- userEvent.click(document.body);
308
+ await user.click(document.body);
293
309
  const updatedList = queryAllByTestId('autosuggest-optionitem');
294
310
  expect(updatedList.length).toBe(0);
295
311
  });
296
312
 
297
- it('updates screen reader option count based on typed field value with multiple matches', () => {
313
+ it('updates screen reader option count based on typed field value with multiple matches', async () => {
314
+ const user = userEvent.setup();
298
315
  const { getByText, getByTestId } = render(<FormAutosuggestTestComponent />);
299
316
  const input = getByTestId('autosuggest-textbox-input');
300
317
 
301
318
  expect(getByText('0 options found')).toBeInTheDocument();
302
- userEvent.click(input);
319
+ await user.click(input);
303
320
 
304
321
  expect(getByText('3 options found')).toBeInTheDocument();
305
322
 
306
- userEvent.click(input);
307
- userEvent.type(input, '1');
323
+ await user.click(input);
324
+ await user.type(input, '1');
308
325
 
309
326
  expect(getByText('2 options found')).toBeInTheDocument();
310
327
  });
311
328
 
312
- it('closes options list when tabbed out and the input is no longer active', () => {
329
+ it('closes options list when tabbed out and the input is no longer active', async () => {
330
+ const user = userEvent.setup();
313
331
  const { getByTestId, queryAllByTestId } = render(<FormAutosuggestTestComponent />);
314
332
  const input = getByTestId('autosuggest-textbox-input');
315
333
 
316
- userEvent.click(input);
334
+ await user.click(input);
317
335
  expect(document.activeElement).toBe(getByTestId('autosuggest-textbox-input'));
318
336
 
319
337
  const list = queryAllByTestId('autosuggest-optionitem');
320
338
  expect(list.length).toBe(3);
321
339
 
322
- userEvent.tab();
340
+ await user.tab();
323
341
  expect(document.activeElement).not.toBe(getByTestId('autosuggest-textbox-input'));
324
342
 
325
343
  const updatedList = queryAllByTestId('autosuggest-optionitem');
326
344
  expect(updatedList.length).toBe(0);
327
345
  });
328
346
 
329
- it('check focus on input after esc', () => {
347
+ it('check focus on input after esc', async () => {
348
+ const user = userEvent.setup();
330
349
  const { getByTestId } = render(<FormAutosuggestTestComponent />);
331
350
  const input = getByTestId('autosuggest-textbox-input');
332
351
  const dropdownBtn = getByTestId('autosuggest-iconbutton');
333
- userEvent.click(dropdownBtn);
352
+ await user.click(dropdownBtn);
334
353
 
335
- userEvent.keyboard('{esc}');
354
+ await user.keyboard('{Escape}');
336
355
 
337
356
  expect(input.matches(':focus')).toBe(true);
338
357
  });
@@ -166,7 +166,8 @@ describe('FormCheckboxSet', () => {
166
166
  });
167
167
  });
168
168
 
169
- it('checks if onClick is called once in FormCheckboxSet', () => {
169
+ it('checks if onClick is called once in FormCheckboxSet', async () => {
170
+ const user = userEvent.setup();
170
171
  const handleChange = jest.fn();
171
172
  const { getByLabelText } = render(
172
173
  <FormGroup controlId="my-field">
@@ -181,7 +182,7 @@ describe('FormCheckboxSet', () => {
181
182
  </FormGroup>,
182
183
  );
183
184
 
184
- userEvent.click(getByLabelText('Red'));
185
+ await user.click(getByLabelText('Red'));
185
186
  expect(handleChange).toHaveBeenCalledTimes(1);
186
187
  });
187
188
  });
@@ -28,7 +28,8 @@ function Component({ isClearValue }) {
28
28
  }
29
29
 
30
30
  describe('FormControl', () => {
31
- it('textarea changes its height with autoResize prop', () => {
31
+ it('textarea changes its height with autoResize prop', async () => {
32
+ const user = userEvent.setup();
32
33
  const useReferenceSpy = jest.spyOn(React, 'useRef').mockReturnValue(ref);
33
34
  const onChangeFunc = jest.fn();
34
35
  const inputText = 'new text';
@@ -45,25 +46,27 @@ describe('FormControl', () => {
45
46
  expect(useReferenceSpy).toHaveBeenCalledTimes(1);
46
47
  expect(ref.current.style.height).toBe('0px');
47
48
 
48
- userEvent.type(textarea, inputText);
49
+ await user.type(textarea, inputText);
49
50
 
50
51
  expect(onChangeFunc).toHaveBeenCalledTimes(inputText.length);
51
52
  expect(ref.current.style.height).toEqual(`${ref.current.scrollHeight + ref.current.offsetHeight}px`);
52
53
  });
53
54
 
54
- it('should apply and accept input mask for phone numbers', () => {
55
+ it('should apply and accept input mask for phone numbers', async () => {
56
+ const user = userEvent.setup();
55
57
  render(<Component />);
56
58
 
57
59
  const input = screen.getByTestId('form-control-with-mask');
58
- userEvent.type(input, '5555555555');
60
+ await user.type(input, '5555555555');
59
61
  expect(input.value).toBe('+1 (555) 555-5555');
60
62
  });
61
63
 
62
- it('should be cleared from the mask elements value', () => {
64
+ it('should be cleared from the mask elements value', async () => {
65
+ const user = userEvent.setup();
63
66
  render(<Component isClearValue />);
64
67
 
65
68
  const input = screen.getByTestId('form-control-with-mask');
66
- userEvent.type(input, '5555555555');
69
+ await user.type(input, '5555555555');
67
70
 
68
71
  expect(input.value).toBe('+1 (555) 555-5555');
69
72
  expect(unmaskedInputValue).toBe('15555555555');
@@ -109,7 +109,8 @@ describe('FormRadioSet', () => {
109
109
  expect(deciduousRadio).toHaveAttribute('name', 'trees');
110
110
  });
111
111
 
112
- it('checks if onClick is called once in FormRadioSet', () => {
112
+ it('checks if onClick is called once in FormRadioSet', async () => {
113
+ const user = userEvent.setup();
113
114
  const handleChange = jest.fn();
114
115
  const { getByLabelText } = render(
115
116
  <FormGroup>
@@ -124,7 +125,7 @@ describe('FormRadioSet', () => {
124
125
  </FormGroup>,
125
126
  );
126
127
 
127
- userEvent.click(getByLabelText('Red'));
128
+ await user.click(getByLabelText('Red'));
128
129
  expect(handleChange).toHaveBeenCalledTimes(1);
129
130
  });
130
131
  });
@@ -1,12 +1,12 @@
1
- import React from 'react';
1
+ import { IntlProvider } from 'react-intl';
2
2
  import { render } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
 
5
- import Hyperlink from '.';
5
+ import Hyperlink, { HyperlinkProps } from '.';
6
6
 
7
- const destination = 'destination';
7
+ const destination = 'http://destination.example';
8
8
  const content = 'content';
9
- const onClick = jest.fn();
9
+ const onClick = jest.fn().mockImplementation((e) => e.preventDefault());
10
10
  const props = {
11
11
  destination,
12
12
  onClick,
@@ -20,13 +20,37 @@ const externalLinkProps = {
20
20
  ...props,
21
21
  };
22
22
 
23
+ interface LinkProps extends HyperlinkProps {
24
+ to: string;
25
+ }
26
+
27
+ function Link({ to, children, ...rest }: LinkProps) {
28
+ return (
29
+ <a
30
+ data-testid="custom-hyperlink-element"
31
+ href={to}
32
+ {...rest}
33
+ >
34
+ {children}
35
+ </a>
36
+ );
37
+ }
38
+
39
+ function HyperlinkWrapper({ children, ...rest }: HyperlinkProps) {
40
+ return (
41
+ <IntlProvider locale="en">
42
+ <Hyperlink {...rest}>{children}</Hyperlink>
43
+ </IntlProvider>
44
+ );
45
+ }
46
+
23
47
  describe('correct rendering', () => {
24
48
  beforeEach(() => {
25
- onClick.mockClear();
49
+ jest.clearAllMocks();
26
50
  });
27
51
 
28
52
  it('renders Hyperlink', async () => {
29
- const { getByRole } = render(<Hyperlink {...props}>{content}</Hyperlink>);
53
+ const { getByRole } = render(<HyperlinkWrapper {...props}>{content}</HyperlinkWrapper>);
30
54
  const wrapper = getByRole('link');
31
55
  expect(wrapper).toBeInTheDocument();
32
56
 
@@ -36,12 +60,29 @@ describe('correct rendering', () => {
36
60
  expect(wrapper).toHaveAttribute('href', destination);
37
61
  expect(wrapper).toHaveAttribute('target', '_self');
38
62
 
63
+ // Clicking on the link should call the onClick handler
39
64
  await userEvent.click(wrapper);
40
65
  expect(onClick).toHaveBeenCalledTimes(1);
41
66
  });
42
67
 
68
+ it('renders with custom element type via "as" prop', () => {
69
+ const propsWithoutDestination = {
70
+ to: destination, // `to` simulates common `Link` components' prop
71
+ };
72
+ const { getByRole } = render(<HyperlinkWrapper as={Link} {...propsWithoutDestination}>{content}</HyperlinkWrapper>);
73
+ const wrapper = getByRole('link');
74
+ expect(wrapper).toBeInTheDocument();
75
+
76
+ expect(wrapper).toHaveClass('pgn__hyperlink');
77
+ expect(wrapper).toHaveClass('standalone-link');
78
+ expect(wrapper).toHaveTextContent(content);
79
+ expect(wrapper).toHaveAttribute('href', destination);
80
+ expect(wrapper).toHaveAttribute('target', '_self');
81
+ expect(wrapper).toHaveAttribute('data-testid', 'custom-hyperlink-element');
82
+ });
83
+
43
84
  it('renders an underlined Hyperlink', async () => {
44
- const { getByRole } = render(<Hyperlink isInline {...props}>{content}</Hyperlink>);
85
+ const { getByRole } = render(<HyperlinkWrapper isInline {...props}>{content}</HyperlinkWrapper>);
45
86
  const wrapper = getByRole('link');
46
87
  expect(wrapper).toBeInTheDocument();
47
88
  expect(wrapper).toHaveClass('pgn__hyperlink');
@@ -50,7 +91,7 @@ describe('correct rendering', () => {
50
91
  });
51
92
 
52
93
  it('renders external Hyperlink', () => {
53
- const { getByRole, getByTestId } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
94
+ const { getByRole, getByTestId } = render(<HyperlinkWrapper {...externalLinkProps}>{content}</HyperlinkWrapper>);
54
95
  const wrapper = getByRole('link');
55
96
  const icon = getByTestId('hyperlink-icon');
56
97
  const iconSvg = icon.querySelector('svg');
@@ -66,19 +107,8 @@ describe('correct rendering', () => {
66
107
 
67
108
  describe('security', () => {
68
109
  it('prevents reverse tabnabbing for links with target="_blank"', () => {
69
- const { getByRole } = render(<Hyperlink {...externalLinkProps}>{content}</Hyperlink>);
110
+ const { getByRole } = render(<HyperlinkWrapper {...externalLinkProps}>{content}</HyperlinkWrapper>);
70
111
  const wrapper = getByRole('link');
71
112
  expect(wrapper).toHaveAttribute('rel', 'noopener noreferrer');
72
113
  });
73
114
  });
74
-
75
- describe('event handlers are triggered correctly', () => {
76
- it('should fire onClick', async () => {
77
- const spy = jest.fn();
78
- const { getByRole } = render(<Hyperlink {...props} onClick={spy}>{content}</Hyperlink>);
79
- const wrapper = getByRole('link');
80
- expect(spy).toHaveBeenCalledTimes(0);
81
- await userEvent.click(wrapper);
82
- expect(spy).toHaveBeenCalledTimes(1);
83
- });
84
- });
@@ -7,7 +7,7 @@ categories:
7
7
  - Buttonlike
8
8
  status: 'Needs Work'
9
9
  designStatus: 'Done'
10
- devStatus: 'To Do'
10
+ devStatus: 'Done'
11
11
  notes: |
12
12
  Improve prop naming. Deprecate content prop.
13
13
  Use React.forwardRef for ref forwarding.
@@ -100,3 +100,16 @@ notes: |
100
100
  </div>
101
101
  </div>
102
102
  ```
103
+
104
+ ## with custom link element (e.g., using a router)
105
+
106
+ ``Hyperlink`` typically relies on the standard HTML anchor tag (i.e., ``a``); however, this behavior may be overriden when the destination link is to an internal route where it should be using routing instead (e.g., ``Link`` from React Router).
107
+
108
+ ```jsx live
109
+ <Hyperlink
110
+ as={GatsbyLink}
111
+ to="/components/button"
112
+ >
113
+ Button
114
+ </Hyperlink>
115
+ ```
@@ -1,9 +1,11 @@
1
+ @use "sass:map";
2
+
1
3
  .pgn__hyperlink {
2
4
  display: inline-flex;
3
5
  align-items: center;
4
6
  text-align: start;
5
7
 
6
8
  &__external-icon {
7
- margin-inline-start: map_get($spacers, 2);
9
+ margin-inline-start: map.get($spacers, 2);
8
10
  }
9
11
  }