@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,305 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen } from '@testing-library/react';
3
+ import { BreakpointSize } from '../../types';
4
+ import { SelectInputInset } from './SelectInputInset';
5
+
6
+ const selectOptions = [
7
+ { value: 'chocolate', label: 'Chocolate' },
8
+ { value: 'strawberry', label: 'Strawberry' },
9
+ { value: 'vanilla', label: 'Vanilla' },
10
+ ];
11
+
12
+ describe('SelectInputInset', () => {
13
+ describe('Callback Handling', () => {
14
+ test('it fires onChange callback on change', async () => {
15
+ const mockedHandleChange = jest.fn();
16
+
17
+ const { getByLabelText } = render(
18
+ <SelectInputInset
19
+ id="testId"
20
+ onChange={mockedHandleChange}
21
+ placeholder="Test Placeholder"
22
+ label="onchange test"
23
+ options={selectOptions}
24
+ value={null}
25
+ />
26
+ );
27
+
28
+ await fireEvent.change(getByLabelText('onchange test'));
29
+
30
+ expect(mockedHandleChange).toBeCalledTimes(1);
31
+ });
32
+
33
+ test('it fires onFocus callback on focus', () => {
34
+ const mockedHandleChange = jest.fn();
35
+ const mockedHandleFocus = jest.fn();
36
+
37
+ render(
38
+ <SelectInputInset
39
+ id="testId"
40
+ onChange={mockedHandleChange}
41
+ onFocus={mockedHandleFocus}
42
+ placeholder="Test Placeholder"
43
+ options={selectOptions}
44
+ value={null}
45
+ label="onfocus test"
46
+ />
47
+ );
48
+ const select = screen.getByLabelText('onfocus test');
49
+ fireEvent.focus(select);
50
+
51
+ expect(mockedHandleFocus).toBeCalledTimes(1);
52
+ });
53
+
54
+ test('it fires onBlur callback on blur', () => {
55
+ const mockedHandleChange = jest.fn();
56
+ const mockedHandleBlur = jest.fn();
57
+
58
+ render(
59
+ <SelectInputInset
60
+ id="testId"
61
+ onChange={mockedHandleChange}
62
+ onBlur={mockedHandleBlur}
63
+ placeholder="Test Placeholder"
64
+ options={selectOptions}
65
+ value={null}
66
+ label="onblur test"
67
+ />
68
+ );
69
+
70
+ const select = screen.getByLabelText('onblur test');
71
+ fireEvent.blur(select);
72
+
73
+ expect(mockedHandleBlur).toBeCalledTimes(1);
74
+ });
75
+ });
76
+
77
+ describe('onClear', () => {
78
+ test('onClear prop renders clear icon when input has value', () => {
79
+ const mockedHandleChange = jest.fn();
80
+ render(
81
+ <SelectInputInset
82
+ id="testId"
83
+ onChange={mockedHandleChange}
84
+ options={selectOptions}
85
+ value="chocolate"
86
+ label="onClear test"
87
+ onClear={() => null}
88
+ />
89
+ );
90
+ const clearButton = screen.getByTestId('text-input-clear-button');
91
+ expect(clearButton).toBeInTheDocument();
92
+ });
93
+
94
+ test('onClear event fires callback function', () => {
95
+ const mockedHandleChange = jest.fn();
96
+ const mockedHandleClear = jest.fn(() => null);
97
+
98
+ render(
99
+ <SelectInputInset
100
+ id="testId"
101
+ onChange={mockedHandleChange}
102
+ options={selectOptions}
103
+ value="chocolate"
104
+ label="onClear test"
105
+ onClear={mockedHandleClear}
106
+ />
107
+ );
108
+ const clearButton = screen.getByTestId('text-input-clear-button');
109
+ expect(clearButton).toBeInTheDocument();
110
+
111
+ fireEvent.click(clearButton);
112
+ expect(mockedHandleClear).toHaveBeenCalledTimes(1);
113
+ fireEvent.keyUp(clearButton, { keyCode: 13 });
114
+ expect(mockedHandleClear).toHaveBeenCalledTimes(2);
115
+ fireEvent.keyUp(clearButton, { keyCode: 99 });
116
+ expect(mockedHandleClear).toHaveBeenCalledTimes(2);
117
+ });
118
+ });
119
+
120
+ describe('States', () => {
121
+ describe('With a label, and no custom placeholder', () => {
122
+ test('it renders input with a label, and with a default placeholder', () => {
123
+ const mockedHandleChange = jest.fn();
124
+
125
+ render(
126
+ <SelectInputInset
127
+ id="testId"
128
+ onChange={mockedHandleChange}
129
+ label="Select Label"
130
+ options={selectOptions}
131
+ value={selectOptions[1].value}
132
+ />
133
+ );
134
+
135
+ expect(screen.getByLabelText('Select Label')).toBeInTheDocument();
136
+ expect(screen.getByText('Select...')).toBeInTheDocument();
137
+ });
138
+
139
+ test('assigns the "aria-labelledby" attribute and renders label correct id, when a label is provided', () => {
140
+ render(
141
+ <SelectInputInset
142
+ id="testInput"
143
+ label="test label"
144
+ options={selectOptions}
145
+ value={null}
146
+ onChange={() => null}
147
+ />
148
+ );
149
+ const inputElement = screen.getByLabelText('test label');
150
+ expect(inputElement).toHaveAttribute(
151
+ 'aria-labelledby',
152
+ 'testInputLabel'
153
+ );
154
+ expect(document.getElementById('testInputLabel')).toBeInTheDocument();
155
+ });
156
+ });
157
+
158
+ describe('Is Required', () => {
159
+ test('it sets the required and aria-required properties on the input', () => {
160
+ const mockedHandleChange = jest.fn();
161
+
162
+ render(
163
+ <SelectInputInset
164
+ id="testId"
165
+ onChange={mockedHandleChange}
166
+ label="Required Select"
167
+ options={selectOptions}
168
+ isRequired
169
+ value={selectOptions[0].value}
170
+ />
171
+ );
172
+
173
+ const inputElement = screen.getByLabelText('Required Select');
174
+ expect(inputElement).toHaveAttribute('aria-required', 'true');
175
+ expect(inputElement).toHaveAttribute('required');
176
+ });
177
+
178
+ test('it renders an asterisk in the label by default', () => {
179
+ const mockedHandleChange = jest.fn();
180
+
181
+ render(
182
+ <SelectInputInset
183
+ id="testId"
184
+ onChange={mockedHandleChange}
185
+ label="Select Label"
186
+ options={selectOptions}
187
+ isRequired
188
+ value={selectOptions[0].value}
189
+ />
190
+ );
191
+
192
+ expect(screen.getByText('*')).toBeInTheDocument();
193
+ });
194
+ });
195
+
196
+ describe('Is Disabled', () => {
197
+ test('it disables the input', () => {
198
+ const mockedHandleChange = jest.fn();
199
+
200
+ render(
201
+ <SelectInputInset
202
+ id="testId"
203
+ onChange={mockedHandleChange}
204
+ label="disabled test"
205
+ options={selectOptions}
206
+ isDisabled
207
+ value={null}
208
+ />
209
+ );
210
+ const select = screen.getByLabelText('disabled test');
211
+ expect(select).toBeDisabled();
212
+ });
213
+ });
214
+
215
+ describe('Is Invalid, with a helpful message', () => {
216
+ test('it renders the helpful message', () => {
217
+ const mockedHandleChange = jest.fn();
218
+
219
+ render(
220
+ <SelectInputInset
221
+ id="testId"
222
+ onChange={mockedHandleChange}
223
+ label="Select Label"
224
+ options={selectOptions}
225
+ error="Helpful message"
226
+ value={null}
227
+ />
228
+ );
229
+
230
+ expect(screen.getByText('Helpful message')).toBeInTheDocument();
231
+ });
232
+ });
233
+ });
234
+
235
+ describe('Sizes', () => {
236
+ const mockedHandleChange = jest.fn();
237
+ const sizes = ['md', 'lg'] as ('md' | 'lg')[];
238
+
239
+ const breakpoints: BreakpointSize[] = ['tablet', 'desktop', 'hd'];
240
+
241
+ sizes.forEach((size) => {
242
+ test(`it has a ${size} class applied to it`, () => {
243
+ render(
244
+ <SelectInputInset
245
+ id="testId"
246
+ onChange={mockedHandleChange}
247
+ options={selectOptions}
248
+ value={selectOptions[0].value}
249
+ size={size}
250
+ label="size test"
251
+ />
252
+ );
253
+ const select = screen.getByLabelText('size test');
254
+ const selectParent = select.closest('div');
255
+ expect(selectParent?.getAttribute('class')).toContain(size);
256
+ });
257
+
258
+ breakpoints.forEach((breakpoint) => {
259
+ test(`it applies responsive classes for breakpoint: ${breakpoint} and size: ${size}`, () => {
260
+ render(
261
+ <SelectInputInset
262
+ id="testId"
263
+ onChange={mockedHandleChange}
264
+ options={selectOptions}
265
+ value={selectOptions[0].value}
266
+ size={{ [breakpoint]: size }}
267
+ label="size test"
268
+ />
269
+ );
270
+ const select = screen.getByLabelText('size test');
271
+ const selectParent = select.closest('div');
272
+
273
+ expect(selectParent?.getAttribute('class')).toContain(
274
+ `size-${size}-${breakpoint}`
275
+ );
276
+ });
277
+ });
278
+ });
279
+
280
+ test('It applies responsive classes when multiple are applied', () => {
281
+ render(
282
+ <SelectInputInset
283
+ id="testId"
284
+ onChange={mockedHandleChange}
285
+ options={selectOptions}
286
+ value={selectOptions[0].value}
287
+ size={{
288
+ base: 'md',
289
+ tablet: 'md',
290
+ desktop: 'lg',
291
+ hd: 'lg',
292
+ }}
293
+ label="size test"
294
+ />
295
+ );
296
+ const select = screen.getByLabelText('size test');
297
+ const selectParent = select.closest('div');
298
+
299
+ expect(selectParent?.getAttribute('class')).toContain('size-md');
300
+ expect(selectParent?.getAttribute('class')).toContain('size-md-tablet');
301
+ expect(selectParent?.getAttribute('class')).toContain('size-lg-desktop');
302
+ expect(selectParent?.getAttribute('class')).toContain('size-lg-hd');
303
+ });
304
+ });
305
+ });
@@ -0,0 +1,235 @@
1
+ import React, {
2
+ ChangeEvent,
3
+ forwardRef,
4
+ MouseEvent,
5
+ KeyboardEvent,
6
+ FocusEvent,
7
+ ForwardRefExoticComponent,
8
+ ReactNode,
9
+ HTMLProps,
10
+ } from 'react';
11
+ import classNames from 'classnames';
12
+ import { ResponsiveProp } from '../../types';
13
+ import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
14
+
15
+ import { Box, BoxProps } from '../Box/Box';
16
+ import { HelpText } from '../HelpText/HelpText';
17
+ import { Icon } from '../Icon/Icon';
18
+ import { getAutoCompleteValue } from '../../lib/getAutoCompleteValue';
19
+ import styles from './SelectInputInset.module.scss';
20
+ import { InputValidationMessage } from '../InputValidationMessage/InputValidationMessage';
21
+
22
+ export type SelectInputInsetSize = 'md' | 'lg';
23
+ export interface SelectInputInsetProps {
24
+ /**
25
+ * The input's id attribute. Used to programmatically tie the input with its label.
26
+ */
27
+ id: string;
28
+ /**
29
+ * Custom content to be displayed above the input. If the label is hidden, will be used to set aria-label attribute.
30
+ */
31
+ label: string;
32
+ /**
33
+ * List of options for the select input.
34
+ */
35
+ options: { value: string | number; label: string | number }[];
36
+ /**
37
+ * Callback function to call on change event.
38
+ */
39
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void;
40
+ /**
41
+ * Value of selected option. Should match the value key in the option object.
42
+ */
43
+ value: string | number | null;
44
+ /**
45
+ * Automatically focus the input when the page is loaded.
46
+ */
47
+ autoFocus?: boolean;
48
+ /**
49
+ * Custom class to be added to standard input classes.
50
+ */
51
+ className?: string;
52
+ /**
53
+ * Mark the input field as invalid and display a validation message.
54
+ * Pass a string or node to render a validation message below the input.
55
+ */
56
+ error?: ReactNode;
57
+ /**
58
+ * Additional clarifying text to help describe the input
59
+ */
60
+ helpText?: ReactNode;
61
+ /**
62
+ * Props passed directly to the input element of the component
63
+ */
64
+ inputProps?: BoxProps & HTMLProps<HTMLInputElement>;
65
+ /**
66
+ * The input's disabled attribute
67
+ */
68
+ isDisabled?: boolean;
69
+ /**
70
+ * The required and aria-required attributes on the input
71
+ */
72
+ isRequired?: boolean;
73
+ /**
74
+ * The input's 'name' attribute.
75
+ */
76
+ name?: string;
77
+ /**
78
+ * Callback function to call on blur event.
79
+ */
80
+ onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
81
+ /**
82
+ * Callback function to call when input us cleared. When this is passed,
83
+ * the input will display an icon on the right side, for triggering this callback.
84
+ */
85
+ onClear?: (
86
+ event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>
87
+ ) => void;
88
+ /**
89
+ * Callback function to call on focus event.
90
+ */
91
+ onFocus?: (event: FocusEvent<HTMLInputElement>) => void;
92
+ /**
93
+ * The input placeholder attribute.
94
+ */
95
+ placeholder?: string;
96
+ /**
97
+ * Visual indicator that the field is required, that gets appended to the label
98
+ */
99
+ requiredIndicator?: ReactNode;
100
+ /**
101
+ * The size of the text input.
102
+ */
103
+ size?: SelectInputInsetSize | ResponsiveProp<SelectInputInsetSize>;
104
+ /**
105
+ * Additional props to be spread to rendered element
106
+ */
107
+ [x: string]: any; // eslint-disable-line
108
+ }
109
+
110
+ export const SelectInputInset: ForwardRefExoticComponent<SelectInputInsetProps> =
111
+ forwardRef<HTMLDivElement, SelectInputInsetProps>(
112
+ (
113
+ {
114
+ id,
115
+ label,
116
+ onChange,
117
+ value,
118
+ autoComplete = false,
119
+ autoFocus = false,
120
+ error = false,
121
+ helpText,
122
+ inputProps = {},
123
+ isDisabled = false,
124
+ isRequired = false,
125
+ name = '',
126
+ onBlur = undefined,
127
+ onClear = undefined,
128
+ onFocus = undefined,
129
+ options,
130
+ placeholder = 'Select...',
131
+ requiredIndicator = ' *',
132
+ size = 'md',
133
+ },
134
+ ref
135
+ ) => {
136
+ const placeholderOption = { value: '', label: placeholder };
137
+ const optionsWithPlaceholder = [{ ...placeholderOption }, ...options];
138
+
139
+ const responsiveClasses = generateResponsiveClasses('size', size);
140
+
141
+ const inputWrapperClasses = classNames(
142
+ 'hyphen-components__variables__form-control',
143
+ styles['text-input-wrapper'],
144
+ ...responsiveClasses.map((c) => styles[c]),
145
+ {
146
+ [styles.error]: error,
147
+ [styles.disabled]: isDisabled,
148
+ [styles['is-clearable']]: onClear,
149
+ }
150
+ );
151
+
152
+ const clearBtnClasses = classNames(styles['clear-button'], styles.md);
153
+
154
+ const renderClearIcon = (): ReactNode => {
155
+ const handleKeyPress = (
156
+ event: KeyboardEvent<HTMLButtonElement>
157
+ ): void => {
158
+ if (event.keyCode === 13 && onClear) onClear(event);
159
+ };
160
+
161
+ return (
162
+ <button
163
+ type="button"
164
+ onClick={onClear}
165
+ onKeyUp={handleKeyPress}
166
+ className={clearBtnClasses}
167
+ data-testid="text-input-clear-button"
168
+ aria-label="clear input"
169
+ >
170
+ <Icon name="remove" className="display-block" />
171
+ </button>
172
+ );
173
+ };
174
+
175
+ const computedInputProps: SelectInputInsetProps['inputProps'] = {
176
+ ...inputProps, // These are spread first so that we don't have top level props overwritten by the user.
177
+ 'aria-required': isRequired,
178
+ 'aria-invalid': !!error,
179
+ 'aria-label': label,
180
+ 'aria-labelledby': label ? `${id}Label` : undefined,
181
+ autoComplete: getAutoCompleteValue(autoComplete),
182
+ autoFocus,
183
+ disabled: isDisabled,
184
+ id,
185
+ name,
186
+ onBlur,
187
+ onChange,
188
+ onFocus,
189
+ required: isRequired,
190
+ value: value ?? '',
191
+ className: classNames(inputProps.className),
192
+ };
193
+
194
+ return (
195
+ <div ref={ref}>
196
+ <Box
197
+ direction="row"
198
+ flex="auto"
199
+ position="relative"
200
+ className={inputWrapperClasses}
201
+ >
202
+ <Box as="select" {...computedInputProps}>
203
+ {optionsWithPlaceholder.map((option) => (
204
+ <Box
205
+ as="option"
206
+ key={option.value}
207
+ value={option.value}
208
+ disabled={option.value === ''}
209
+ hidden={option.value === ''}
210
+ color={option.value === '' ? 'secondary' : 'base'}
211
+ >
212
+ {option.label}
213
+ </Box>
214
+ ))}
215
+ </Box>
216
+ {!!onClear && !!value && renderClearIcon()}
217
+ <label
218
+ htmlFor={id}
219
+ className={styles['select-input-label']}
220
+ id={`${id}Label`}
221
+ >
222
+ {label}
223
+ {isRequired && requiredIndicator && (
224
+ <span>{requiredIndicator}</span>
225
+ )}
226
+ </label>
227
+ </Box>
228
+ {helpText && <HelpText>{helpText}</HelpText>}
229
+ {error && error !== true && (
230
+ <InputValidationMessage>{error}</InputValidationMessage>
231
+ )}
232
+ </div>
233
+ );
234
+ }
235
+ );
@@ -0,0 +1,87 @@
1
+ import { Canvas, Meta, ArgTypes } from '@storybook/blocks';
2
+ import { SelectInputNative } from './SelectInputNative';
3
+ import * as Stories from './SelectInputNative.stories';
4
+
5
+ <Meta of={Stories} />
6
+
7
+ # SelectInputNative
8
+
9
+ Allows users to pick a value from a predefined list of options. Ideally, it should be used when there are 5 or more options, otherwise you should consider using a radio group instead.
10
+
11
+ _NOTE:_ This is an alternative to the `SelectInput` component, but leveraging the native platform dropdown instead of a custom one. This is useful,
12
+ when a custom UI is not needed for a select, or the native UI is preferred (this is recommended for most cases except multi-selects).
13
+
14
+ ## Props
15
+
16
+ <ArgTypes of={SelectInputNative} />
17
+
18
+ ## Default
19
+
20
+ All that is required to render a basic version of the SelectInputNative is a unique `id`, a `label`, a list of options passed to the `options` prop, a `value`, and an onchange event handler passed to the `onChange` prop.
21
+
22
+ <Canvas of={Stories.Default} />
23
+
24
+ ## Pre-Selected
25
+
26
+ Pre-select an option by passing its value to the `value` prop.
27
+
28
+ <Canvas of={Stories.PreSelected} />
29
+
30
+ ## Help Text
31
+
32
+ Use the `helpText` prop to add clarifying text beneath the input label.
33
+
34
+ <Canvas of={Stories.HelpText} />
35
+
36
+ ## Placeholder
37
+
38
+ Use the `placeholder` prop to add a custom placeholder. Note that this component automatically renders the first option
39
+ to use as a placeholder with a value of `''`. This first option is however disabled can't be selected after a user has picked
40
+ one of the real options.
41
+
42
+ <Canvas of={Stories.Placeholder} />
43
+
44
+ ## Hidden Label
45
+
46
+ Use the `hideLabel` prop to not render the label.
47
+
48
+ <Canvas of={Stories.HiddenLabel} />
49
+
50
+ ## Autofocus
51
+
52
+ Use the `autoFocus` prop to autofocus a SelectInput.
53
+
54
+ <Canvas of={Stories.Autofocus} />
55
+
56
+ ## Required
57
+
58
+ Use the `isRequired` prop to set the `required` and `aria-required` on the underlying input element.
59
+
60
+ <Canvas of={Stories.Required} />
61
+
62
+ You can remove or customize the required indicator using the `requiredIndicator` prop.
63
+
64
+ <Canvas of={Stories.CustomRequiredIndicator} />
65
+
66
+ ## Disabled
67
+
68
+ Use the `isDisabled` prop to mark the input as disabled.
69
+
70
+ <Canvas of={Stories.Disabled} />
71
+
72
+ ## Error
73
+
74
+ Use the `error` prop to mark the input as invalid. `error` accepts a `boolean`, `string`, or `node`. If either a `string` or `node` is passed, a validation message is displayed below it.
75
+
76
+ <Canvas of={Stories.Error} />
77
+
78
+ ## Sizes
79
+
80
+ Set the size of the input to `sm`, `md` or `lg`. `md` is the default size.
81
+ The `size` prop is also a `ResponsiveProp`, so it can be sized differently at each [breakpoint](?path=/docs/design-tokens-design-tokens--page#breakpoints) by passing an object.
82
+
83
+ <Canvas of={Stories.Sizes} />
84
+
85
+ ## Component Design Tokens
86
+
87
+ This component shares component design tokens with all form controls. For a complete list of tokens, see the [Theming Form Controls documentation](/docs/theming-form-controls--custom-theme-form).