@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,437 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen, within } from '@testing-library/react';
3
+ import { Button } from '../Button/Button';
4
+ import { Table } from './Table';
5
+
6
+ const columnConfig = [
7
+ { heading: 'ID', dataKey: 'id' },
8
+ { heading: 'Color', dataKey: 'color' },
9
+ { heading: 'Flavor', dataKey: 'flavor' },
10
+ ];
11
+
12
+ const columnConfigSortable = [
13
+ { heading: 'ID', dataKey: 'id', isSortable: true },
14
+ { heading: 'Color', dataKey: 'color' },
15
+ { heading: 'Flavor', dataKey: 'flavor', isSortable: true },
16
+ ];
17
+
18
+ const tableData = [
19
+ { id: 1, color: 'red', flavor: 'vanilla' },
20
+ { id: 2, color: 'blue', flavor: 'chocolate' },
21
+ { id: 3, color: 'green', flavor: 'strawberry' },
22
+ ];
23
+
24
+ const mockHandleSort = jest.fn();
25
+
26
+ describe('Table', () => {
27
+ describe('Callback Handling', () => {
28
+ describe('onSort', () => {
29
+ test('onSort event fires callback function', () => {
30
+ render(
31
+ <Table
32
+ columns={columnConfigSortable}
33
+ rows={tableData}
34
+ rowKey="id"
35
+ onSort={mockHandleSort}
36
+ />
37
+ );
38
+
39
+ const sortableColumnHeader = screen.getByText('ID');
40
+
41
+ fireEvent.click(sortableColumnHeader);
42
+ expect(mockHandleSort).toHaveBeenCalledTimes(1);
43
+ });
44
+ });
45
+ });
46
+
47
+ describe('States', () => {
48
+ describe('Default', () => {
49
+ test('it renders a table', () => {
50
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
51
+
52
+ const table = screen.getByRole('table');
53
+
54
+ expect(table).toBeInTheDocument();
55
+ });
56
+
57
+ test('it renders a thead and tbody', () => {
58
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
59
+
60
+ const rows = screen.queryAllByRole('rowgroup');
61
+
62
+ expect(rows).toHaveLength(2);
63
+ });
64
+
65
+ test(`it renders the correct amount of headers: ${columnConfig.length}`, () => {
66
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
67
+
68
+ const rows = screen.queryAllByRole('columnheader');
69
+
70
+ expect(rows).toHaveLength(3);
71
+ });
72
+
73
+ test(`it renders 3 headings that match columns headings: ${[
74
+ ...columnConfig.map((c) => `${c.heading}, `),
75
+ ]}`, () => {
76
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
77
+
78
+ const idHeader = screen.getByText('ID');
79
+ const colorHeader = screen.getByText('Color');
80
+ const flavorHeader = screen.getByText('Flavor');
81
+
82
+ expect(idHeader).toBeInTheDocument();
83
+ expect(colorHeader).toBeInTheDocument();
84
+ expect(flavorHeader).toBeInTheDocument();
85
+ });
86
+
87
+ test(`it renders correct amount of rows based on data: ${tableData.length} + 1 (header row)`, () => {
88
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
89
+
90
+ const rows = screen.queryAllByRole('row');
91
+
92
+ expect(rows).toHaveLength(4);
93
+ });
94
+
95
+ test(`it renders correct amount of cells based on columns and rows: ${
96
+ columnConfig.length + tableData.length
97
+ }`, () => {
98
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
99
+
100
+ const cells = screen.queryAllByRole('cell');
101
+
102
+ expect(cells).toHaveLength(9);
103
+ });
104
+
105
+ test('it renders the cell content', () => {
106
+ render(<Table columns={columnConfig} rows={tableData} rowKey="id" />);
107
+ const tableRowArrays = tableData.map((row) =>
108
+ Object.values(row).map((v) => v.toString())
109
+ );
110
+ const cellData = tableRowArrays.reduce(
111
+ (target, current) => [...current, ...target],
112
+ []
113
+ );
114
+
115
+ cellData.map((value) =>
116
+ expect(screen.getByText(value)).toBeInTheDocument()
117
+ );
118
+ });
119
+ });
120
+
121
+ describe('Loading', () => {
122
+ test('it renders a loading indicator', () => {
123
+ render(
124
+ <Table
125
+ columns={columnConfig}
126
+ rows={tableData}
127
+ rowKey="id"
128
+ isLoading
129
+ />
130
+ );
131
+
132
+ const spinner = screen.getByTestId('spinner-testid');
133
+
134
+ expect(spinner).toBeInTheDocument();
135
+ });
136
+ });
137
+
138
+ describe('Sortable', () => {
139
+ test('it renders 2 sortable table headers', () => {
140
+ render(
141
+ <Table columns={columnConfigSortable} rows={tableData} rowKey="id" />
142
+ );
143
+
144
+ const sortableHeaders = screen.getAllByTestId(
145
+ 'tableHeaderCellSortNone-testid'
146
+ );
147
+
148
+ expect(sortableHeaders).toHaveLength(2);
149
+ });
150
+
151
+ test('it passes the sorted column properly to the correct th element', () => {
152
+ render(
153
+ <Table
154
+ columns={columnConfigSortable}
155
+ rows={tableData}
156
+ rowKey="id"
157
+ sortedColumn={{ dataKey: 'flavor', sortDirection: 'ascending' }}
158
+ />
159
+ );
160
+
161
+ const headingSpan = screen.getByText('Flavor');
162
+ expect(headingSpan).toBeInTheDocument();
163
+ const headingElement = headingSpan.closest('th');
164
+ expect(headingElement).toBeInTheDocument();
165
+ if (headingElement) {
166
+ const { getByTestId } = within(headingElement);
167
+ expect(
168
+ getByTestId('tableHeaderCellSortAsc-testid')
169
+ ).toBeInTheDocument();
170
+ }
171
+ });
172
+ });
173
+
174
+ describe('Scrollable', () => {
175
+ test('It renders the table container limited by width/height attributes', () => {
176
+ render(
177
+ <Table
178
+ columns={columnConfig}
179
+ rows={tableData}
180
+ rowKey="id"
181
+ isScrollable={{ x: true, y: true }}
182
+ />
183
+ );
184
+
185
+ const tableContainer = screen.getByTestId('tableContainerDiv-testid');
186
+
187
+ expect(tableContainer).toHaveClass(
188
+ 'scrollable',
189
+ 'scrollable-x',
190
+ 'scrollable-y'
191
+ );
192
+ });
193
+ });
194
+
195
+ describe('Custom Actions', () => {
196
+ test('if action is triggered, function is called', () => {
197
+ const mockHandleClick = jest.fn();
198
+ const tableDataWithClickableButton = [
199
+ { id: 1, color: null, flavor: <Button onClick={mockHandleClick}>Click me</Button> }, // eslint-disable-line
200
+ { id: 2, color: 'blue', flavor: 'chocolate' },
201
+ { id: 3, color: 'green', flavor: 'strawberry' },
202
+ ];
203
+
204
+ render(
205
+ <Table
206
+ columns={columnConfig}
207
+ rows={tableDataWithClickableButton}
208
+ rowKey="id"
209
+ />
210
+ );
211
+
212
+ const button = screen.getByText('Click me');
213
+
214
+ fireEvent.click(button);
215
+ expect(mockHandleClick).toHaveBeenCalledTimes(1);
216
+ });
217
+ });
218
+
219
+ describe('Empty Cell Placeholder', () => {
220
+ test('it renders the placeholder in the empty cell', () => {
221
+ const tableDataWithMissingCellContent = [
222
+ { id: 1, color: null, flavor: 'vanilla' },
223
+ { id: 2, color: 'blue', flavor: 'chocolate' },
224
+ { id: 3, color: 'green', flavor: 'strawberry' },
225
+ ];
226
+
227
+ render(
228
+ <Table
229
+ columns={columnConfig}
230
+ rows={tableDataWithMissingCellContent}
231
+ emptyCellPlaceholder="--"
232
+ rowKey="id"
233
+ />
234
+ );
235
+
236
+ const placeholder = screen.getByText('--');
237
+
238
+ expect(placeholder).toBeInTheDocument();
239
+ });
240
+ });
241
+
242
+ describe('Custom width in column config', () => {
243
+ test('it renders columns with fixed width based on prop.', () => {
244
+ render(
245
+ <Table
246
+ columns={[...columnConfig.map((col) => ({ ...col, width: 100 }))]}
247
+ rows={tableData}
248
+ rowKey="id"
249
+ />
250
+ );
251
+
252
+ const tableHeaderCell = screen.getByText('Flavor').closest('th');
253
+ expect(tableHeaderCell).toHaveStyle({ width: '100px' });
254
+ });
255
+ });
256
+
257
+ describe('Column with no corresponding data key', () => {
258
+ test('it renders a column despite not matching a data key in the rows collection', () => {
259
+ const columnConfigNoKey = [
260
+ { heading: 'ID', dataKey: 'id', isSortable: true },
261
+ { heading: 'Color', dataKey: 'color' },
262
+ { heading: 'Flavor', isSortable: true },
263
+ ];
264
+
265
+ render(
266
+ <Table columns={columnConfigNoKey} rows={tableData} rowKey="id" />
267
+ );
268
+
269
+ const tableHeaderCell = screen.getByText('Flavor');
270
+ expect(tableHeaderCell).toBeInTheDocument();
271
+ });
272
+ });
273
+
274
+ describe('Column with render method', () => {
275
+ test('it renders a column with a custom render method', () => {
276
+ const columnConfigRender = [
277
+ { heading: 'ID', dataKey: 'id', isSortable: true },
278
+ { heading: 'Color', dataKey: 'color' },
279
+ {
280
+ heading: 'Flavor',
281
+ isSortable: true,
282
+ render: () => <button type="submit">Submit</button>,
283
+ },
284
+ ];
285
+
286
+ render(
287
+ <Table columns={columnConfigRender} rows={tableData} rowKey="id" />
288
+ );
289
+
290
+ const submitButtons = screen.getAllByText('Submit');
291
+ expect(submitButtons).toHaveLength(tableData.length);
292
+ });
293
+ });
294
+
295
+ describe('Column with render method based on row', () => {
296
+ test('it renders a column with a custom render method', () => {
297
+ const columnConfigRender = [
298
+ { heading: 'ID', dataKey: 'id', isSortable: true },
299
+ { heading: 'Color', dataKey: 'color' },
300
+ {
301
+ heading: 'Flavor',
302
+ dataKey: 'flavor',
303
+ render: (cell: any) => <button type="submit">{cell}</button>,
304
+ },
305
+ ];
306
+
307
+ render(
308
+ <Table columns={columnConfigRender} rows={tableData} rowKey="id" />
309
+ );
310
+
311
+ const submitButton = screen.getByText('vanilla');
312
+ expect(submitButton).toBeInTheDocument();
313
+ });
314
+ });
315
+
316
+ describe('Text alignment of columns', () => {
317
+ test('it renders column cells with text aligned based on global align prop', () => {
318
+ const { rerender } = render(
319
+ <Table
320
+ columns={columnConfig}
321
+ rows={tableData}
322
+ rowKey="id"
323
+ align="right"
324
+ />
325
+ );
326
+
327
+ const cellsRight = screen.queryAllByRole('cell');
328
+ cellsRight.forEach((cell) => {
329
+ expect(cell).toHaveClass('align-right');
330
+ });
331
+
332
+ rerender(
333
+ <Table
334
+ columns={columnConfig}
335
+ rows={tableData}
336
+ rowKey="id"
337
+ align="center"
338
+ />
339
+ );
340
+
341
+ const cellsCenter = screen.queryAllByRole('cell');
342
+ cellsCenter.forEach((cell) => {
343
+ expect(cell).toHaveClass('align-center');
344
+ });
345
+ });
346
+
347
+ test('It renders column cells with alignment based on column align prop', () => {
348
+ const columnConfigWithAlign = [
349
+ { heading: 'ID', dataKey: 'id' },
350
+ { heading: 'Color', dataKey: 'color', align: 'left' as const },
351
+ { heading: 'Flavor', dataKey: 'flavor', align: 'right' as const },
352
+ ];
353
+
354
+ const tableDataAlign = [
355
+ { id: 1, color: 'red', flavor: 'vanilla' },
356
+ { id: 2, color: 'blue', flavor: 'chocolate' },
357
+ { id: 3, color: 'green', flavor: 'strawberry' },
358
+ ];
359
+
360
+ render(
361
+ <Table
362
+ columns={columnConfigWithAlign}
363
+ rows={tableDataAlign}
364
+ rowKey="id"
365
+ align="center"
366
+ />
367
+ );
368
+
369
+ const cells = screen.queryAllByRole('cell');
370
+ // Checking cell classes based on where they are in the table.
371
+ cells.forEach((cell, index) => {
372
+ // First column
373
+ if (index === 0 || index === 3 || index === 6) {
374
+ expect(cell).toHaveClass('align-center');
375
+ }
376
+ // Second Column
377
+ if (index === 1 || index === 4 || index === 7) {
378
+ expect(cell).not.toHaveClass('align-center');
379
+ expect(cell).not.toHaveClass('align-right');
380
+ }
381
+ // Third Column
382
+ if (index === 2 || index === 5 || index === 8) {
383
+ expect(cell).toHaveClass('align-right');
384
+ }
385
+ });
386
+ });
387
+ });
388
+
389
+ describe('Custom cell classes', () => {
390
+ test('It renders columns with classes passed in the column config', () => {
391
+ const columnConfigClasses = [
392
+ { heading: 'ID', dataKey: 'id', headerClassName: 'header-class' },
393
+ { heading: 'Color', dataKey: 'color', cellClassName: 'cell-class' },
394
+ { heading: 'Flavor', dataKey: 'flavor', align: 'right' as const },
395
+ ];
396
+
397
+ render(
398
+ <Table columns={columnConfigClasses} rows={tableData} rowKey="id" />
399
+ );
400
+
401
+ const headerCells = screen.queryAllByRole('columnheader');
402
+ expect(headerCells[0]).toHaveClass('header-class'); // Header(th) for first column.
403
+
404
+ const cells = screen.queryAllByRole('cell');
405
+ // Checking cell classes based on where they are in the table.
406
+ cells.forEach((cell, index) => {
407
+ // Second Column
408
+ if (index === 1 || index === 4 || index === 7) {
409
+ expect(cell).toHaveClass('cell-class');
410
+ }
411
+ });
412
+ });
413
+ });
414
+
415
+ describe('Sticky Column', () => {
416
+ test('if a column is sticky, it renders the row header elements', () => {
417
+ const columnConfigStickyColumn = [
418
+ { heading: 'ID', dataKey: 'id', sticky: 'left' as const },
419
+ { heading: 'Color', dataKey: 'color' },
420
+ { heading: 'Flavor', dataKey: 'flavor' },
421
+ ];
422
+
423
+ render(
424
+ <Table
425
+ columns={columnConfigStickyColumn}
426
+ rows={tableData}
427
+ rowKey="id"
428
+ />
429
+ );
430
+
431
+ const rowHeaders = screen.getAllByRole('rowheader');
432
+
433
+ expect(rowHeaders).toHaveLength(3);
434
+ });
435
+ });
436
+ });
437
+ });
@@ -0,0 +1,171 @@
1
+ import React, { FC } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Column, Row, EventWithColumnKey } from '../../types';
4
+ import { Spinner } from '../Spinner/Spinner';
5
+ import styles from './Table.module.scss';
6
+ import { TableBody } from './TableBody/TableBody';
7
+ import { TableHead } from './TableHead/TableHead';
8
+
9
+ export interface TableProps {
10
+ /**
11
+ * Columns for the table. See Column definition below for details.
12
+ */
13
+ columns: Column[];
14
+ /**
15
+ * The data rows to be displayed
16
+ */
17
+ rows: Row[];
18
+ /**
19
+ * Key that represents a unique value for a row. This is necessary in
20
+ * order to supply React with a node key on each row.
21
+ */
22
+ rowKey: string;
23
+ /**
24
+ * Additional classes to add.
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Text alignment for all table cells. Can be superseded by passing the same prop into the `Column` object
29
+ * for a specific column.
30
+ */
31
+ align?: 'left' | 'right' | 'center';
32
+ /**
33
+ * Global placeholder for empty cells. Can be overwritten by setting the same attribute
34
+ * in the `Column` config.
35
+ */
36
+ emptyCellPlaceholder?: string | number | undefined;
37
+ /**
38
+ * Enable a hover state on table rows.
39
+ */
40
+ hoverableRows?: boolean;
41
+ /**
42
+ * Remove borders around table, thead, tbody, td, etc.
43
+ */
44
+ isBorderless?: boolean;
45
+ /**
46
+ * Make Table more compact by cutting cell padding in half.
47
+ */
48
+ isCompact?: boolean;
49
+ /**
50
+ * If table scrolls vertically, header will remain stuck to the top of the table, and not scroll away.
51
+ */
52
+ hasStickyHeader?: boolean;
53
+ /**
54
+ * Set to true if data is loading.
55
+ */
56
+ isLoading?: boolean;
57
+ /**
58
+ * Set the maximum width and height and enable scrolling within the container when the table grows
59
+ * past those values. Useful for when we want to render a large table but not force the parent container
60
+ * to grow and instead make the user scroll. Set values to boolean `true` to enable `overflow: scroll` on the table
61
+ * without specifying a width/height
62
+ */
63
+ isScrollable?: {
64
+ x?: boolean;
65
+ y?: boolean;
66
+ };
67
+ /**
68
+ * Adds zebra-striping to any table row within the table body.
69
+ */
70
+ isStriped?: boolean;
71
+ /**
72
+ * Callback function to fire on sorting one of the table headers.
73
+ */
74
+ onSort?: (event: EventWithColumnKey) => void;
75
+ /**
76
+ * The key of the sorted column and its sort direction.
77
+ */
78
+ sortedColumn?: {
79
+ dataKey: string | undefined;
80
+ sortDirection: 'none' | 'ascending' | 'descending' | undefined;
81
+ };
82
+ /**
83
+ * Control the `table-layout` css property for the table.
84
+ */
85
+ useFixedTableLayout?: boolean;
86
+ /**
87
+ * Truncate overflow inside column based on column width. Can be overwritten on specific columns,
88
+ * by passing `truncateOverflow` value on a specific Column
89
+ */
90
+ truncateOverflow?: boolean;
91
+ }
92
+
93
+ export const Table: FC<TableProps> = ({
94
+ columns,
95
+ rows,
96
+ rowKey,
97
+ align = 'left',
98
+ className = undefined,
99
+ emptyCellPlaceholder = undefined,
100
+ hoverableRows = false,
101
+ isBorderless = false,
102
+ isCompact = false,
103
+ hasStickyHeader = false,
104
+ isLoading = false,
105
+ isScrollable = undefined,
106
+ isStriped = false,
107
+ onSort = undefined,
108
+ sortedColumn = undefined,
109
+ useFixedTableLayout = false,
110
+ truncateOverflow = false,
111
+ }) => {
112
+ const containerClasses = classNames(styles.container, {
113
+ [styles['full-height']]: !!isScrollable?.y,
114
+ });
115
+
116
+ const scrollContainerClasses = classNames(
117
+ styles['scroll-container'],
118
+ {
119
+ [styles.scrollable]: !!isScrollable?.x || !!isScrollable?.y,
120
+ [styles['scrollable-x']]: !!isScrollable?.x,
121
+ [styles['scrollable-y']]: !!isScrollable?.y,
122
+ },
123
+ className
124
+ );
125
+
126
+ const tableClasses = classNames(styles.table, {
127
+ [styles.fixed]: useFixedTableLayout,
128
+ [styles.striped]: isStriped,
129
+ [styles.borderless]: isBorderless,
130
+ [styles.compact]: isCompact,
131
+ });
132
+
133
+ return (
134
+ <div className={containerClasses}>
135
+ {isLoading && (
136
+ <div className={styles['loading-mask']}>
137
+ <Spinner size="xl" />
138
+ </div>
139
+ )}
140
+ <div
141
+ className={scrollContainerClasses}
142
+ data-testid="tableContainerDiv-testid"
143
+ >
144
+ <table className={tableClasses}>
145
+ <TableHead
146
+ columns={columns}
147
+ align={align}
148
+ onSort={onSort}
149
+ isBorderless={isBorderless}
150
+ isCompact={isCompact}
151
+ sortedColumn={sortedColumn}
152
+ truncateOverflow={truncateOverflow}
153
+ hasStickyHeader={hasStickyHeader}
154
+ />
155
+ <TableBody
156
+ rows={rows}
157
+ columns={columns}
158
+ rowKey={rowKey}
159
+ align={align}
160
+ isStriped={isStriped}
161
+ emptyCellPlaceholder={emptyCellPlaceholder}
162
+ hoverableRows={hoverableRows}
163
+ truncateOverflow={truncateOverflow}
164
+ isBorderless={isBorderless}
165
+ isCompact={isCompact}
166
+ />
167
+ </table>
168
+ </div>
169
+ </div>
170
+ );
171
+ };
@@ -0,0 +1,19 @@
1
+ .table-body {
2
+ &.striped {
3
+ tr:nth-of-type(odd) {
4
+ th,
5
+ td {
6
+ background-color: var(--color-background-secondary);
7
+ }
8
+ }
9
+ }
10
+
11
+ &.hover {
12
+ tr:hover {
13
+ th,
14
+ td {
15
+ background-color: var(--color-background-secondary);
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { TableBody } from './TableBody';
4
+
5
+ const columns = [
6
+ { heading: 'ID', dataKey: 'id' },
7
+ { heading: 'Color', dataKey: 'color' },
8
+ { heading: 'Flavor', dataKey: 'flavor' },
9
+ ];
10
+
11
+ const rows = [
12
+ { id: 1, color: 'red', flavor: 'vanilla' },
13
+ { id: 2, color: 'blue', flavor: 'chocolate' },
14
+ { id: 3, color: 'green', flavor: 'strawberry' },
15
+ ];
16
+
17
+ describe('TableBody', () => {
18
+ test("It renders with striped rows if passed 'isStriped' prop", () => {
19
+ render(<TableBody columns={columns} rows={rows} rowKey="id" isStriped />);
20
+
21
+ const tableBody = screen.getByRole('rowgroup');
22
+ expect(tableBody).toHaveClass('striped');
23
+ });
24
+
25
+ test('It renders with a custom class when passed as a prop', () => {
26
+ render(
27
+ <TableBody
28
+ columns={columns}
29
+ rows={rows}
30
+ rowKey="id"
31
+ className="my-custom-class"
32
+ />
33
+ );
34
+
35
+ const tableBody = screen.getByRole('rowgroup');
36
+ expect(tableBody).toHaveClass('my-custom-class');
37
+ });
38
+ });