@hyphen/hyphen-components 2.9.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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/package.json +138 -0
  4. package/src/components/Alert/Alert.constants.ts +19 -0
  5. package/src/components/Alert/Alert.mdx +29 -0
  6. package/src/components/Alert/Alert.module.scss +74 -0
  7. package/src/components/Alert/Alert.stories.tsx +102 -0
  8. package/src/components/Alert/Alert.test.tsx +187 -0
  9. package/src/components/Alert/Alert.tsx +157 -0
  10. package/src/components/Alert/Alert.types.ts +14 -0
  11. package/src/components/Badge/Badge.mdx +28 -0
  12. package/src/components/Badge/Badge.module.scss +155 -0
  13. package/src/components/Badge/Badge.stories.tsx +52 -0
  14. package/src/components/Badge/Badge.test.tsx +74 -0
  15. package/src/components/Badge/Badge.tsx +70 -0
  16. package/src/components/Box/Box.mdx +259 -0
  17. package/src/components/Box/Box.module.scss +16 -0
  18. package/src/components/Box/Box.stories.tsx +1679 -0
  19. package/src/components/Box/Box.test.tsx +478 -0
  20. package/src/components/Box/Box.tsx +636 -0
  21. package/src/components/Button/Button.constants.ts +10 -0
  22. package/src/components/Button/Button.mdx +71 -0
  23. package/src/components/Button/Button.module.scss +312 -0
  24. package/src/components/Button/Button.stories.tsx +117 -0
  25. package/src/components/Button/Button.test.tsx +460 -0
  26. package/src/components/Button/Button.tsx +241 -0
  27. package/src/components/Card/Card.mdx +46 -0
  28. package/src/components/Card/Card.module.scss +6 -0
  29. package/src/components/Card/Card.stories.tsx +101 -0
  30. package/src/components/Card/Card.test.tsx +11 -0
  31. package/src/components/Card/Card.tsx +61 -0
  32. package/src/components/Card/components/CardFooter/CardFooter.test.tsx +11 -0
  33. package/src/components/Card/components/CardFooter/CardFooter.tsx +35 -0
  34. package/src/components/Card/components/CardHeader/CardHeader.test.tsx +23 -0
  35. package/src/components/Card/components/CardHeader/CardHeader.tsx +54 -0
  36. package/src/components/Card/components/CardSection/CardSection.test.tsx +28 -0
  37. package/src/components/Card/components/CardSection/CardSection.tsx +102 -0
  38. package/src/components/Card/components/index.ts +3 -0
  39. package/src/components/CheckboxInput/CheckboxInput.mdx +98 -0
  40. package/src/components/CheckboxInput/CheckboxInput.stories.tsx +254 -0
  41. package/src/components/CheckboxInput/CheckboxInput.test.tsx +436 -0
  42. package/src/components/CheckboxInput/CheckboxInput.tsx +171 -0
  43. package/src/components/CheckboxInput/components/Checkbox.module.scss +174 -0
  44. package/src/components/CheckboxInput/components/Checkbox.test.tsx +201 -0
  45. package/src/components/CheckboxInput/components/Checkbox.tsx +176 -0
  46. package/src/components/CheckboxInput/components/CheckboxIcon.tsx +71 -0
  47. package/src/components/DateInput/DateInput.mdx +61 -0
  48. package/src/components/DateInput/DateInput.stories.tsx +168 -0
  49. package/src/components/DateInput/DateInput.test.tsx +258 -0
  50. package/src/components/DateInput/DateInput.tsx +189 -0
  51. package/src/components/DatePicker/DatePicker.mdx +52 -0
  52. package/src/components/DatePicker/DatePicker.module.scss +603 -0
  53. package/src/components/DatePicker/DatePicker.stories.tsx +199 -0
  54. package/src/components/DatePicker/DatePicker.test.tsx +26 -0
  55. package/src/components/DatePicker/DatePicker.tsx +138 -0
  56. package/src/components/Details/Details.mdx +30 -0
  57. package/src/components/Details/Details.module.scss +32 -0
  58. package/src/components/Details/Details.stories.tsx +38 -0
  59. package/src/components/Details/Details.test.tsx +189 -0
  60. package/src/components/Details/Details.tsx +51 -0
  61. package/src/components/Details/DetailsSummary.tsx +65 -0
  62. package/src/components/Drawer/Drawer.mdx +117 -0
  63. package/src/components/Drawer/Drawer.module.scss +96 -0
  64. package/src/components/Drawer/Drawer.stories.tsx +380 -0
  65. package/src/components/Drawer/Drawer.test.tsx +90 -0
  66. package/src/components/Drawer/Drawer.tsx +249 -0
  67. package/src/components/FormControl/FormControl.tsx +78 -0
  68. package/src/components/FormLabel/FormLabel.mdx +24 -0
  69. package/src/components/FormLabel/FormLabel.module.scss +19 -0
  70. package/src/components/FormLabel/FormLabel.stories.tsx +20 -0
  71. package/src/components/FormLabel/FormLabel.test.tsx +35 -0
  72. package/src/components/FormLabel/FormLabel.tsx +96 -0
  73. package/src/components/Formik/Formik.mdx +10 -0
  74. package/src/components/Formik/Formik.stories.tsx +307 -0
  75. package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.test.tsx +172 -0
  76. package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.tsx +41 -0
  77. package/src/components/Formik/FormikRadioGroup/FormikRadioGroup.test.tsx +205 -0
  78. package/src/components/Formik/FormikRadioGroup/FormikRadioGroup.tsx +37 -0
  79. package/src/components/Formik/FormikSelectInput/FormikSelectInput.test.tsx +210 -0
  80. package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +41 -0
  81. package/src/components/Formik/FormikSelectInputInset/FormikSelectInputInset.test.tsx +153 -0
  82. package/src/components/Formik/FormikSelectInputInset/FormikSelectInputInset.tsx +44 -0
  83. package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.test.tsx +161 -0
  84. package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.tsx +46 -0
  85. package/src/components/Formik/FormikTextInput/FormikTextInput.test.tsx +176 -0
  86. package/src/components/Formik/FormikTextInput/FormikTextInput.tsx +38 -0
  87. package/src/components/Formik/FormikTextInputInset/FormikTextInputInset.test.tsx +170 -0
  88. package/src/components/Formik/FormikTextInputInset/FormikTextInputInset.tsx +42 -0
  89. package/src/components/Formik/FormikTextareaInput/FormikTextareaInput.test.tsx +186 -0
  90. package/src/components/Formik/FormikTextareaInput/FormikTextareaInput.tsx +42 -0
  91. package/src/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.test.tsx +179 -0
  92. package/src/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.tsx +42 -0
  93. package/src/components/Formik/FormikTimePicker/FormikTimePicker.test.tsx +224 -0
  94. package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +37 -0
  95. package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.test.tsx +175 -0
  96. package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.tsx +38 -0
  97. package/src/components/Formik/FormikToggle/FormikToggle.test.tsx +172 -0
  98. package/src/components/Formik/FormikToggle/FormikToggle.tsx +38 -0
  99. package/src/components/Heading/Heading.constants.ts +19 -0
  100. package/src/components/Heading/Heading.mdx +35 -0
  101. package/src/components/Heading/Heading.module.scss +5 -0
  102. package/src/components/Heading/Heading.stories.tsx +90 -0
  103. package/src/components/Heading/Heading.test.tsx +67 -0
  104. package/src/components/Heading/Heading.tsx +67 -0
  105. package/src/components/HelpText/HelpText.module.scss +14 -0
  106. package/src/components/HelpText/HelpText.tsx +33 -0
  107. package/src/components/Icon/Icon.mdx +40 -0
  108. package/src/components/Icon/Icon.stories.tsx +72 -0
  109. package/src/components/Icon/Icon.test.tsx +30 -0
  110. package/src/components/Icon/Icon.tsx +61 -0
  111. package/src/components/InputValidationMessage/InputValidationMessage.module.scss +3 -0
  112. package/src/components/InputValidationMessage/InputValidationMessage.tsx +27 -0
  113. package/src/components/Modal/Modal.mdx +60 -0
  114. package/src/components/Modal/Modal.module.scss +135 -0
  115. package/src/components/Modal/Modal.stories.tsx +194 -0
  116. package/src/components/Modal/Modal.test.tsx +81 -0
  117. package/src/components/Modal/Modal.tsx +174 -0
  118. package/src/components/Modal/components/ModalBody/ModalBody.test.tsx +20 -0
  119. package/src/components/Modal/components/ModalBody/ModalBody.tsx +24 -0
  120. package/src/components/Modal/components/ModalFooter/ModalFooter.test.tsx +32 -0
  121. package/src/components/Modal/components/ModalFooter/ModalFooter.tsx +37 -0
  122. package/src/components/Modal/components/ModalHeader/ModalHeader.test.tsx +29 -0
  123. package/src/components/Modal/components/ModalHeader/ModalHeader.tsx +58 -0
  124. package/src/components/Modal/components/index.ts +5 -0
  125. package/src/components/Pagination/Pagination.mdx +26 -0
  126. package/src/components/Pagination/Pagination.stories.tsx +55 -0
  127. package/src/components/Pagination/Pagination.test.tsx +225 -0
  128. package/src/components/Pagination/Pagination.tsx +162 -0
  129. package/src/components/Pagination/Pagination.utilities.test.ts +133 -0
  130. package/src/components/Pagination/Pagination.utilities.ts +101 -0
  131. package/src/components/Popover/Popover.mdx +104 -0
  132. package/src/components/Popover/Popover.module.scss +74 -0
  133. package/src/components/Popover/Popover.stories.tsx +471 -0
  134. package/src/components/Popover/Popover.test.tsx +128 -0
  135. package/src/components/Popover/Popover.tsx +277 -0
  136. package/src/components/RadioGroup/RadioGroup.mdx +81 -0
  137. package/src/components/RadioGroup/RadioGroup.module.scss +23 -0
  138. package/src/components/RadioGroup/RadioGroup.stories.tsx +375 -0
  139. package/src/components/RadioGroup/RadioGroup.test.tsx +282 -0
  140. package/src/components/RadioGroup/RadioGroup.tsx +145 -0
  141. package/src/components/RadioGroup/RadioInput/RadioInput.module.scss +114 -0
  142. package/src/components/RadioGroup/RadioInput/RadioInput.test.tsx +156 -0
  143. package/src/components/RadioGroup/RadioInput/RadioInput.tsx +148 -0
  144. package/src/components/RadioGroup/RadioInput/RadioInputIcon.tsx +59 -0
  145. package/src/components/ResponsiveProvider/ResponsiveProvider.mdx +36 -0
  146. package/src/components/ResponsiveProvider/ResponsiveProvider.stories.tsx +54 -0
  147. package/src/components/ResponsiveProvider/ResponsiveProvider.test.tsx +70 -0
  148. package/src/components/ResponsiveProvider/ResponsiveProvider.tsx +73 -0
  149. package/src/components/SelectInput/SelectInput.mdx +115 -0
  150. package/src/components/SelectInput/SelectInput.module.scss +357 -0
  151. package/src/components/SelectInput/SelectInput.stories.tsx +373 -0
  152. package/src/components/SelectInput/SelectInput.test.tsx +403 -0
  153. package/src/components/SelectInput/SelectInput.tsx +245 -0
  154. package/src/components/SelectInputInset/SelectInputInset.mdx +56 -0
  155. package/src/components/SelectInputInset/SelectInputInset.module.scss +397 -0
  156. package/src/components/SelectInputInset/SelectInputInset.stories.tsx +189 -0
  157. package/src/components/SelectInputInset/SelectInputInset.test.tsx +305 -0
  158. package/src/components/SelectInputInset/SelectInputInset.tsx +235 -0
  159. package/src/components/SelectInputNative/SelectInputNative.mdx +87 -0
  160. package/src/components/SelectInputNative/SelectInputNative.module.scss +356 -0
  161. package/src/components/SelectInputNative/SelectInputNative.stories.tsx +282 -0
  162. package/src/components/SelectInputNative/SelectInputNative.test.tsx +341 -0
  163. package/src/components/SelectInputNative/SelectInputNative.tsx +121 -0
  164. package/src/components/Spinner/Spinner.mdx +29 -0
  165. package/src/components/Spinner/Spinner.module.scss +16 -0
  166. package/src/components/Spinner/Spinner.stories.tsx +48 -0
  167. package/src/components/Spinner/Spinner.test.tsx +47 -0
  168. package/src/components/Spinner/Spinner.tsx +116 -0
  169. package/src/components/Table/Table.mdx +216 -0
  170. package/src/components/Table/Table.module.scss +61 -0
  171. package/src/components/Table/Table.stories.tsx +884 -0
  172. package/src/components/Table/Table.test.tsx +437 -0
  173. package/src/components/Table/Table.tsx +171 -0
  174. package/src/components/Table/TableBody/TableBody.module.scss +19 -0
  175. package/src/components/Table/TableBody/TableBody.test.tsx +38 -0
  176. package/src/components/Table/TableBody/TableBody.tsx +96 -0
  177. package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.module.scss +47 -0
  178. package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.test.tsx +81 -0
  179. package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.tsx +94 -0
  180. package/src/components/Table/TableHead/TableHead.test.tsx +20 -0
  181. package/src/components/Table/TableHead/TableHead.tsx +78 -0
  182. package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.module.scss +72 -0
  183. package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.test.tsx +187 -0
  184. package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.tsx +192 -0
  185. package/src/components/Table/common/TableRow/TableRow.module.scss +5 -0
  186. package/src/components/Table/common/TableRow/TableRow.test.tsx +52 -0
  187. package/src/components/Table/common/TableRow/TableRow.tsx +155 -0
  188. package/src/components/TextInput/TextInput.mdx +96 -0
  189. package/src/components/TextInput/TextInput.module.scss +405 -0
  190. package/src/components/TextInput/TextInput.stories.tsx +268 -0
  191. package/src/components/TextInput/TextInput.test.tsx +231 -0
  192. package/src/components/TextInput/TextInput.tsx +263 -0
  193. package/src/components/TextInputInset/TextInputInset.mdx +62 -0
  194. package/src/components/TextInputInset/TextInputInset.module.scss +418 -0
  195. package/src/components/TextInputInset/TextInputInset.stories.tsx +213 -0
  196. package/src/components/TextInputInset/TextInputInset.test.tsx +222 -0
  197. package/src/components/TextInputInset/TextInputInset.tsx +261 -0
  198. package/src/components/TextareaInput/TextareaInput.mdx +117 -0
  199. package/src/components/TextareaInput/TextareaInput.module.scss +275 -0
  200. package/src/components/TextareaInput/TextareaInput.stories.tsx +293 -0
  201. package/src/components/TextareaInput/TextareaInput.test.tsx +195 -0
  202. package/src/components/TextareaInput/TextareaInput.tsx +182 -0
  203. package/src/components/TextareaInputInset/TextareaInputInset.mdx +55 -0
  204. package/src/components/TextareaInputInset/TextareaInputInset.module.scss +337 -0
  205. package/src/components/TextareaInputInset/TextareaInputInset.stories.tsx +160 -0
  206. package/src/components/TextareaInputInset/TextareaInputInset.test.tsx +199 -0
  207. package/src/components/TextareaInputInset/TextareaInputInset.tsx +213 -0
  208. package/src/components/ThemeProvider/ThemeProvider.mdx +11 -0
  209. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +56 -0
  210. package/src/components/ThemeProvider/ThemeProvider.tsx +75 -0
  211. package/src/components/TimePicker/TimePicker.mdx +75 -0
  212. package/src/components/TimePicker/TimePicker.stories.tsx +149 -0
  213. package/src/components/TimePicker/TimePicker.test.tsx +97 -0
  214. package/src/components/TimePicker/TimePicker.tsx +83 -0
  215. package/src/components/TimePickerNative/TimePickerNative.mdx +67 -0
  216. package/src/components/TimePickerNative/TimePickerNative.stories.tsx +151 -0
  217. package/src/components/TimePickerNative/TimePickerNative.test.tsx +117 -0
  218. package/src/components/TimePickerNative/TimePickerNative.tsx +93 -0
  219. package/src/components/Toast/Toast.mdx +134 -0
  220. package/src/components/Toast/Toast.store.ts +280 -0
  221. package/src/components/Toast/Toast.stories.tsx +283 -0
  222. package/src/components/Toast/Toast.test.tsx +784 -0
  223. package/src/components/Toast/Toast.types.ts +98 -0
  224. package/src/components/Toast/ToastContainer.tsx +170 -0
  225. package/src/components/Toast/ToastNotification.module.scss +63 -0
  226. package/src/components/Toast/ToastNotification.tsx +176 -0
  227. package/src/components/Toast/index.ts +4 -0
  228. package/src/components/Toast/toast.ts +102 -0
  229. package/src/components/Toast/useToasts.ts +102 -0
  230. package/src/components/Toggle/Toggle.mdx +51 -0
  231. package/src/components/Toggle/Toggle.module.scss +294 -0
  232. package/src/components/Toggle/Toggle.stories.tsx +128 -0
  233. package/src/components/Toggle/Toggle.test.tsx +308 -0
  234. package/src/components/Toggle/Toggle.tsx +184 -0
  235. package/src/constants/keyCodes.ts +2 -0
  236. package/src/docs/Brands.mdx +153 -0
  237. package/src/docs/Colors.mdx +158 -0
  238. package/src/docs/DesignTokens.mdx +415 -0
  239. package/src/docs/GetStarted.mdx +47 -0
  240. package/src/docs/intro.mdx +12 -0
  241. package/src/fonts/AvenirBold.otf +0 -0
  242. package/src/fonts/AvenirBold.woff +0 -0
  243. package/src/fonts/AvenirBold.woff2 +0 -0
  244. package/src/fonts/AvenirLight.otf +0 -0
  245. package/src/fonts/AvenirLight.woff +0 -0
  246. package/src/fonts/AvenirLight.woff2 +0 -0
  247. package/src/fonts/AvenirRegular.otf +0 -0
  248. package/src/fonts/AvenirRegular.woff +0 -0
  249. package/src/fonts/AvenirRegular.woff2 +0 -0
  250. package/src/fonts/Geist-Bold.otf +0 -0
  251. package/src/fonts/Geist-Bold.woff +0 -0
  252. package/src/fonts/Geist-Bold.woff2 +0 -0
  253. package/src/fonts/Geist-Medium.otf +0 -0
  254. package/src/fonts/Geist-Medium.woff +0 -0
  255. package/src/fonts/Geist-Medium.woff2 +0 -0
  256. package/src/fonts/Geist-Regular.otf +0 -0
  257. package/src/fonts/Geist-Regular.woff +0 -0
  258. package/src/fonts/Geist-Regular.woff2 +0 -0
  259. package/src/fonts/Geist-SemiBold.otf +0 -0
  260. package/src/fonts/Geist-SemiBold.woff +0 -0
  261. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  262. package/src/fonts/GeistMono-Regular.otf +0 -0
  263. package/src/fonts/GeistMono-Regular.woff +0 -0
  264. package/src/fonts/GeistMono-Regular.woff2 +0 -0
  265. package/src/hooks/index.ts +4 -0
  266. package/src/hooks/useBreakpoint/useBreakpoint.mdx +26 -0
  267. package/src/hooks/useBreakpoint/useBreakpoint.stories.tsx +30 -0
  268. package/src/hooks/useBreakpoint/useBreakpoint.test.tsx +19 -0
  269. package/src/hooks/useBreakpoint/useBreakpoint.ts +50 -0
  270. package/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayouEffect.ts +5 -0
  271. package/src/hooks/useOpenClose/useOpenClose.mdx +15 -0
  272. package/src/hooks/useOpenClose/useOpenClose.stories.tsx +41 -0
  273. package/src/hooks/useOpenClose/useOpenClose.test.tsx +119 -0
  274. package/src/hooks/useOpenClose/useOpenClose.tsx +95 -0
  275. package/src/hooks/useWindowSize/useWindowSize.mdx +25 -0
  276. package/src/hooks/useWindowSize/useWindowSize.stories.tsx +35 -0
  277. package/src/hooks/useWindowSize/useWindowSize.ts +24 -0
  278. package/src/index.ts +48 -0
  279. package/src/lib/cssShorthandToClasses.test.ts +149 -0
  280. package/src/lib/cssShorthandToClasses.ts +133 -0
  281. package/src/lib/doesStringIncludeCssUnit.ts +6 -0
  282. package/src/lib/generateResponsiveClasses.test.ts +24 -0
  283. package/src/lib/generateResponsiveClasses.ts +30 -0
  284. package/src/lib/getAutoCompleteValue.test.ts +27 -0
  285. package/src/lib/getAutoCompleteValue.ts +12 -0
  286. package/src/lib/getColumnKeys.ts +27 -0
  287. package/src/lib/getDimensionCss.test.ts +148 -0
  288. package/src/lib/getDimensionCss.ts +73 -0
  289. package/src/lib/getElementType.test.tsx +42 -0
  290. package/src/lib/getElementType.ts +42 -0
  291. package/src/lib/getFlexCss.test.ts +122 -0
  292. package/src/lib/getFlexCss.ts +67 -0
  293. package/src/lib/index.ts +15 -0
  294. package/src/lib/isFunction.ts +6 -0
  295. package/src/lib/mergeRefs.ts +15 -0
  296. package/src/lib/prefersReducedMotion.ts +12 -0
  297. package/src/lib/react-children-utilities/filter.ts +12 -0
  298. package/src/lib/react-children-utilities/index.ts +1 -0
  299. package/src/lib/reactRouterClickHandler.ts +37 -0
  300. package/src/lib/resolveValue.ts +7 -0
  301. package/src/lib/tokens.ts +139 -0
  302. package/src/modes.ts +8 -0
  303. package/src/styles/animation.scss +152 -0
  304. package/src/styles/cursor.scss +43 -0
  305. package/src/styles/display.scss +119 -0
  306. package/src/styles/flex.scss +453 -0
  307. package/src/styles/fonts.scss +44 -0
  308. package/src/styles/globals/utilities.scss +4 -0
  309. package/src/styles/mixins.scss +14 -0
  310. package/src/styles/overflow.scss +41 -0
  311. package/src/styles/position.scss +45 -0
  312. package/src/styles/reset.scss +108 -0
  313. package/src/styles/text-align.scss +21 -0
  314. package/src/styles/utilities.scss +9 -0
  315. package/src/styles/variables/forms.scss +71 -0
  316. package/src/styles/variables/index.scss +3 -0
  317. package/src/styles/white-space.scss +21 -0
  318. package/src/types/index.ts +201 -0
  319. package/src/types/lib.types.ts +3 -0
@@ -0,0 +1,205 @@
1
+ import React from 'react';
2
+ import {
3
+ render,
4
+ fireEvent,
5
+ screen,
6
+ waitFor,
7
+ act,
8
+ } from '@testing-library/react';
9
+ import { Formik, Form, Field, getIn, setIn, FormikValues } from 'formik';
10
+ import { FormikRadioGroup } from './FormikRadioGroup';
11
+
12
+ const testGroupName = 'colors';
13
+
14
+ const groupOptions = [
15
+ {
16
+ id: 'purple',
17
+ value: 'purple',
18
+ label: 'Purple',
19
+ },
20
+ {
21
+ id: 'green',
22
+ value: 'green',
23
+ label: 'Green',
24
+ },
25
+ {
26
+ id: 'blue',
27
+ value: 'blue',
28
+ label: 'Blue',
29
+ disabled: true,
30
+ },
31
+ ];
32
+
33
+ const handleValidation = (testValueKey: string) => (values: FormikValues) =>
34
+ getIn(values, testValueKey)
35
+ ? {}
36
+ : setIn({}, testValueKey, 'selection is required');
37
+
38
+ const renderForm = (
39
+ initialValue: any,
40
+ props: any,
41
+ testValueKey = testGroupName
42
+ ) => (
43
+ <Formik
44
+ initialValues={{
45
+ [testGroupName]: initialValue,
46
+ }}
47
+ onSubmit={props.handleSubmit} // eslint-disable-line
48
+ validate={props.isRequired ? handleValidation(testValueKey) : undefined} // eslint-disable-line
49
+ >
50
+ {() => (
51
+ <Form noValidate>
52
+ <Field
53
+ label={testValueKey}
54
+ name={testValueKey}
55
+ id={testValueKey}
56
+ options={groupOptions}
57
+ component={FormikRadioGroup}
58
+ {...props}
59
+ />
60
+ <button type="submit">submit</button>
61
+ </Form>
62
+ )}
63
+ </Formik>
64
+ );
65
+
66
+ describe('FormikRadioGroup', () => {
67
+ describe('Callback Handling', () => {
68
+ describe('onChange', () => {
69
+ test("Custom onChange event fires callback function, overwriting Formik's onChange", () => {
70
+ let value = null;
71
+ const mockedHandleChange = jest.fn((event) => {
72
+ value = event.target.value;
73
+ });
74
+
75
+ const { getByLabelText } = render(
76
+ renderForm(value, { onChange: mockedHandleChange })
77
+ );
78
+ const blueRadioInput = getByLabelText('Blue');
79
+
80
+ act(() => {
81
+ fireEvent.click(blueRadioInput);
82
+ });
83
+
84
+ expect(mockedHandleChange).toHaveBeenCalledTimes(1);
85
+ expect(value).toBe('blue');
86
+ });
87
+
88
+ test('Standard Formik onChange modifies the target value', async () => {
89
+ const { getByLabelText, getByText, queryByText } = render(
90
+ renderForm(null, { isRequired: true })
91
+ );
92
+ const submitButton = getByText('submit');
93
+ const blueRadioInput = getByLabelText('Blue') as HTMLInputElement;
94
+ expect(blueRadioInput.checked).toBe(false);
95
+
96
+ fireEvent.click(submitButton);
97
+ await waitFor(() =>
98
+ expect(getByText('selection is required')).toBeInTheDocument()
99
+ );
100
+
101
+ act(() => {
102
+ fireEvent.click(blueRadioInput);
103
+ });
104
+ expect(blueRadioInput.checked).toBe(true);
105
+ await waitFor(() =>
106
+ expect(queryByText('selection is required')).not.toBeInTheDocument()
107
+ );
108
+ });
109
+ });
110
+ });
111
+
112
+ describe('States', () => {
113
+ describe('Default', () => {
114
+ test('it renders 3 radio inputs', () => {
115
+ render(renderForm(null, {}));
116
+
117
+ const radioInputElements = screen.getAllByRole('radio');
118
+ expect(radioInputElements).toHaveLength(3);
119
+ });
120
+ });
121
+
122
+ describe('With Title', () => {
123
+ test('it renders the title', () => {
124
+ render(renderForm(null, { title: 'Mock Title' }));
125
+
126
+ const title = screen.getByText('Mock Title');
127
+ expect(title).toBeInTheDocument();
128
+ });
129
+ });
130
+
131
+ describe('With Title and Description', () => {
132
+ test('it renders the title and description', () => {
133
+ render(
134
+ renderForm(null, {
135
+ title: 'Mock Title',
136
+ description: 'Mock Description',
137
+ })
138
+ );
139
+
140
+ const title = screen.getByText('Mock Title');
141
+ const description = screen.getByText('Mock Description');
142
+ expect(title).toBeInTheDocument();
143
+ expect(description).toBeInTheDocument();
144
+ });
145
+ });
146
+
147
+ describe('Pre-Selected Option', () => {
148
+ test('an option is automatically selected', () => {
149
+ render(renderForm('green', {}));
150
+
151
+ const greenRadioInput = screen.getByLabelText('Green');
152
+ expect(greenRadioInput).toBeChecked();
153
+ });
154
+ });
155
+
156
+ describe('Disabled Option', () => {
157
+ test('the group contains a disabled option', () => {
158
+ render(renderForm(null, {}));
159
+
160
+ const disabledRadioInputElements = screen.getAllByRole('radio');
161
+ expect(disabledRadioInputElements[2]).toBeDisabled();
162
+ });
163
+ });
164
+
165
+ describe('Disabled Group', () => {
166
+ test('all options in the group are disabled', () => {
167
+ render(renderForm(null, { isDisabled: true }));
168
+
169
+ const disabledRadioInputElements = screen.getAllByRole('radio');
170
+ expect(disabledRadioInputElements[0]).toBeDisabled();
171
+ expect(disabledRadioInputElements[1]).toBeDisabled();
172
+ expect(disabledRadioInputElements[2]).toBeDisabled();
173
+ });
174
+ });
175
+
176
+ describe('Error with Validation Message', () => {
177
+ test('it renders a validation message', async () => {
178
+ const { getByText } = render(renderForm(null, { isRequired: true }));
179
+
180
+ const submitButton = getByText('submit');
181
+
182
+ fireEvent.click(submitButton);
183
+
184
+ await waitFor(() =>
185
+ expect(getByText('selection is required')).toBeInTheDocument()
186
+ );
187
+ });
188
+
189
+ test('it renders a validation message with nested value', async () => {
190
+ const { getByText } = render(
191
+ renderForm(
192
+ { outer: { nested: null } },
193
+ { isRequired: true },
194
+ `${testGroupName}.outer.nested`
195
+ )
196
+ );
197
+ const submitButton = getByText('submit');
198
+ fireEvent.click(submitButton);
199
+ await waitFor(() =>
200
+ expect(getByText('selection is required')).toBeInTheDocument()
201
+ );
202
+ });
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import {
3
+ FormikTouched,
4
+ FormikErrors,
5
+ FieldAttributes,
6
+ FormikValues,
7
+ getIn,
8
+ } from 'formik';
9
+ import { RadioGroup, RadioGroupProps } from '../../RadioGroup/RadioGroup';
10
+
11
+ export interface FormikRadioGroupProps
12
+ extends Omit<RadioGroupProps, 'onChange'> {
13
+ field: FieldAttributes<HTMLInputElement>;
14
+ form: {
15
+ touched: FormikTouched<FormikValues>;
16
+ errors: FormikErrors<FormikValues>;
17
+ };
18
+ onChange?: RadioGroupProps['onChange'];
19
+ }
20
+
21
+ export const FormikRadioGroup: React.FC<FormikRadioGroupProps> = ({
22
+ field: { name, onBlur, onChange: formikOnChange, value },
23
+ form: { touched, errors },
24
+ onChange,
25
+ options,
26
+ ...props
27
+ }) => (
28
+ <RadioGroup
29
+ {...props}
30
+ options={options}
31
+ name={name}
32
+ onBlur={onBlur}
33
+ onChange={onChange ?? formikOnChange}
34
+ value={value}
35
+ error={getIn(touched, name) && getIn(errors, name)}
36
+ />
37
+ );
@@ -0,0 +1,210 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import selectEvent from 'react-select-event';
4
+ import { Formik, Form, Field, getIn, setIn } from 'formik';
5
+ import { FormikSelectInput } from './FormikSelectInput';
6
+ import { SelectInput } from '../../SelectInput/SelectInput';
7
+
8
+ const testLabelName = 'test select';
9
+
10
+ const selectOptions = [
11
+ { value: 'chocolate', label: 'Chocolate' },
12
+ { value: 'strawberry', label: 'Strawberry' },
13
+ { value: 'vanilla', label: 'Vanilla' },
14
+ ];
15
+
16
+ const handleValidation = (testValueKey: string | string[]) => (values: any) =>
17
+ getIn(values, testValueKey)?.length > 1
18
+ ? {}
19
+ : setIn({}, testValueKey as string, 'input is required');
20
+
21
+ const renderForm = (
22
+ initialValue: any,
23
+ props: any,
24
+ testValueKey = testLabelName
25
+ ) => (
26
+ <Formik
27
+ initialValues={{
28
+ [testLabelName]: initialValue,
29
+ }}
30
+ validate={props.isRequired ? handleValidation(testValueKey) : undefined} // eslint-disable-line
31
+ onSubmit={async () => null}
32
+ >
33
+ {() => (
34
+ <Form>
35
+ <Field
36
+ label={testValueKey}
37
+ name={testValueKey}
38
+ id={testValueKey}
39
+ options={selectOptions}
40
+ component={FormikSelectInput}
41
+ {...props}
42
+ />
43
+ <button type="submit">submit</button>
44
+ </Form>
45
+ )}
46
+ </Formik>
47
+ );
48
+
49
+ describe('FormikSelectInput', () => {
50
+ describe('States', () => {
51
+ describe('Hidden label, with a placeholder', () => {
52
+ test('it renders input without a visual label, and with a placeholder', () => {
53
+ render(
54
+ renderForm([], { placeholder: 'Test Placeholder', hideLabel: true })
55
+ );
56
+ expect(screen.queryByText(testLabelName)).toBeNull();
57
+ expect(screen.getByText('Test Placeholder')).toBeInTheDocument();
58
+ });
59
+ });
60
+
61
+ describe('No Aria-labelledby', () => {
62
+ test('does not assign "aria-labelledby" attribute when a label is hidden', () => {
63
+ render(renderForm([], { hideLabel: true }));
64
+ const inputElement = screen.getByLabelText(testLabelName);
65
+ expect(inputElement).not.toHaveAttribute('aria-labelledby');
66
+ });
67
+ });
68
+
69
+ describe('With a label, and no custom placeholder', () => {
70
+ test('it renders input with a label, and with a default placeholder', () => {
71
+ render(renderForm([], {}));
72
+
73
+ expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
74
+ expect(screen.getByText('Select...')).toBeInTheDocument();
75
+ });
76
+
77
+ test('assigns the "aria-labelledby" attribute and renders label with correct id, when label is provided', () => {
78
+ render(renderForm([], {}));
79
+ const inputElement = screen.getByLabelText(testLabelName);
80
+ expect(inputElement).toHaveAttribute(
81
+ 'aria-labelledby',
82
+ `${testLabelName}Label`
83
+ );
84
+ expect(
85
+ document.getElementById(`${testLabelName}Label`)
86
+ ).toBeInTheDocument();
87
+ });
88
+ });
89
+
90
+ describe('Single select, pre-selected', () => {
91
+ test('it renders with value pre-selected', () => {
92
+ render(renderForm(selectOptions[2], {}));
93
+
94
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
95
+ });
96
+ });
97
+
98
+ describe('Multi select, no selection', () => {
99
+ test('it renders input with a label, and with a default placeholder', () => {
100
+ render(renderForm([], { isMulti: true }));
101
+
102
+ expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
103
+ expect(screen.getByText('Select...')).toBeInTheDocument();
104
+ });
105
+ });
106
+
107
+ describe('Multi select, with multiple items selected', () => {
108
+ test('it renders input with a label, and with two items selected', () => {
109
+ render(
110
+ renderForm([selectOptions[0], selectOptions[2]], { isMulti: true })
111
+ );
112
+
113
+ expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
114
+ expect(screen.queryByText('Select...')).toBeNull();
115
+ expect(screen.getByText('Chocolate')).toBeInTheDocument();
116
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
117
+ });
118
+ });
119
+
120
+ describe('Is Disabled', () => {
121
+ test('it disables the input', () => {
122
+ const { container } = render(renderForm([], { isDisabled: true }));
123
+
124
+ const combobox = container.querySelector(
125
+ '.react-select__control[aria-disabled="true"]'
126
+ );
127
+
128
+ expect(combobox).toBeInTheDocument();
129
+ });
130
+ });
131
+
132
+ describe('Is Invalid, with a helpful message', () => {
133
+ test('it renders the helpful message', async () => {
134
+ const { getByText } = render(renderForm([], { isRequired: true }));
135
+ const submitButton = getByText('submit');
136
+
137
+ fireEvent.click(submitButton);
138
+ await waitFor(() =>
139
+ expect(screen.getByText('input is required')).toBeInTheDocument()
140
+ );
141
+ });
142
+
143
+ test('it renders errors from nested objects', async () => {
144
+ const { getByText } = render(
145
+ renderForm(
146
+ { outer: { nested: [] } },
147
+ { isRequired: true },
148
+ `${testLabelName}.outer.nested`
149
+ )
150
+ );
151
+ const submitButton = getByText('submit');
152
+
153
+ fireEvent.click(submitButton);
154
+ await waitFor(() =>
155
+ expect(screen.getByText('input is required')).toBeInTheDocument()
156
+ );
157
+ });
158
+ });
159
+ });
160
+
161
+ describe('Callback Handling', () => {
162
+ describe('onChange', () => {
163
+ test("Custom onChange event fires callback function, overwriting Formik's onChange", async () => {
164
+ let value: any[] = [];
165
+ const mockedHandleChange = jest.fn((event) => {
166
+ value = event.target.value;
167
+ });
168
+
169
+ const { getByLabelText, container, getByText } = render(
170
+ renderForm(value, { onChange: mockedHandleChange })
171
+ );
172
+ const selectInput = getByLabelText(testLabelName);
173
+ /**
174
+ * This class is specific to react-select, combined with our custom classNamePrefix prop.
175
+ * While this is an implementation detail there appears to be
176
+ * no clearer path to test our own component which depends on react-select
177
+ */
178
+ const selectInputWrapper = container.querySelector(
179
+ '.react-select__control'
180
+ );
181
+
182
+ fireEvent.focus(selectInput);
183
+ fireEvent.mouseDown(selectInputWrapper!);
184
+ const option = await waitFor(() => getByText('Vanilla'), { container });
185
+ fireEvent.click(option);
186
+ expect(mockedHandleChange).toHaveBeenCalledTimes(1);
187
+ expect(value).toStrictEqual({ label: 'Vanilla', value: 'vanilla' });
188
+ });
189
+
190
+ test('it fires onChange callback on change', async () => {
191
+ const mockedHandleChange = jest.fn();
192
+
193
+ const { getByLabelText } = render(
194
+ <SelectInput
195
+ id="testId"
196
+ onChange={mockedHandleChange}
197
+ placeholder="Test Placeholder"
198
+ label="onchange test"
199
+ options={selectOptions}
200
+ value={null}
201
+ />
202
+ );
203
+
204
+ await selectEvent.select(getByLabelText('onchange test'), 'Vanilla');
205
+
206
+ expect(mockedHandleChange).toBeCalledTimes(1);
207
+ });
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import {
3
+ FormikTouched,
4
+ FormikErrors,
5
+ FieldAttributes,
6
+ FormikValues,
7
+ getIn,
8
+ } from 'formik';
9
+ import { SelectInput, SelectInputProps } from '../../SelectInput/SelectInput';
10
+
11
+ export interface FormikSelectInputProps
12
+ extends Omit<SelectInputProps, 'onChange'> {
13
+ field: FieldAttributes<HTMLSelectElement>;
14
+ form: {
15
+ touched: FormikTouched<FormikValues>;
16
+ errors: FormikErrors<FormikValues>;
17
+ };
18
+ onChange?: SelectInputProps['onChange'];
19
+ }
20
+
21
+ export const FormikSelectInput: React.FC<FormikSelectInputProps> = ({
22
+ field: { name, onBlur, onChange: formikOnChange, value },
23
+ form: { touched, errors },
24
+ onChange,
25
+ id,
26
+ label,
27
+ options,
28
+ ...props
29
+ }) => (
30
+ <SelectInput
31
+ {...props}
32
+ id={id}
33
+ label={label}
34
+ options={options}
35
+ name={name}
36
+ onBlur={onBlur}
37
+ onChange={onChange ?? formikOnChange}
38
+ value={value}
39
+ error={getIn(touched, name) && getIn(errors, name)}
40
+ />
41
+ );
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import { Formik, Form, Field, getIn, setIn } from 'formik';
4
+ import { SelectInputInset } from '../../SelectInputInset/SelectInputInset';
5
+ import { FormikSelectInputInset } from './FormikSelectInputInset';
6
+
7
+ const testLabelName = 'test select';
8
+
9
+ const selectOptions = [
10
+ { value: 'chocolate', label: 'Chocolate' },
11
+ { value: 'strawberry', label: 'Strawberry' },
12
+ { value: 'vanilla', label: 'Vanilla' },
13
+ ];
14
+
15
+ const handleValidation = (testValueKey: string | string[]) => (values: any) =>
16
+ getIn(values, testValueKey)?.length > 1
17
+ ? {}
18
+ : setIn({}, testValueKey as string, 'input is required');
19
+
20
+ const renderForm = (
21
+ initialValue: any,
22
+ props: any,
23
+ testValueKey = testLabelName
24
+ ) => (
25
+ <Formik
26
+ initialValues={{
27
+ [testLabelName]: initialValue,
28
+ }}
29
+ validate={props.isRequired ? handleValidation(testValueKey) : undefined} // eslint-disable-line
30
+ onSubmit={async () => null}
31
+ >
32
+ {() => (
33
+ <Form noValidate>
34
+ <Field
35
+ label={testValueKey}
36
+ name={testValueKey}
37
+ id={testValueKey}
38
+ options={selectOptions}
39
+ component={FormikSelectInputInset}
40
+ {...props}
41
+ />
42
+ <button type="submit">submit</button>
43
+ </Form>
44
+ )}
45
+ </Formik>
46
+ );
47
+
48
+ describe('FormikSelectInputInset', () => {
49
+ describe('States', () => {
50
+ describe('With a label, and no custom placeholder', () => {
51
+ test('it renders input with a label, and with a default placeholder', () => {
52
+ render(renderForm([], {}));
53
+
54
+ expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
55
+ expect(screen.getByText('Select...')).toBeInTheDocument();
56
+ });
57
+
58
+ test('assigns the "aria-labelledby" attribute and renders label with correct id, when label is provided', () => {
59
+ render(renderForm([], {}));
60
+ const inputElement = screen.getByLabelText(testLabelName);
61
+ expect(inputElement).toHaveAttribute(
62
+ 'aria-labelledby',
63
+ `${testLabelName}Label`
64
+ );
65
+ expect(
66
+ document.getElementById(`${testLabelName}Label`)
67
+ ).toBeInTheDocument();
68
+ });
69
+ });
70
+
71
+ describe('Single select, pre-selected', () => {
72
+ test('it renders with value pre-selected', () => {
73
+ render(renderForm(selectOptions[2], {}));
74
+
75
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
76
+ });
77
+ });
78
+
79
+ describe('Is Disabled', () => {
80
+ test('it disables the input', () => {
81
+ const { getByRole } = render(renderForm([], { isDisabled: true }));
82
+
83
+ const combobox = getByRole('combobox');
84
+ expect(combobox).toBeDisabled();
85
+ });
86
+ });
87
+
88
+ describe('Is Invalid, with a helpful message', () => {
89
+ test('it renders the helpful message', async () => {
90
+ const { getByText } = render(renderForm([], { isRequired: true }));
91
+ const submitButton = getByText('submit');
92
+
93
+ fireEvent.click(submitButton);
94
+ await waitFor(() =>
95
+ expect(screen.getByText('input is required')).toBeInTheDocument()
96
+ );
97
+ });
98
+
99
+ test('it renders errors from nested objects', async () => {
100
+ const { getByText } = render(
101
+ renderForm(
102
+ { outer: { nested: [] } },
103
+ { isRequired: true },
104
+ `${testLabelName}.outer.nested`
105
+ )
106
+ );
107
+ const submitButton = getByText('submit');
108
+
109
+ fireEvent.click(submitButton);
110
+ await waitFor(() =>
111
+ expect(screen.getByText('input is required')).toBeInTheDocument()
112
+ );
113
+ });
114
+ });
115
+ });
116
+
117
+ describe('Callback Handling', () => {
118
+ describe('onChange', () => {
119
+ test("Custom onChange event fires callback function, overwriting Formik's onChange", async () => {
120
+ let value = null;
121
+ const mockedHandleChange = jest.fn((event) => {
122
+ value = event.target.value;
123
+ });
124
+
125
+ const { getByLabelText } = render(
126
+ renderForm(value, { onChange: mockedHandleChange })
127
+ );
128
+ const selectInput = getByLabelText(testLabelName);
129
+
130
+ fireEvent.change(selectInput);
131
+ expect(mockedHandleChange).toHaveBeenCalledTimes(1);
132
+ });
133
+
134
+ test('it fires onChange callback on change', async () => {
135
+ const mockedHandleChange = jest.fn();
136
+
137
+ const { getByRole } = render(
138
+ <SelectInputInset
139
+ id="testId"
140
+ onChange={mockedHandleChange}
141
+ placeholder="Test Placeholder"
142
+ label="onchange test"
143
+ options={selectOptions}
144
+ value={null}
145
+ />
146
+ );
147
+ const combobox = getByRole('combobox');
148
+ fireEvent.change(combobox, { target: { value: 'vanilla' } });
149
+ expect(mockedHandleChange).toBeCalledTimes(1);
150
+ });
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import {
3
+ FormikTouched,
4
+ FormikErrors,
5
+ FieldAttributes,
6
+ FormikValues,
7
+ getIn,
8
+ } from 'formik';
9
+ import {
10
+ SelectInputInset,
11
+ SelectInputInsetProps,
12
+ } from '../../SelectInputInset/SelectInputInset';
13
+
14
+ export interface FormikSelectInputInsetProps
15
+ extends Omit<SelectInputInsetProps, 'onChange'> {
16
+ field: FieldAttributes<HTMLSelectElement>;
17
+ form: {
18
+ touched: FormikTouched<FormikValues>;
19
+ errors: FormikErrors<FormikValues>;
20
+ };
21
+ onChange?: SelectInputInsetProps['onChange'];
22
+ }
23
+
24
+ export const FormikSelectInputInset: React.FC<FormikSelectInputInsetProps> = ({
25
+ field: { name, onBlur, onChange: formikOnChange, value },
26
+ form: { touched, errors },
27
+ onChange,
28
+ id,
29
+ label,
30
+ options,
31
+ ...props
32
+ }) => (
33
+ <SelectInputInset
34
+ {...props}
35
+ id={id}
36
+ label={label}
37
+ options={options}
38
+ name={name}
39
+ onBlur={onBlur}
40
+ onChange={onChange ?? formikOnChange}
41
+ value={value}
42
+ error={getIn(touched, name) && getIn(errors, name)}
43
+ />
44
+ );