@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,194 @@
1
+ import React, { useRef } from 'react';
2
+ import { Modal } from './Modal';
3
+ import type { Meta } from '@storybook/react';
4
+ import { Button } from '../Button/Button';
5
+ import { useOpenClose } from '../../hooks/useOpenClose/useOpenClose';
6
+
7
+ const meta: Meta<typeof Modal> = {
8
+ title: 'Components/Modal',
9
+ component: Modal,
10
+ };
11
+
12
+ export default meta;
13
+
14
+ export const BasicUsage = () => {
15
+ const {
16
+ isOpen: isModalOpen,
17
+ handleOpen: openModal,
18
+ handleClose: closeModal,
19
+ } = useOpenClose();
20
+ return (
21
+ <div>
22
+ <Button variant="primary" onClick={openModal}>
23
+ Show Modal
24
+ </Button>
25
+ <Modal
26
+ ariaLabelledBy="titleBasic"
27
+ isOpen={isModalOpen}
28
+ onDismiss={closeModal}
29
+ >
30
+ <Modal.Header
31
+ id="titleBasic"
32
+ title="The Modal Title"
33
+ onDismiss={closeModal}
34
+ />
35
+ <Modal.Body>Modal content</Modal.Body>
36
+ <Modal.Footer>
37
+ <Button variant="secondary" onClick={closeModal}>
38
+ Cancel
39
+ </Button>
40
+ <Button variant="primary">Primary Action</Button>
41
+ </Modal.Footer>
42
+ </Modal>
43
+ </div>
44
+ );
45
+ };
46
+
47
+ export const BodyAndFooter = () => {
48
+ const {
49
+ isOpen: isModalOpen,
50
+ handleOpen: openModal,
51
+ handleClose: closeModal,
52
+ } = useOpenClose();
53
+ return (
54
+ <div>
55
+ <Button variant="primary" onClick={openModal}>
56
+ Show Modal With Body and Footer
57
+ </Button>
58
+ <Modal
59
+ ariaLabelledBy="titleFooterBody"
60
+ isOpen={isModalOpen}
61
+ onDismiss={closeModal}
62
+ >
63
+ <Modal.Header
64
+ id="titleFooterBody"
65
+ title="The Modal Title"
66
+ onDismiss={closeModal}
67
+ />
68
+ <Modal.Body>Modal body content</Modal.Body>
69
+ <Modal.Footer>This is content in the modal footer</Modal.Footer>
70
+ </Modal>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export const CloseButton = () => {
76
+ const {
77
+ isOpen: isModalOpen,
78
+ handleOpen: openModal,
79
+ handleClose: closeModal,
80
+ } = useOpenClose();
81
+ return (
82
+ <div>
83
+ <Button variant="primary" onClick={openModal}>
84
+ Show Modal With Close Button
85
+ </Button>
86
+ <Modal
87
+ ariaLabel="modal with close button"
88
+ isOpen={isModalOpen}
89
+ onDismiss={closeModal}
90
+ >
91
+ <Modal.Header id="header" onDismiss={closeModal} />
92
+ <Modal.Body>Modal content</Modal.Body>
93
+ </Modal>
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export const WithoutHeader = () => {
99
+ const {
100
+ isOpen: isModalOpen,
101
+ handleOpen: openModal,
102
+ handleClose: closeModal,
103
+ } = useOpenClose();
104
+ return (
105
+ <div>
106
+ <Button variant="primary" onClick={openModal}>
107
+ Show Modal Without Header
108
+ </Button>
109
+ <Modal
110
+ ariaLabel="Modal without a header"
111
+ isOpen={isModalOpen}
112
+ onDismiss={closeModal}
113
+ >
114
+ <Modal.Body>Modal content</Modal.Body>
115
+ <Modal.Footer>
116
+ <Button variant="secondary" onClick={closeModal}>
117
+ Cancel
118
+ </Button>
119
+ <Button variant="primary">Primary Action</Button>
120
+ </Modal.Footer>
121
+ </Modal>
122
+ </div>
123
+ );
124
+ };
125
+
126
+ export const FullscreenMobile = () => {
127
+ const {
128
+ isOpen: isModalOpen,
129
+ handleOpen: openModal,
130
+ handleClose: closeModal,
131
+ } = useOpenClose();
132
+ return (
133
+ <div>
134
+ <Button variant="primary" onClick={openModal}>
135
+ Show Fullscreen On Mobile Modal
136
+ </Button>
137
+ <Modal
138
+ ariaLabelledBy="titleFullscreen"
139
+ fullScreenMobile
140
+ isOpen={isModalOpen}
141
+ onDismiss={closeModal}
142
+ >
143
+ <Modal.Header
144
+ id="titleFullscreen"
145
+ title="Fullscreen Modal on Mobile"
146
+ onDismiss={closeModal}
147
+ />
148
+ <Modal.Body>Modal content</Modal.Body>
149
+ <Modal.Footer>
150
+ <Button variant="secondary" onClick={closeModal}>
151
+ Cancel
152
+ </Button>
153
+ <Button variant="primary">Primary Action</Button>
154
+ </Modal.Footer>
155
+ </Modal>
156
+ </div>
157
+ );
158
+ };
159
+
160
+ export const MaxWidth = () => {
161
+ const {
162
+ isOpen: isModalOpen,
163
+ handleOpen: openModal,
164
+ handleClose: closeModal,
165
+ } = useOpenClose();
166
+ const ref = useRef(null);
167
+
168
+ return (
169
+ <div>
170
+ <Button variant="primary" onClick={openModal}>
171
+ Open Max Width Modal
172
+ </Button>
173
+ <Modal
174
+ ariaLabelledBy="titleFooterBody"
175
+ isOpen={isModalOpen}
176
+ onDismiss={closeModal}
177
+ maxWidth={{ tablet: '8xl', desktop: '9xl' }}
178
+ initialFocusRef={ref}
179
+ >
180
+ <Modal.Header
181
+ id="titleFooterBody"
182
+ title="The Modal Title"
183
+ onDismiss={closeModal}
184
+ />
185
+ <Modal.Body>Modal body content</Modal.Body>
186
+ <Modal.Footer>
187
+ <Button variant="secondary" ref={ref} onClick={closeModal}>
188
+ Cancel
189
+ </Button>
190
+ </Modal.Footer>
191
+ </Modal>
192
+ </div>
193
+ );
194
+ };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import { Modal } from './Modal';
4
+
5
+ describe('Modal', () => {
6
+ test('renders its children', () => {
7
+ const { getByText } = render(
8
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
9
+ <Modal isOpen onDismiss={() => {}} ariaLabel="testDefault">
10
+ test modal
11
+ </Modal>
12
+ );
13
+ expect(getByText('test modal')).toBeInTheDocument();
14
+ });
15
+
16
+ test('it open and closes based on isOpen prop', () => {
17
+ const { queryByText, getByText, rerender } = render(
18
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
19
+ <Modal isOpen={false} onDismiss={() => {}} ariaLabel="testIsOpen">
20
+ test modal
21
+ </Modal>
22
+ );
23
+
24
+ expect(queryByText('test modal')).toBe(null);
25
+
26
+ rerender(
27
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
28
+ <Modal isOpen onDismiss={() => {}} ariaLabel="testIsOpen">
29
+ test modal
30
+ </Modal>
31
+ );
32
+
33
+ expect(getByText('test modal')).toBeInTheDocument();
34
+ });
35
+
36
+ test('Subcomponents', () => {
37
+ const { getByText } = render(
38
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
39
+ <Modal isOpen onDismiss={() => {}} ariaLabel="testSubcomponents">
40
+ {/* eslint-disable-next-line @typescript-eslint/no-empty-function */}
41
+ <Modal.Header
42
+ id="titleFooterBody"
43
+ title="The Modal Title"
44
+ onDismiss={() => {}}
45
+ />
46
+ <Modal.Body>Modal body content</Modal.Body>
47
+ <Modal.Footer>This is content in the modal footer</Modal.Footer>
48
+ </Modal>
49
+ );
50
+
51
+ expect(getByText('The Modal Title')).toBeInTheDocument();
52
+ expect(getByText('Modal body content')).toBeInTheDocument();
53
+ expect(
54
+ getByText('This is content in the modal footer')
55
+ ).toBeInTheDocument();
56
+ });
57
+
58
+ test('onDismiss', async () => {
59
+ const mockOnDismiss = jest.fn();
60
+ const { getByTestId } = render(
61
+ <Modal isOpen onDismiss={mockOnDismiss} ariaLabel="testSubcomponents">
62
+ <Modal.Header
63
+ id="titleFooterBody"
64
+ title="The Modal Title"
65
+ onDismiss={mockOnDismiss}
66
+ />
67
+ <Modal.Body>Modal body content</Modal.Body>
68
+ <Modal.Footer>This is content in the modal footer</Modal.Footer>
69
+ </Modal>
70
+ );
71
+
72
+ const closeButton = getByTestId('icon-testid--remove').closest('button');
73
+ expect(closeButton).toBeInTheDocument();
74
+
75
+ if (closeButton) {
76
+ await fireEvent.click(closeButton);
77
+ }
78
+
79
+ expect(mockOnDismiss).toHaveBeenCalledTimes(1);
80
+ });
81
+ });
@@ -0,0 +1,174 @@
1
+ import React, { ReactNode, RefObject, forwardRef, useCallback } from 'react';
2
+ import ReactModal from 'react-modal';
3
+ import FocusLock from 'react-focus-lock';
4
+ import { RemoveScroll } from 'react-remove-scroll';
5
+ import classNames from 'classnames';
6
+ import { CssOverflowValue } from '../../types';
7
+ import { getDimensionCss } from '../../lib/getDimensionCss';
8
+ import { Box, BoxProps } from '../Box/Box';
9
+ import { ModalFooter, ModalHeader, ModalBody } from './components';
10
+ import styles from './Modal.module.scss';
11
+
12
+ export interface ModalProps {
13
+ /**
14
+ * Handle zoom/pinch gestures on iOS devices when scroll locking is enabled.
15
+ */
16
+ allowPinchZoom?: boolean;
17
+ /**
18
+ * Each modal needs to be properly labeled to provide context for users with
19
+ * assistive technology such as screen readers. If a modal is announced to
20
+ * the user without a label, it can be confusing and difficult to navigate.
21
+ */
22
+ ariaLabel?: string;
23
+ /**
24
+ * The id of the element that should be used as the Modal's label by assistive
25
+ * technologies like screen readers. Usually the id is set on the `Modal.Header`
26
+ */
27
+ ariaLabelledBy?: string;
28
+ /**
29
+ * Contents of the dialog.
30
+ */
31
+ children?: ReactNode;
32
+ /**
33
+ * Additional ClassNames to add to dialog.
34
+ */
35
+ className?: string;
36
+ /**
37
+ * The ref of the container where the dialog will be inserted into the DOM.
38
+ * By default, Modals are inserted in the document.body, but if need be they can
39
+ * be scoped as necessary.
40
+ */
41
+ containerRef?: React.RefObject<Node>;
42
+ /**
43
+ * At mobile viewport widths, the modal will take up the fullscreen
44
+ */
45
+ fullScreenMobile?: boolean;
46
+ /**
47
+ * By default the first focusable element will receive focus when the dialog
48
+ * opens but you can provide a ref to focus instead.
49
+ *
50
+ * @see Docs https://reach.tech/dialog#dialog-initialfocusref
51
+ */
52
+ initialFocusRef?: RefObject<HTMLDivElement>;
53
+ /**
54
+ * Whether the modal is visible or not
55
+ */
56
+ isOpen: boolean;
57
+ /**
58
+ * Max width for modal content. Uses the same maxWidth prop as the `Box` component,
59
+ * and as such can be responsive as well.
60
+ */
61
+ maxWidth?: BoxProps['maxWidth'];
62
+ /**
63
+ * Function that is called whenever the user hits "Escape" key or clicks outside the modal.
64
+ */
65
+ onDismiss: (event?: React.SyntheticEvent) => void;
66
+ /**
67
+ * The css overflow behavior of the Modal
68
+ */
69
+ overflow?: CssOverflowValue;
70
+ /**
71
+ * Allows spread props
72
+ */
73
+ [x: string]: any; // eslint-disable-line
74
+ }
75
+
76
+ export const ModalBaseComponent: React.FC<ModalProps> = forwardRef<
77
+ HTMLDivElement,
78
+ ModalProps
79
+ >(
80
+ (
81
+ {
82
+ ariaLabel,
83
+ ariaLabelledBy,
84
+ allowPinchZoom = false,
85
+ children,
86
+ className,
87
+ containerRef = undefined,
88
+ fullScreenMobile = false,
89
+ initialFocusRef,
90
+ isOpen,
91
+ maxWidth = undefined,
92
+ onDismiss,
93
+ overflow = 'hidden',
94
+ ...restProps
95
+ },
96
+ ref
97
+ ) => {
98
+ const activateFocusLock = useCallback(() => {
99
+ setTimeout(() => {
100
+ if (initialFocusRef && initialFocusRef.current) {
101
+ initialFocusRef.current.focus();
102
+ }
103
+ }, 100);
104
+ }, [initialFocusRef]);
105
+
106
+ const maxWidthCss = getDimensionCss('mw', maxWidth);
107
+
108
+ const overlayClassnames = classNames(styles.overlay, styles.modal, {
109
+ fullscreen: fullScreenMobile,
110
+ });
111
+ const contentClassnames = classNames(
112
+ styles['modal-content'],
113
+ className,
114
+ maxWidthCss.classes,
115
+ {
116
+ [`overflow-${overflow}`]: overflow,
117
+ }
118
+ );
119
+
120
+ const parentElement = containerRef?.current
121
+ ? (containerRef.current as HTMLElement)
122
+ : undefined;
123
+ return (
124
+ <FocusLock
125
+ autoFocus
126
+ returnFocus
127
+ disabled={!isOpen}
128
+ onActivation={activateFocusLock}
129
+ crossFrame
130
+ >
131
+ <RemoveScroll allowPinchZoom={allowPinchZoom} enabled={isOpen}>
132
+ <Box ref={ref}>
133
+ <ReactModal
134
+ isOpen={isOpen}
135
+ overlayClassName={overlayClassnames}
136
+ className={contentClassnames}
137
+ onRequestClose={onDismiss}
138
+ ariaHideApp={false}
139
+ parentSelector={parentElement ? () => parentElement : undefined}
140
+ {...restProps}
141
+ >
142
+ <Box
143
+ aria-label={ariaLabel}
144
+ aria-labelledby={ariaLabelledBy}
145
+ style={{ ...maxWidthCss.styles }}
146
+ height="100"
147
+ >
148
+ {children}
149
+ </Box>
150
+ </ReactModal>
151
+ </Box>
152
+ </RemoveScroll>
153
+ </FocusLock>
154
+ );
155
+ }
156
+ );
157
+
158
+ export interface ModalStatic {
159
+ Body: typeof ModalBody;
160
+ Header: typeof ModalHeader;
161
+ Footer: typeof ModalFooter;
162
+ }
163
+
164
+ export type ModalWithStaticComponents = typeof ModalBaseComponent & ModalStatic;
165
+
166
+ // Actual component is wrapped in an IIFE for the export
167
+ // To allow tree-shaking even with static properties (subcomponents in this case).
168
+ export const Modal = (() => {
169
+ const Modal = ModalBaseComponent as ModalWithStaticComponents; // eslint-disable-line no-shadow
170
+ Modal.Body = ModalBody;
171
+ Modal.Footer = ModalFooter;
172
+ Modal.Header = ModalHeader;
173
+ return Modal;
174
+ })();
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { ModalBody } from './ModalBody';
4
+
5
+ describe('ModalBody', () => {
6
+ test('renders its children', () => {
7
+ const { getByText } = render(<ModalBody>test modal</ModalBody>);
8
+ expect(getByText('test modal')).toBeInTheDocument();
9
+ });
10
+
11
+ test('xl padding class is applied by default', () => {
12
+ const { container } = render(<ModalBody>test modal</ModalBody>);
13
+ expect(container.children[0].classList).toContain('p-xl');
14
+ });
15
+
16
+ test('flex-auto class is applied by default', () => {
17
+ const { container } = render(<ModalBody>test modal</ModalBody>);
18
+ expect(container.children[0].classList).toContain('flex-auto');
19
+ });
20
+ });
@@ -0,0 +1,24 @@
1
+ import React, { FC } from 'react';
2
+ import { Box, BoxProps } from '../../../Box/Box';
3
+
4
+ export type ModalBodyProps = BoxProps;
5
+
6
+ export const ModalBody: FC<ModalBodyProps> = ({
7
+ children,
8
+ flex = 'auto',
9
+ padding = 'xl',
10
+ overflow = 'auto',
11
+ height = '100',
12
+ ...restProps
13
+ }) => (
14
+ <Box
15
+ padding={padding}
16
+ flex={flex}
17
+ overflow={overflow}
18
+ height={height}
19
+ style={{ position: 'relative' }}
20
+ {...restProps}
21
+ >
22
+ {children}
23
+ </Box>
24
+ );
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { ModalFooter } from './ModalFooter';
4
+
5
+ describe('ModalFooter', () => {
6
+ test('renders its children', () => {
7
+ const { getByText } = render(<ModalFooter>test modal</ModalFooter>);
8
+ expect(getByText('test modal')).toBeInTheDocument();
9
+ });
10
+
11
+ test('xl padding class is applied by default', () => {
12
+ const { container } = render(<ModalFooter>test modal</ModalFooter>);
13
+ expect(container.children[0].classList).toContain('p-xl');
14
+ });
15
+
16
+ test('row direction class is applied by default', () => {
17
+ const { container } = render(<ModalFooter>test modal</ModalFooter>);
18
+ expect(container.children[0].classList).toContain('flex-direction-row');
19
+ });
20
+
21
+ test('align-items center class is applied by default', () => {
22
+ const { container } = render(<ModalFooter>test modal</ModalFooter>);
23
+ expect(container.children[0].classList).toContain('align-items-center');
24
+ });
25
+
26
+ test('justify content flex-end class is applied by default', () => {
27
+ const { container } = render(<ModalFooter>test modal</ModalFooter>);
28
+ expect(container.children[0].classList).toContain(
29
+ 'justify-content-flex-end'
30
+ );
31
+ });
32
+ });
@@ -0,0 +1,37 @@
1
+ import React, { FC } from 'react';
2
+ import { Box, BoxProps } from '../../../Box/Box';
3
+
4
+ export type ModalFooterProps = Omit<
5
+ BoxProps,
6
+ 'as' | 'background' | 'borderColor' | 'borderWidth' | 'radius'
7
+ >;
8
+
9
+ export const ModalFooter: FC<ModalFooterProps> = ({
10
+ children,
11
+ padding = 'xl',
12
+ direction = 'row',
13
+ alignItems = 'center',
14
+ justifyContent = 'flex-end',
15
+ background = 'secondary',
16
+ gap = 'sm',
17
+ style,
18
+ ...restProps
19
+ }) => (
20
+ <Box
21
+ background={background}
22
+ padding={padding}
23
+ direction={direction}
24
+ alignItems={alignItems}
25
+ justifyContent={justifyContent}
26
+ borderWidth="sm 0 0 0"
27
+ borderColor="default"
28
+ gap={gap}
29
+ style={{
30
+ flexShrink: 0,
31
+ ...style,
32
+ }}
33
+ {...restProps}
34
+ >
35
+ {children}
36
+ </Box>
37
+ );
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { fireEvent, screen, render } from '@testing-library/react';
3
+ import { ModalHeader } from './ModalHeader';
4
+
5
+ describe('ModalHeader', () => {
6
+ test('renders a title if provided', () => {
7
+ const { getByText } = render(
8
+ <ModalHeader id="modal" title="modal title" />
9
+ );
10
+ expect(getByText('modal title')).toBeInTheDocument();
11
+ });
12
+
13
+ test('renders close button if onDismiss is set', () => {
14
+ render(<ModalHeader id="modal" onDismiss={() => null} />);
15
+ expect(screen.getByLabelText('close')).toBeDefined();
16
+ });
17
+
18
+ test('clicking the close button fires onDismiss', () => {
19
+ const mockOnDismiss = jest.fn();
20
+ render(<ModalHeader id="modal" onDismiss={mockOnDismiss} />);
21
+ fireEvent.click(screen.getByLabelText('close'));
22
+ expect(mockOnDismiss).toBeCalledTimes(1);
23
+ });
24
+
25
+ test('xl padding class is applied by default', () => {
26
+ const { container } = render(<ModalHeader id="modal" />);
27
+ expect(container.children[0].classList).toContain('p-xl');
28
+ });
29
+ });
@@ -0,0 +1,58 @@
1
+ import React, { FC } from 'react';
2
+ import { Box } from '../../../Box/Box';
3
+ import { Icon } from '../../../Icon/Icon';
4
+ import styles from '../../Modal.module.scss';
5
+
6
+ export type ModalHeaderProps = {
7
+ /**
8
+ * id of the element containing the title, used by the Modal `aria-labelledby` prop
9
+ */
10
+ id: string;
11
+ /**
12
+ * Modal's header title
13
+ */
14
+ title?: string;
15
+ /**
16
+ * If defined, will render a 'x' close button on the right side of the ModalHeader
17
+ */
18
+ onDismiss?: (event?: React.SyntheticEvent) => void;
19
+ };
20
+
21
+ export const ModalHeader: FC<ModalHeaderProps> = ({
22
+ id,
23
+ onDismiss,
24
+ title = undefined,
25
+ }) => {
26
+ const justifyContentValue =
27
+ title === undefined && onDismiss ? 'flex-end' : 'space-between';
28
+
29
+ return (
30
+ <Box
31
+ padding="xl"
32
+ direction="row"
33
+ alignItems="center"
34
+ justifyContent={justifyContentValue}
35
+ borderWidth="0 0 sm 0"
36
+ borderColor="default"
37
+ style={{
38
+ flexShrink: 0,
39
+ }}
40
+ >
41
+ {title && (
42
+ <Box as="h4" fontSize={{ base: 'md', tablet: 'lg' }} id={id}>
43
+ {title}
44
+ </Box>
45
+ )}
46
+ {onDismiss && (
47
+ <button
48
+ aria-label="close"
49
+ type="button"
50
+ className={styles['modal-close']}
51
+ onClick={onDismiss}
52
+ >
53
+ <Icon name="remove" />
54
+ </button>
55
+ )}
56
+ </Box>
57
+ );
58
+ };
@@ -0,0 +1,5 @@
1
+ import { ModalFooter } from './ModalFooter/ModalFooter';
2
+ import { ModalHeader } from './ModalHeader/ModalHeader';
3
+ import { ModalBody } from './ModalBody/ModalBody';
4
+
5
+ export { ModalFooter, ModalHeader, ModalBody };
@@ -0,0 +1,26 @@
1
+ import { useState } from 'react';
2
+ import { Meta, Story, Canvas, ArgTypes } from '@storybook/addon-docs';
3
+ import { Pagination } from './Pagination';
4
+ import * as Stories from './Pagination.stories';
5
+
6
+ <Meta of={Stories} />
7
+
8
+ # Pagination
9
+
10
+ Use pagination to improve user scanning, as well as load times, for large collections or lists.
11
+
12
+ ## Props
13
+
14
+ <ArgTypes of={Pagination} />
15
+
16
+ ## Default
17
+
18
+ <Canvas of={Stories.Default} />
19
+
20
+ ## Compact
21
+
22
+ <Canvas of={Stories.Compact} />
23
+
24
+ ## With Interactive Page Numbers
25
+
26
+ <Canvas of={Stories.PageNumbers} />