@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,128 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
3
+ import { Placement } from '@popperjs/core';
4
+ import { Popover } from './Popover';
5
+
6
+ describe('Popover', () => {
7
+ describe('Default', () => {
8
+ it('Renders a popover with default props', async () => {
9
+ // NOTE: popperJS is throwing a warning due to missing act, but it is unclear how to fix these.
10
+ // https://github.com/popperjs/react-popper/issues/368
11
+ render(
12
+ <Popover isOpen content={<>hello</>}>
13
+ <p>trigger</p>
14
+ </Popover>
15
+ );
16
+
17
+ const popoverContent = screen.getByText('hello');
18
+ const popoverContainer = screen.getByRole('dialog');
19
+ const trigger = screen.getByText('trigger');
20
+ expect(popoverContent).toBeInTheDocument();
21
+ expect(trigger).toBeInTheDocument();
22
+ expect(trigger).toHaveAttribute('role', 'button');
23
+ expect(popoverContainer).toBeInTheDocument();
24
+ expect(popoverContainer).toHaveAttribute('role', 'dialog');
25
+ expect(popoverContainer).toHaveAttribute('aria-hidden', 'false');
26
+ expect(popoverContainer).toHaveClass('background-color-primary');
27
+ expect(popoverContainer).toHaveClass('p-sm');
28
+ await waitFor(() =>
29
+ expect(popoverContainer).toHaveAttribute(
30
+ 'data-popper-placement',
31
+ 'right'
32
+ )
33
+ );
34
+ });
35
+ });
36
+
37
+ describe('Callbacks', () => {
38
+ it('Fires a callback when a user clicks outside the popover', () => {
39
+ const mockedOnClickOutside = jest.fn();
40
+ const { container } = render(
41
+ <Popover
42
+ isOpen
43
+ content={<>hello</>}
44
+ onClickOutside={mockedOnClickOutside}
45
+ >
46
+ <p>trigger</p>
47
+ </Popover>
48
+ );
49
+
50
+ const popover = screen.getByText('hello');
51
+ const trigger = screen.getByText('trigger');
52
+ expect(popover).toBeInTheDocument();
53
+ fireEvent.click(popover);
54
+ fireEvent.click(trigger);
55
+ fireEvent.click(container);
56
+ fireEvent.keyUp(container, { key: 'Escape' });
57
+ expect(mockedOnClickOutside).toBeCalledTimes(2);
58
+ });
59
+ });
60
+
61
+ describe('Placement', () => {
62
+ // We do not test auto placements since those compute out to one of the below after detection.
63
+ const positions: Placement[] = [
64
+ 'top',
65
+ 'bottom',
66
+ 'right',
67
+ 'left',
68
+ 'top-start',
69
+ 'top-end',
70
+ 'bottom-start',
71
+ 'bottom-end',
72
+ 'right-start',
73
+ 'right-end',
74
+ 'left-start',
75
+ 'left-end',
76
+ ];
77
+
78
+ positions.forEach((position) => {
79
+ it(`Places the tooltop correctly in position: ${position} when prop is passed`, async () => {
80
+ render(
81
+ <Popover isOpen content={<>hello</>} placement={position}>
82
+ <p>trigger</p>
83
+ </Popover>
84
+ );
85
+
86
+ const popoverContainer = screen.getByRole('dialog');
87
+ await waitFor(() =>
88
+ expect(popoverContainer).toHaveAttribute(
89
+ 'data-popper-placement',
90
+ position
91
+ )
92
+ );
93
+ });
94
+ });
95
+ });
96
+
97
+ describe('Portal', () => {
98
+ it('Renders the Popover in the body if withPortal is true.', async () => {
99
+ render(
100
+ <>
101
+ <div id="nest1">
102
+ <div id="nest2">
103
+ <Popover
104
+ isOpen
105
+ content={
106
+ <button type="button" id="inside-button">
107
+ hello
108
+ </button>
109
+ }
110
+ withPortal
111
+ portalTarget={document.body}
112
+ >
113
+ <p>trigger</p>
114
+ </Popover>
115
+ </div>
116
+ </div>
117
+ </>
118
+ );
119
+
120
+ await waitFor(() => {
121
+ expect(document.body.children[1]).toHaveAttribute(
122
+ 'data-popper-placement',
123
+ 'right'
124
+ );
125
+ });
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,277 @@
1
+ import React, {
2
+ cloneElement,
3
+ FC,
4
+ isValidElement,
5
+ ReactNode,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ RefObject,
10
+ } from 'react';
11
+ import { createPortal } from 'react-dom';
12
+ import { usePopper } from 'react-popper';
13
+ import { Placement } from '@popperjs/core';
14
+ import FocusTrap from 'focus-trap-react';
15
+ import classNames from 'classnames';
16
+ import { BackgroundColor } from '../../types';
17
+ import styles from './Popover.module.scss';
18
+ import { Box, BoxProps } from '../Box/Box';
19
+ import { mergeRefs } from '../../lib';
20
+
21
+ export type PopoverProps = {
22
+ /**
23
+ * Custom class to apply to the alert.
24
+ */
25
+ className?: string;
26
+ /**
27
+ * The trigger element
28
+ */
29
+ children: ReactNode;
30
+ /**
31
+ * Content of the tooltip. Can be any JSX node.
32
+ */
33
+ content: ReactNode;
34
+ /**
35
+ * The Popover is a controlled input, and will be shown when `isOpen === true`.
36
+ */
37
+ isOpen: boolean;
38
+ /**
39
+ * Color of the arrow background. NOTE: That the arrowColor will default to the
40
+ * `background` color applied in the `contentContainerProps`, but can be overwritten
41
+ * by passing a specific value here.
42
+ */
43
+ arrowColor?: BackgroundColor;
44
+ /**
45
+ * An object matching the interface of the `Box` component props.
46
+ * This is useful for styling the tooltip container using all the options available in
47
+ * a `Box`.
48
+ */
49
+ contentContainerProps?: BoxProps;
50
+ /**
51
+ * Whether the arrow is shown.
52
+ */
53
+ hasArrow?: boolean;
54
+ /**
55
+ * How far (in pixels) the Popover element will be from the target.
56
+ * Note that this is from the edge of the target to the edge of the popover content,
57
+ * and it DOES NOT include the arrow element.
58
+ */
59
+ offsetFromTarget?: number;
60
+ /**
61
+ * Callback function to handle when a user clicks outside the Popover
62
+ */
63
+ onClickOutside?: (event: MouseEvent | KeyboardEvent) => void;
64
+ /**
65
+ * The placement (position) of the Popover relative to its trigger.
66
+ */
67
+ placement?: Placement;
68
+ /**
69
+ * Whether you want to trap focus in the Popover element when it is open.
70
+ * Read more about focus traps:
71
+ * [Here](https://allyjs.io/tutorials/accessible-dialog.html#trapping-focus-inside-the-dialog)
72
+ */
73
+ trapFocus?: boolean;
74
+ /**
75
+ * Additional props to be spread to rendered element
76
+ */
77
+ [x: string]: any; // eslint-disable-line
78
+ } & (
79
+ | {
80
+ /**
81
+ * Whether the element should be rendered outside its DOM structure
82
+ * for reasons of placement. Use this when the element is being cut-off or
83
+ * re-positioned due to lack of space in the parent container.
84
+ * NOTE: `portalTarget` is required if this is true.
85
+ */
86
+ withPortal: true;
87
+ /**
88
+ * The target element where the Popover will be portaled to, when `withPortal === true`.
89
+ * `document.body` will work for many cases, but you can also use a custom container for this.
90
+ * Only required if withPortal is true.
91
+ */
92
+ portalTarget: HTMLElement;
93
+ }
94
+ | {
95
+ withPortal?: false;
96
+ portalTarget?: never;
97
+ }
98
+ );
99
+
100
+ const contentContainerDefaults: BoxProps = {
101
+ background: 'primary',
102
+ padding: 'sm',
103
+ radius: 'sm',
104
+ shadow: 'md',
105
+ };
106
+
107
+ export const Popover: FC<PopoverProps> = ({
108
+ className,
109
+ isOpen,
110
+ children,
111
+ content,
112
+ arrowColor = undefined,
113
+ contentContainerProps = { ...contentContainerDefaults },
114
+ hasArrow = true,
115
+ offsetFromTarget = 12,
116
+ onClickOutside = undefined,
117
+ placement = 'right',
118
+ withPortal = false,
119
+ portalTarget,
120
+ trapFocus = false,
121
+ ...restProps
122
+ }) => {
123
+ const triggerRef = useRef<HTMLElement>(null);
124
+ const popperRef = useRef<HTMLElement>(null);
125
+ const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
126
+
127
+ useEffect(() => {
128
+ const handleClickOutside = (event: MouseEvent) => {
129
+ const popover = popperRef.current;
130
+ const trigger = triggerRef.current;
131
+
132
+ if (!popover || !trigger) {
133
+ return;
134
+ }
135
+
136
+ if (event.target === trigger || trigger?.contains(event.target as Node)) {
137
+ return;
138
+ }
139
+
140
+ if (
141
+ event.target !== popover &&
142
+ !popover?.contains(event.target as Node)
143
+ ) {
144
+ if (onClickOutside) onClickOutside(event);
145
+ }
146
+ };
147
+
148
+ const handleKeyUp = (event: KeyboardEvent) => {
149
+ if (event.key === 'Escape') {
150
+ if (onClickOutside) onClickOutside(event);
151
+ }
152
+ };
153
+
154
+ if (onClickOutside) {
155
+ document.body.addEventListener('click', handleClickOutside, false);
156
+ document.body.addEventListener('keyup', handleKeyUp);
157
+ }
158
+
159
+ return () => {
160
+ if (onClickOutside) {
161
+ document.body.removeEventListener('click', handleClickOutside, false);
162
+ document.body.removeEventListener('keyup', handleKeyUp);
163
+ }
164
+ };
165
+ }, [onClickOutside]);
166
+
167
+ const { styles: popperStyles, attributes } = usePopper(
168
+ triggerRef.current,
169
+ popperRef.current,
170
+ {
171
+ placement,
172
+ modifiers: [
173
+ {
174
+ name: 'arrow',
175
+ options: { element: arrowElement },
176
+ },
177
+ {
178
+ name: 'offset',
179
+ options: {
180
+ offset: [0, offsetFromTarget],
181
+ },
182
+ },
183
+ ],
184
+ }
185
+ );
186
+
187
+ const containerBoxProps = {
188
+ ...contentContainerDefaults,
189
+ ...contentContainerProps,
190
+ };
191
+
192
+ const computedArrowColor = arrowColor || containerBoxProps.background;
193
+
194
+ const arrowClasses = classNames(
195
+ styles['popover-arrow'],
196
+ `background-color-${computedArrowColor}`,
197
+ {
198
+ 'display-none': !hasArrow,
199
+ }
200
+ );
201
+
202
+ const renderPopperContent = () => {
203
+ const renderPopperBox = () => (
204
+ <Box
205
+ ref={popperRef}
206
+ className={classNames(styles.popover, className)}
207
+ style={popperStyles.popper}
208
+ role="dialog"
209
+ aria-label="Popover"
210
+ aria-hidden={!isOpen}
211
+ {...containerBoxProps}
212
+ {...attributes.popper}
213
+ {...restProps}
214
+ >
215
+ <div
216
+ ref={setArrowElement}
217
+ style={popperStyles.arrow}
218
+ className={arrowClasses}
219
+ data-popper-arrow
220
+ />
221
+ {content}
222
+ </Box>
223
+ );
224
+
225
+ return trapFocus ? (
226
+ <FocusTrap
227
+ active={isOpen}
228
+ focusTrapOptions={{
229
+ clickOutsideDeactivates: true,
230
+ }}
231
+ >
232
+ {renderPopperBox()}
233
+ </FocusTrap>
234
+ ) : (
235
+ renderPopperBox()
236
+ );
237
+ };
238
+
239
+ const childrenWithRef = React.Children.map(children, (child) => {
240
+ const childProps = {
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
+ ref: triggerRef as RefObject<HTMLElement> | ((instance: any) => void),
243
+ role: 'button',
244
+ 'aria-expanded': isOpen,
245
+ 'aria-haspopup': true,
246
+ };
247
+
248
+ // Merge local ref with any ref passed originally to child component.
249
+ // We have to cast with `as` so TS compiler doesn't complain since ReactNode/ReactChild types don't
250
+ // explicitly declare ref as a property in the object.
251
+ if ((child as ReactNode & { ref: any })?.ref) {
252
+ // eslint-disable-line @typescript-eslint/no-explicit-any
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
+ childProps.ref = mergeRefs([
255
+ (child as ReactNode & { ref: any })?.ref,
256
+ childProps.ref,
257
+ ]);
258
+ }
259
+
260
+ if (isValidElement(child)) {
261
+ return cloneElement(child, childProps);
262
+ }
263
+
264
+ return child;
265
+ });
266
+
267
+ return (
268
+ <>
269
+ {childrenWithRef}
270
+ {isOpen &&
271
+ // portalTarget should always be defined if withPortal is true, but better safe than sorry here!
272
+ (withPortal && portalTarget
273
+ ? createPortal(renderPopperContent(), portalTarget)
274
+ : renderPopperContent())}
275
+ </>
276
+ );
277
+ };
@@ -0,0 +1,81 @@
1
+ import { Canvas, Meta, ArgTypes } from '@storybook/blocks';
2
+ import { RadioGroup } from './RadioGroup';
3
+ import * as Stories from './RadioGroup.stories';
4
+
5
+ <Meta of={Stories} />
6
+
7
+ # RadioGroup
8
+
9
+ Use a RadioGroup when a user is required to select one of five or fewer options. It is ideal for this scenario because the options are displayed without having to interact. If there are more than five options, use the [SelectInput](?path=/docs/components-selectinput-overview--default-story) instead.
10
+
11
+ ## Props
12
+
13
+ <ArgTypes of={RadioGroup} />
14
+
15
+ ## Default
16
+
17
+ All that is required to render a basic version of the RadioGroup is the group's `name`, an onchange event handler passed to the `onChange` prop, and a list of options passed to the `options` prop. Each option must include a unique `id`, a `value`, and a `label`.
18
+
19
+ <Canvas isExpanded of={Stories.Default} />
20
+
21
+ ## Title
22
+
23
+ Use the `title` prop to add a title.
24
+
25
+ <Canvas of={Stories.Title} />
26
+
27
+ ## Title and Description
28
+
29
+ Use the `title` and `description` props to add a title and description.
30
+
31
+ <Canvas of={Stories.TitleAndDescription} />
32
+
33
+ ## Required
34
+
35
+ Use the `isRequired` prop to mark the group as requiring at least one selection. To avoid confusion as to whether a radio button group is required or not, authors are encouraged to specify the attribute on all the radio buttons in a group. We recommend to avoid having radio button groups that do not have any initially checked controls in the first place, because this is a state that the user cannot return to.
36
+
37
+ <Canvas of={Stories.Required} />
38
+
39
+ You can remove or customize the required indicator using the `requiredIndicator` prop.
40
+
41
+ <Canvas of={Stories.CustomRequiredIndicator} />
42
+
43
+ ## Pre-Selected Option
44
+
45
+ Use the `value` prop to pre-select an option.
46
+
47
+ <Canvas of={Stories.PreSelectedOption} />
48
+
49
+ ## Disabled Option
50
+
51
+ An individual option, or multiple options can be disabled by marking them as disabled in the list of options.
52
+
53
+ <Canvas of={Stories.DisabledOption} />
54
+
55
+ ## Disabled Group
56
+
57
+ A group can be fully disabled by using the `isDisabled` prop.
58
+
59
+ <Canvas of={Stories.DisabledGroup} />
60
+
61
+ ## Error
62
+
63
+ 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.
64
+
65
+ <Canvas of={Stories.Error} />
66
+
67
+ ## Sizes
68
+
69
+ Use the `size` prop to render differently sized radio icons.
70
+
71
+ <Canvas of={Stories.Sizes} />
72
+
73
+ ## Horizontally Aligned
74
+
75
+ Use the `direction` prop change the alignment of the radio inputs.
76
+
77
+ <Canvas of={Stories.HorizontallyAligned} />
78
+
79
+ ## Component Design Tokens
80
+
81
+ 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).
@@ -0,0 +1,23 @@
1
+ .radio-group {
2
+ .fieldset {
3
+ margin: 0;
4
+ border: none;
5
+ padding: 0;
6
+ font-family: var(--form-control-font-family, var(--INTERNAL_form-control-font-family));
7
+
8
+ .legend {
9
+ display: block;
10
+ margin-bottom: var(--form-control-radio-group-options-spacing, var(--INTERNAL_form-control-radio-group-options-spacing));
11
+ line-height: 1.25;
12
+ color: var(--form-control-font-color, var(--INTERNAL_form-control-font-color));
13
+ font-size: var(--form-control-size-md-label-size, var(--INTERNAL_form-control-size-md-label-size));
14
+ font-weight: var(--form-control-legend-font-weight, var(--INTERNAL_form-control-legend-font-weight));
15
+
16
+ .description {
17
+ margin-top: var(--INTERNAL_form-control-radio-group-description-spacing, var(--INTERNAL_form-control-radio-group-description-spacing));
18
+ color: var(--form-control-description-color, var(--INTERNAL_form-control-description-color));
19
+ font-weight: 400;
20
+ }
21
+ }
22
+ }
23
+ }