@onewelcome/react-lib-components 0.1.0-alpha

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 (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/BaseModal/BaseModal.d.ts +16 -0
  4. package/dist/BaseModal/BaseModalActions/BaseModalActions.d.ts +5 -0
  5. package/dist/BaseModal/BaseModalContent/BaseModalContent.d.ts +8 -0
  6. package/dist/BaseModal/BaseModalContext.d.ts +2 -0
  7. package/dist/BaseModal/BaseModalHeader/BaseModalHeader.d.ts +8 -0
  8. package/dist/Breadcrumbs/Breadcrumbs.d.ts +9 -0
  9. package/dist/Button/BaseButton.d.ts +8 -0
  10. package/dist/Button/Button.d.ts +10 -0
  11. package/dist/Button/IconButton.d.ts +10 -0
  12. package/dist/ContextMenu/ContextMenu.d.ts +18 -0
  13. package/dist/ContextMenu/ContextMenuItem.d.ts +6 -0
  14. package/dist/Dialog/Dialog.d.ts +18 -0
  15. package/dist/Dialog/DialogActions/DialogActions.d.ts +6 -0
  16. package/dist/Dialog/DialogTitle/DialogTitle.d.ts +6 -0
  17. package/dist/Form/Checkbox/Checkbox.d.ts +13 -0
  18. package/dist/Form/Fieldset/Fieldset.d.ts +13 -0
  19. package/dist/Form/Form.d.ts +5 -0
  20. package/dist/Form/FormControl/FormControl.d.ts +8 -0
  21. package/dist/Form/FormGroup/FormGroup.d.ts +18 -0
  22. package/dist/Form/FormHelperText/FormHelperText.d.ts +7 -0
  23. package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +18 -0
  24. package/dist/Form/Input/Input.d.ts +12 -0
  25. package/dist/Form/Label/Label.d.ts +6 -0
  26. package/dist/Form/Radio/Radio.d.ts +11 -0
  27. package/dist/Form/Select/Option.d.ts +12 -0
  28. package/dist/Form/Select/Select.d.ts +15 -0
  29. package/dist/Form/Textarea/Textarea.d.ts +7 -0
  30. package/dist/Form/Toggle/Toggle.d.ts +6 -0
  31. package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +8 -0
  32. package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +17 -0
  33. package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +10 -0
  34. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +12 -0
  35. package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +14 -0
  36. package/dist/Form/Wrapper/Wrapper/Wrapper.d.ts +28 -0
  37. package/dist/Form/form.interfaces.d.ts +12 -0
  38. package/dist/Icon/Icon.d.ts +75 -0
  39. package/dist/Link/Link.d.ts +15 -0
  40. package/dist/Modal/Modal.d.ts +1 -0
  41. package/dist/Modal/ModalActions/ModalActions.d.ts +1 -0
  42. package/dist/Modal/ModalContent/ModalContent.d.ts +1 -0
  43. package/dist/Modal/ModalHeader/ModalHeader.d.ts +1 -0
  44. package/dist/Popover/Popover.d.ts +11 -0
  45. package/dist/Snackbar/SnackbarContainer/SnackbarContainer.d.ts +12 -0
  46. package/dist/Snackbar/SnackbarItem/SnackbarItem.d.ts +13 -0
  47. package/dist/Snackbar/SnackbarProvider/SnackbarProvider.d.ts +18 -0
  48. package/dist/Snackbar/SnackbarProvider/SnackbarStateProvider.d.ts +14 -0
  49. package/dist/Snackbar/interfaces.d.ts +10 -0
  50. package/dist/Snackbar/useSnackbar.d.ts +1 -0
  51. package/dist/Tiles/Tile.d.ts +16 -0
  52. package/dist/Tiles/Tiles.d.ts +6 -0
  53. package/dist/Tooltip/Tooltip.d.ts +11 -0
  54. package/dist/Typography/Typography.d.ts +12 -0
  55. package/dist/Wizard/BaseWizardSteps/BaseWizardSteps.d.ts +13 -0
  56. package/dist/Wizard/Wizard.d.ts +12 -0
  57. package/dist/Wizard/WizardActions/WizardActions.d.ts +12 -0
  58. package/dist/Wizard/WizardStateProvider.d.ts +12 -0
  59. package/dist/Wizard/WizardSteps/WizardSteps.d.ts +5 -0
  60. package/dist/Wizard/wizardStateReducer.d.ts +26 -0
  61. package/dist/_BaseStyling_/BaseStyling.d.ts +47 -0
  62. package/dist/hooks/useAnimation.d.ts +6 -0
  63. package/dist/hooks/useBodyClick.d.ts +1 -0
  64. package/dist/hooks/useFormSelector.d.ts +13 -0
  65. package/dist/hooks/usePosition.d.ts +36 -0
  66. package/dist/hooks/useScroll.d.ts +2 -0
  67. package/dist/hooks/useSpacing.d.ts +18 -0
  68. package/dist/hooks/useWrapper.d.ts +11 -0
  69. package/dist/index.d.ts +43 -0
  70. package/dist/index.js +8 -0
  71. package/dist/interfaces.d.ts +13 -0
  72. package/dist/react-lib-components.cjs.development.js +3282 -0
  73. package/dist/react-lib-components.cjs.development.js.map +1 -0
  74. package/dist/react-lib-components.cjs.production.min.js +2 -0
  75. package/dist/react-lib-components.cjs.production.min.js.map +1 -0
  76. package/dist/react-lib-components.esm.js +3235 -0
  77. package/dist/react-lib-components.esm.js.map +1 -0
  78. package/dist/util/helper.d.ts +1 -0
  79. package/package.json +88 -0
  80. package/src/BaseModal/BaseModal.module.scss +58 -0
  81. package/src/BaseModal/BaseModal.test.tsx +59 -0
  82. package/src/BaseModal/BaseModal.tsx +113 -0
  83. package/src/BaseModal/BaseModalActions/BaseModalActions.module.scss +9 -0
  84. package/src/BaseModal/BaseModalActions/BaseModalActions.test.tsx +17 -0
  85. package/src/BaseModal/BaseModalActions/BaseModalActions.tsx +14 -0
  86. package/src/BaseModal/BaseModalContent/BaseModalContent.module.scss +6 -0
  87. package/src/BaseModal/BaseModalContent/BaseModalContent.test.tsx +29 -0
  88. package/src/BaseModal/BaseModalContent/BaseModalContent.tsx +35 -0
  89. package/src/BaseModal/BaseModalContext.ts +2 -0
  90. package/src/BaseModal/BaseModalHeader/BaseModalHeader.module.scss +17 -0
  91. package/src/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +30 -0
  92. package/src/BaseModal/BaseModalHeader/BaseModalHeader.tsx +28 -0
  93. package/src/Breadcrumbs/Breadcrumbs.module.scss +14 -0
  94. package/src/Breadcrumbs/Breadcrumbs.test.tsx +42 -0
  95. package/src/Breadcrumbs/Breadcrumbs.tsx +48 -0
  96. package/src/Button/BaseButton.module.scss +20 -0
  97. package/src/Button/BaseButton.test.tsx +59 -0
  98. package/src/Button/BaseButton.tsx +31 -0
  99. package/src/Button/Button.module.scss +336 -0
  100. package/src/Button/Button.test.tsx +76 -0
  101. package/src/Button/Button.tsx +44 -0
  102. package/src/Button/IconButton.module.scss +161 -0
  103. package/src/Button/IconButton.test.tsx +47 -0
  104. package/src/Button/IconButton.tsx +29 -0
  105. package/src/ContextMenu/ContextMenu.module.scss +20 -0
  106. package/src/ContextMenu/ContextMenu.test.tsx +93 -0
  107. package/src/ContextMenu/ContextMenu.tsx +91 -0
  108. package/src/ContextMenu/ContextMenuItem.module.scss +31 -0
  109. package/src/ContextMenu/ContextMenuItem.tsx +15 -0
  110. package/src/Dialog/Dialog.module.scss +16 -0
  111. package/src/Dialog/Dialog.test.tsx +79 -0
  112. package/src/Dialog/Dialog.tsx +96 -0
  113. package/src/Dialog/DialogActions/DialogActions.module.scss +11 -0
  114. package/src/Dialog/DialogActions/DialogActions.test.tsx +25 -0
  115. package/src/Dialog/DialogActions/DialogActions.tsx +21 -0
  116. package/src/Dialog/DialogTitle/DialogTitle.module.scss +7 -0
  117. package/src/Dialog/DialogTitle/DialogTitle.test.tsx +18 -0
  118. package/src/Dialog/DialogTitle/DialogTitle.tsx +18 -0
  119. package/src/Form/Checkbox/Checkbox.module.scss +65 -0
  120. package/src/Form/Checkbox/Checkbox.test.tsx +119 -0
  121. package/src/Form/Checkbox/Checkbox.tsx +145 -0
  122. package/src/Form/Fieldset/Fieldset.module.scss +19 -0
  123. package/src/Form/Fieldset/Fieldset.test.tsx +85 -0
  124. package/src/Form/Fieldset/Fieldset.tsx +55 -0
  125. package/src/Form/Form.module.scss +3 -0
  126. package/src/Form/Form.test.tsx +47 -0
  127. package/src/Form/Form.tsx +14 -0
  128. package/src/Form/FormControl/FormControl.module.scss +67 -0
  129. package/src/Form/FormControl/FormControl.test.tsx +56 -0
  130. package/src/Form/FormControl/FormControl.tsx +47 -0
  131. package/src/Form/FormGroup/FormGroup.module.scss +29 -0
  132. package/src/Form/FormGroup/FormGroup.test.tsx +61 -0
  133. package/src/Form/FormGroup/FormGroup.tsx +78 -0
  134. package/src/Form/FormHelperText/FormHelperText.module.scss +8 -0
  135. package/src/Form/FormHelperText/FormHelperText.test.tsx +42 -0
  136. package/src/Form/FormHelperText/FormHelperText.tsx +22 -0
  137. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.module.scss +33 -0
  138. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.tsx +65 -0
  139. package/src/Form/Input/Input.module.scss +65 -0
  140. package/src/Form/Input/Input.test.tsx +135 -0
  141. package/src/Form/Input/Input.tsx +72 -0
  142. package/src/Form/Label/Label.module.scss +5 -0
  143. package/src/Form/Label/Label.test.tsx +26 -0
  144. package/src/Form/Label/Label.tsx +19 -0
  145. package/src/Form/Radio/Radio.module.scss +100 -0
  146. package/src/Form/Radio/Radio.test.tsx +88 -0
  147. package/src/Form/Radio/Radio.tsx +98 -0
  148. package/src/Form/Select/Option.test.tsx +15 -0
  149. package/src/Form/Select/Option.tsx +57 -0
  150. package/src/Form/Select/Select.module.scss +189 -0
  151. package/src/Form/Select/Select.test.tsx +96 -0
  152. package/src/Form/Select/Select.tsx +217 -0
  153. package/src/Form/Textarea/Textarea.module.scss +53 -0
  154. package/src/Form/Textarea/Textarea.test.tsx +76 -0
  155. package/src/Form/Textarea/Textarea.tsx +33 -0
  156. package/src/Form/Toggle/Toggle.module.scss +58 -0
  157. package/src/Form/Toggle/Toggle.test.tsx +29 -0
  158. package/src/Form/Toggle/Toggle.tsx +20 -0
  159. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.module.scss +12 -0
  160. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +99 -0
  161. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.tsx +62 -0
  162. package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +24 -0
  163. package/src/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +93 -0
  164. package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +92 -0
  165. package/src/Form/Wrapper/RadioWrapper/RadioWrapper.module.scss +12 -0
  166. package/src/Form/Wrapper/RadioWrapper/RadioWrapper.test.tsx +101 -0
  167. package/src/Form/Wrapper/RadioWrapper/RadioWrapper.tsx +74 -0
  168. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.module.scss +14 -0
  169. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +101 -0
  170. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +59 -0
  171. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +65 -0
  172. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.test.tsx +125 -0
  173. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +105 -0
  174. package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +35 -0
  175. package/src/Form/Wrapper/Wrapper/Wrapper.test.tsx +17 -0
  176. package/src/Form/Wrapper/Wrapper/Wrapper.tsx +101 -0
  177. package/src/Form/form.interfaces.ts +14 -0
  178. package/src/Icon/Icon.module.scss +278 -0
  179. package/src/Icon/Icon.test.tsx +39 -0
  180. package/src/Icon/Icon.tsx +94 -0
  181. package/src/Link/Link.module.scss +46 -0
  182. package/src/Link/Link.test.tsx +122 -0
  183. package/src/Link/Link.tsx +80 -0
  184. package/src/Link/types.d.ts +9 -0
  185. package/src/Modal/Modal.test.tsx +16 -0
  186. package/src/Modal/Modal.tsx +1 -0
  187. package/src/Modal/ModalActions/ModalActions.tsx +4 -0
  188. package/src/Modal/ModalContent/ModalContent.tsx +4 -0
  189. package/src/Modal/ModalHeader/ModalHeader.tsx +4 -0
  190. package/src/Popover/Popover.module.scss +18 -0
  191. package/src/Popover/Popover.test.tsx +84 -0
  192. package/src/Popover/Popover.tsx +46 -0
  193. package/src/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +35 -0
  194. package/src/Snackbar/SnackbarContainer/SnackbarContainer.test.tsx +37 -0
  195. package/src/Snackbar/SnackbarContainer/SnackbarContainer.tsx +28 -0
  196. package/src/Snackbar/SnackbarItem/SnackbarItem.module.scss +135 -0
  197. package/src/Snackbar/SnackbarItem/SnackbarItem.test.tsx +47 -0
  198. package/src/Snackbar/SnackbarItem/SnackbarItem.tsx +105 -0
  199. package/src/Snackbar/SnackbarProvider/SnackbarProvider.test.tsx +179 -0
  200. package/src/Snackbar/SnackbarProvider/SnackbarProvider.tsx +127 -0
  201. package/src/Snackbar/SnackbarProvider/SnackbarStateProvider.tsx +25 -0
  202. package/src/Snackbar/interfaces.ts +11 -0
  203. package/src/Snackbar/useSnackbar.ts +4 -0
  204. package/src/Tiles/Tile.module.scss +72 -0
  205. package/src/Tiles/Tile.test.tsx +129 -0
  206. package/src/Tiles/Tile.tsx +48 -0
  207. package/src/Tiles/Tiles.module.scss +11 -0
  208. package/src/Tiles/Tiles.test.tsx +118 -0
  209. package/src/Tiles/Tiles.tsx +48 -0
  210. package/src/Tooltip/Tooltip.module.scss +42 -0
  211. package/src/Tooltip/Tooltip.test.tsx +72 -0
  212. package/src/Tooltip/Tooltip.tsx +130 -0
  213. package/src/Typography/Typography.module.scss +46 -0
  214. package/src/Typography/Typography.test.tsx +114 -0
  215. package/src/Typography/Typography.tsx +84 -0
  216. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.module.scss +192 -0
  217. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.test.tsx +75 -0
  218. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.tsx +86 -0
  219. package/src/Wizard/Wizard.test.tsx +198 -0
  220. package/src/Wizard/Wizard.tsx +49 -0
  221. package/src/Wizard/WizardActions/WizardActions.test.tsx +168 -0
  222. package/src/Wizard/WizardActions/WizardActions.tsx +100 -0
  223. package/src/Wizard/WizardStateProvider.tsx +26 -0
  224. package/src/Wizard/WizardSteps/WizardSteps.test.tsx +110 -0
  225. package/src/Wizard/WizardSteps/WizardSteps.tsx +30 -0
  226. package/src/Wizard/wizardStateReducer.ts +51 -0
  227. package/src/_BaseStyling_/BaseStyling.test.tsx +39 -0
  228. package/src/_BaseStyling_/BaseStyling.tsx +115 -0
  229. package/src/hooks/useAnimation.test.tsx +45 -0
  230. package/src/hooks/useAnimation.ts +20 -0
  231. package/src/hooks/useBodyClick.test.tsx +39 -0
  232. package/src/hooks/useBodyClick.ts +20 -0
  233. package/src/hooks/useFormSelector.test.ts +40 -0
  234. package/src/hooks/useFormSelector.ts +47 -0
  235. package/src/hooks/usePosition.test.tsx +494 -0
  236. package/src/hooks/usePosition.ts +347 -0
  237. package/src/hooks/useScroll.test.tsx +20 -0
  238. package/src/hooks/useScroll.ts +16 -0
  239. package/src/hooks/useSpacing.test.ts +70 -0
  240. package/src/hooks/useSpacing.ts +42 -0
  241. package/src/hooks/useWrapper.test.ts +49 -0
  242. package/src/hooks/useWrapper.ts +35 -0
  243. package/src/index.ts +52 -0
  244. package/src/interfaces.ts +15 -0
  245. package/src/readyclasses.module.scss +77 -0
  246. package/src/types.d.ts +4 -0
  247. package/src/util/helper.test.tsx +15 -0
  248. package/src/util/helper.tsx +80 -0
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { IconButton, Props } from './IconButton';
3
+ import { render, getByRole } from '@testing-library/react';
4
+ import { Icon, Icons } from '../Icon/Icon';
5
+
6
+ const createButton = ({
7
+ title = 'Icon',
8
+ children = <Icon icon={Icons.Calendar} />,
9
+ ...rest
10
+ }: Partial<Props> = {}) => {
11
+ const queries = render(
12
+ <IconButton {...rest} title={title}>
13
+ {children}
14
+ </IconButton>
15
+ );
16
+ const button = getByRole(queries.container, 'button');
17
+
18
+ return {
19
+ ...queries,
20
+ button,
21
+ };
22
+ };
23
+
24
+ describe('IconButton', () => {
25
+ it('renders without crashing', () => {
26
+ const { button } = createButton();
27
+
28
+ expect(button).toBeDefined();
29
+ });
30
+ });
31
+
32
+ describe('Different variants of the button', () => {
33
+ it('should have a class of "primary"', () => {
34
+ const { button } = createButton({ color: 'primary' });
35
+ expect(button.classList.contains('primary')).toBe(true);
36
+ });
37
+
38
+ it('should have a class of "secondary"', () => {
39
+ const { button } = createButton({ color: 'secondary' });
40
+ expect(button.classList.contains('secondary')).toBe(true);
41
+ });
42
+
43
+ it('should have a class of "tertiary"', () => {
44
+ const { button } = createButton({ color: 'tertiary' });
45
+ expect(button.classList.contains('tertiary')).toBe(true);
46
+ });
47
+ });
@@ -0,0 +1,29 @@
1
+ import React, { RefObject } from 'react';
2
+ import { BaseButton, Props as BaseButtonProps } from './BaseButton';
3
+ import classes from './IconButton.module.scss';
4
+ import readyclasses from '../readyclasses.module.scss';
5
+
6
+ export interface Props extends Omit<BaseButtonProps, 'ref'> {
7
+ children?: React.ReactNode;
8
+ iconSize?: 's' | 'm' | 'l';
9
+ className?: string;
10
+ title: string;
11
+ ref?: RefObject<HTMLButtonElement>;
12
+ }
13
+
14
+ export const IconButton = React.forwardRef<HTMLButtonElement, Props>(
15
+ ({ children, color = 'primary', iconSize = 'm', title, ...rest }, ref) => {
16
+ return (
17
+ <BaseButton
18
+ {...rest}
19
+ ref={ref}
20
+ className={`${classes['icon-button']} ${classes[color]} ${classes['button-' + iconSize]}`}
21
+ >
22
+ <>
23
+ {children}
24
+ <span className={readyclasses['sr-only']}>{title}</span>
25
+ </>
26
+ </BaseButton>
27
+ );
28
+ }
29
+ );
@@ -0,0 +1,20 @@
1
+ .context-menu {
2
+ position: relative;
3
+ display: inline-block;
4
+ box-sizing: border-box;
5
+
6
+ ul {
7
+ white-space: nowrap;
8
+ margin: 1rem 0;
9
+ }
10
+ }
11
+
12
+ .menu {
13
+ list-style: none;
14
+ padding: 0;
15
+ min-width: 200px;
16
+
17
+ button {
18
+ text-align: left;
19
+ }
20
+ }
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import { ContextMenu, Props } from './ContextMenu';
3
+ import { render, getByRole } from '@testing-library/react';
4
+ import { Button } from '../Button/Button';
5
+ import { ContextMenuItem } from './ContextMenuItem';
6
+ import userEvent from '@testing-library/user-event';
7
+
8
+ const onShow = jest.fn();
9
+ const onClick = jest.fn();
10
+
11
+ const defaultParams: Props = {
12
+ id: 'example-contextmenu',
13
+ trigger: <Button data-testid="trigger">Click me for a context menu</Button>,
14
+ children: [
15
+ <ContextMenuItem onClick={onClick} data-testid="contextmenuitem" key="1">
16
+ Example item 1
17
+ </ContextMenuItem>,
18
+ <ContextMenuItem key="2">Example item 2</ContextMenuItem>,
19
+ <ContextMenuItem key="3">Example item 3</ContextMenuItem>,
20
+ ],
21
+ show: false,
22
+ onShow: onShow,
23
+ };
24
+
25
+ const createContextMenu = (params?: (defaultParams: Props) => Props) => {
26
+ let parameters: Props = defaultParams;
27
+ if (params) {
28
+ parameters = params(defaultParams);
29
+ }
30
+
31
+ const queries = render(<ContextMenu {...parameters} data-testid="contextmenu"></ContextMenu>);
32
+
33
+ const contextmenu = queries.getByTestId('contextmenu');
34
+ const trigger = queries.getByTestId('trigger');
35
+
36
+ return {
37
+ ...queries,
38
+ contextmenu,
39
+ trigger,
40
+ };
41
+ };
42
+
43
+ describe('ContextMenu should render', () => {
44
+ it('renders without crashing', () => {
45
+ const { contextmenu } = createContextMenu();
46
+
47
+ expect(contextmenu).toBeTruthy();
48
+ });
49
+
50
+ it('executed onShow function', () => {
51
+ const { trigger } = createContextMenu();
52
+
53
+ userEvent.click(trigger);
54
+
55
+ expect(onShow).toHaveBeenCalled();
56
+ });
57
+
58
+ it('executed onShow function', () => {
59
+ const { getByTestId } = createContextMenu((defaultParams) => ({
60
+ ...defaultParams,
61
+ show: true,
62
+ }));
63
+
64
+ const contextmenuitem = getByTestId('contextmenuitem');
65
+ const button = getByRole(contextmenuitem, 'button');
66
+
67
+ userEvent.click(button);
68
+
69
+ expect(contextmenuitem).toBeTruthy();
70
+ expect(onClick).toHaveBeenCalled();
71
+ });
72
+
73
+ it('should throw an error', () => {
74
+ // Prevent throwing an error in the console when this test is executed. We fix this and the end of this test.
75
+ const err = console.error;
76
+ console.error = jest.fn();
77
+
78
+ let actual;
79
+
80
+ try {
81
+ // @ts-ignore: mandatory props (test for non-typescript react projects)
82
+ render(<ContextMenu />);
83
+ } catch (e: any) {
84
+ actual = e.message;
85
+ }
86
+
87
+ const expected = 'You need to provide an ID to the context menu';
88
+
89
+ expect(actual).toEqual(expected);
90
+
91
+ console.error = err;
92
+ });
93
+ });
@@ -0,0 +1,91 @@
1
+ import React, { HTMLProps, ReactElement, useEffect, useRef, useState } from 'react';
2
+ import { Props as ButtonProps } from '../Button/Button';
3
+ import { Props as IconButtonProps } from '../Button/IconButton';
4
+ import { Popover } from '../Popover/Popover';
5
+ import { Placement, Offset } from '../hooks/usePosition';
6
+ import { Props as ContextMenuItemProps } from './ContextMenuItem';
7
+ import classes from './ContextMenu.module.scss';
8
+ import { useBodyClick } from '../hooks/useBodyClick';
9
+ import { createPortal } from 'react-dom';
10
+
11
+ export interface Props extends HTMLProps<HTMLDivElement> {
12
+ trigger: ReactElement<ButtonProps> | ReactElement<IconButtonProps>;
13
+ children: ReactElement<ContextMenuItemProps> | ReactElement<ContextMenuItemProps>[];
14
+ placement?: Placement;
15
+ transformOrigin?: Placement;
16
+ offset?: Offset;
17
+ id: string;
18
+ show?: boolean;
19
+ domRoot?: HTMLElement;
20
+ onShow?: () => void;
21
+ onClose?: () => void;
22
+ }
23
+
24
+ export const ContextMenu = ({
25
+ trigger,
26
+ children,
27
+ id,
28
+ show = false,
29
+ onShow,
30
+ onClose,
31
+ placement = { horizontal: 'right', vertical: 'top' },
32
+ offset = { top: 0, bottom: 0, left: 0, right: 0 },
33
+ transformOrigin = { horizontal: 'left', vertical: 'top' },
34
+ domRoot = document.body,
35
+ ...rest
36
+ }: Props) => {
37
+ const anchorEl = useRef<HTMLButtonElement>(null);
38
+ const [showContextMenu, setShowContextMenu] = useState(show);
39
+
40
+ if (!id) {
41
+ throw new Error('You need to provide an ID to the context menu');
42
+ }
43
+
44
+ useBodyClick(
45
+ (event) => {
46
+ return showContextMenu && anchorEl.current !== event.target;
47
+ },
48
+ () => {
49
+ setShowContextMenu(false);
50
+ },
51
+ showContextMenu
52
+ );
53
+
54
+ useEffect(() => {
55
+ if (showContextMenu === true) {
56
+ onShow && onShow();
57
+ } else {
58
+ onClose && onClose();
59
+ }
60
+ }, [showContextMenu]);
61
+
62
+ const renderTrigger = () =>
63
+ React.cloneElement(trigger, {
64
+ id: id,
65
+ ['aria-haspopup']: 'true',
66
+ ['aria-controls']: `${id}-menu`,
67
+ ['aria-expanded']: show,
68
+ onClick: () => setShowContextMenu(!showContextMenu),
69
+ ref: anchorEl,
70
+ });
71
+
72
+ return (
73
+ <div {...rest} className={classes['context-menu']}>
74
+ {renderTrigger()}
75
+ {createPortal(
76
+ <Popover
77
+ placement={placement}
78
+ transformOrigin={transformOrigin}
79
+ offset={offset}
80
+ anchorEl={anchorEl}
81
+ show={showContextMenu}
82
+ >
83
+ <ul className={classes.menu} id={`${id}-menu`} aria-describedby={id} role="menu">
84
+ {children}
85
+ </ul>
86
+ </Popover>,
87
+ domRoot
88
+ )}
89
+ </div>
90
+ );
91
+ };
@@ -0,0 +1,31 @@
1
+ .context-menu-item {
2
+ position: relative;
3
+
4
+ button {
5
+ background-color: transparent;
6
+ border: 0;
7
+ font-family: var(--font-family);
8
+ font-size: var(--font-size);
9
+ cursor: pointer;
10
+ width: 100%;
11
+ height: 100%;
12
+ padding: 0.5rem 1.25rem;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ &:after {
17
+ content: '';
18
+ position: absolute;
19
+ top: 0;
20
+ left: 0;
21
+ background-color: transparent;
22
+ width: 100%;
23
+ height: 100%;
24
+ opacity: 0.1;
25
+ pointer-events: none;
26
+ }
27
+
28
+ &:hover:after {
29
+ background-color: var(--color-primary);
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ import React, { HTMLProps } from 'react';
2
+ import classes from './ContextMenuItem.module.scss';
3
+
4
+ export interface Props extends Omit<HTMLProps<HTMLLIElement>, 'onClick'> {
5
+ children?: string;
6
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
7
+ }
8
+
9
+ export const ContextMenuItem = ({ children, onClick, ...rest }: Props) => {
10
+ return (
11
+ <li {...rest} role="menuitem" className={classes['context-menu-item']}>
12
+ <button onClick={(event) => onClick && onClick(event)}>{children}</button>
13
+ </li>
14
+ );
15
+ };
@@ -0,0 +1,16 @@
1
+ .dialog {
2
+ align-items: center;
3
+ }
4
+
5
+ @media only screen and (min-width: 800px) {
6
+ .container {
7
+ width: auto;
8
+ max-width: 440px;
9
+ margin-top: 0;
10
+ max-height: 90%;
11
+ }
12
+ }
13
+
14
+ .content {
15
+ padding-top: 0;
16
+ }
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import { Dialog, Props } from './Dialog';
3
+ import { render, getAllByRole, getByRole, fireEvent, getByText } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const initParams: Props = {
7
+ id: 'modal',
8
+ open: true,
9
+ onClose: jest.fn(),
10
+ alignActions: 'left',
11
+ title: 'Example dialog',
12
+ primaryAction: {
13
+ label: 'Save',
14
+ onClick: jest.fn(),
15
+ },
16
+ secondaryAction: {
17
+ label: 'Cancel',
18
+ onClick: jest.fn(),
19
+ },
20
+ children: 'This is example dialog content.',
21
+ };
22
+
23
+ const getButtons = (container: HTMLElement) => getAllByRole(container, 'button');
24
+
25
+ describe('Dialog', () => {
26
+ it('renders without crashing', () => {
27
+ const { container } = render(<Dialog {...initParams} />);
28
+ const [primaryButton, secondaryButton] = getButtons(container);
29
+
30
+ expect(getByText(container, initParams.title)).toBeDefined();
31
+ expect(getByText(container, initParams.children as string)).toBeDefined();
32
+ const actionsDiv = primaryButton.closest('footer');
33
+ expect(actionsDiv).toHaveClass('left');
34
+ expect(actionsDiv?.children[0]).toEqual(primaryButton);
35
+ expect(actionsDiv?.children[1]).toEqual(secondaryButton);
36
+ expect(primaryButton).toHaveClass('fill');
37
+ expect(secondaryButton).toHaveClass('text');
38
+ });
39
+
40
+ it('renders action aligned to right', () => {
41
+ const { container } = render(<Dialog {...initParams} alignActions="right" />);
42
+ const [secondaryButton, primaryButton] = getButtons(container);
43
+
44
+ const actionsDiv = primaryButton.closest('footer');
45
+ expect(actionsDiv).not.toHaveClass('left');
46
+ expect(actionsDiv?.children[0]).toEqual(secondaryButton);
47
+ expect(actionsDiv?.children[1]).toEqual(primaryButton);
48
+ expect(primaryButton).toHaveClass('fill');
49
+ expect(secondaryButton).toHaveClass('text');
50
+ });
51
+
52
+ it('renders only one button', () => {
53
+ const { container } = render(<Dialog {...initParams} secondaryAction={undefined} />);
54
+ const buttons = getButtons(container);
55
+
56
+ expect(buttons).toHaveLength(1);
57
+ expect(buttons[0]).toHaveClass('fill');
58
+ });
59
+
60
+ it('should handle clicking on buttons, ESC and ENTER keys', () => {
61
+ const { container } = render(<Dialog {...initParams} />);
62
+ const [primaryButton, secondaryButton] = getButtons(container);
63
+ expect(initParams.primaryAction.onClick).toHaveBeenCalledTimes(0);
64
+ expect(initParams.secondaryAction?.onClick).toHaveBeenCalledTimes(0);
65
+ expect(initParams.onClose).toHaveBeenCalledTimes(0);
66
+
67
+ const autoSummissionInput = container.querySelector('input') as HTMLElement;
68
+ userEvent.type(autoSummissionInput, '{enter}');
69
+ expect(initParams.primaryAction.onClick).toHaveBeenCalledTimes(1);
70
+
71
+ fireEvent.keyDown(getByRole(container, 'dialog'), { key: 'Escape' });
72
+ expect(initParams.onClose).toHaveBeenCalledTimes(1);
73
+
74
+ userEvent.click(primaryButton);
75
+ expect(initParams.primaryAction.onClick).toHaveBeenCalledTimes(2);
76
+ userEvent.click(secondaryButton);
77
+ expect(initParams.secondaryAction?.onClick).toHaveBeenCalledTimes(1);
78
+ });
79
+ });
@@ -0,0 +1,96 @@
1
+ import React, { HTMLAttributes } from 'react';
2
+ import { BaseModal } from '../BaseModal/BaseModal';
3
+ import { BaseModalContent } from '../BaseModal/BaseModalContent/BaseModalContent';
4
+ import { DialogActions } from './DialogActions/DialogActions';
5
+ import classes from './Dialog.module.scss';
6
+ import { DialogTitle } from './DialogTitle/DialogTitle';
7
+ import { Button, Props as ButtonProps } from '../Button/Button';
8
+ import { labelId, descriptionId } from '../BaseModal/BaseModalContext';
9
+
10
+ export interface Props extends HTMLAttributes<HTMLDivElement> {
11
+ id: string;
12
+ open: boolean;
13
+ children: React.ReactNode;
14
+ alignActions: 'left' | 'right';
15
+ onClose: () => void;
16
+ title: string;
17
+ primaryAction: Action;
18
+ secondaryAction?: Action;
19
+ zIndex?: number;
20
+ }
21
+
22
+ export interface Action extends Omit<ButtonProps, 'variant' | 'ref'> {
23
+ label: string;
24
+ onClick: (event?: React.MouseEvent<HTMLButtonElement>) => unknown;
25
+ }
26
+
27
+ export const Dialog = ({
28
+ id,
29
+ open,
30
+ children,
31
+ alignActions,
32
+ onClose,
33
+ title,
34
+ primaryAction,
35
+ secondaryAction,
36
+ zIndex,
37
+ ...restProps
38
+ }: Props) => {
39
+ const { label: primaryLabel, ...restOfPrimaryAction } = primaryAction;
40
+ const PrimaryButton = (
41
+ <Button key="primary" {...restOfPrimaryAction}>
42
+ {primaryLabel}
43
+ </Button>
44
+ );
45
+ const TertiaryButton =
46
+ secondaryAction &&
47
+ (function () {
48
+ const { label: secondaryLabel, ...restOfSecondaryAction } = secondaryAction;
49
+ return (
50
+ <Button key="tertiary" variant="text" {...restOfSecondaryAction}>
51
+ {secondaryLabel}
52
+ </Button>
53
+ );
54
+ })();
55
+
56
+ const onHiddenInputKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
57
+ /** It has to be here because then we will need to check if user doesn't click tab to select action button and want to do another action then primary one? */
58
+ if (event.key === 'Enter') {
59
+ primaryAction.onClick();
60
+ }
61
+ };
62
+
63
+ return (
64
+ <BaseModal
65
+ {...restProps}
66
+ id={id}
67
+ className={classes['dialog']}
68
+ containerClassName={classes['container']}
69
+ open={open}
70
+ disableBackdrop
71
+ onClose={onClose}
72
+ zIndex={zIndex}
73
+ >
74
+ <DialogTitle id={labelId(id)} title={title} />
75
+ <BaseModalContent id={descriptionId(id)} className={classes['content']} disableAutoFocus>
76
+ {children}
77
+ </BaseModalContent>
78
+ <DialogActions align={alignActions}>
79
+ {alignActions === 'left'
80
+ ? [PrimaryButton, TertiaryButton]
81
+ : [TertiaryButton, PrimaryButton]}
82
+ </DialogActions>
83
+ <input
84
+ autoFocus
85
+ style={{
86
+ position: 'absolute',
87
+ width: 0,
88
+ height: 0,
89
+ opacity: 0,
90
+ }}
91
+ tabIndex={-1}
92
+ onKeyPress={onHiddenInputKeyPress}
93
+ />
94
+ </BaseModal>
95
+ );
96
+ };
@@ -0,0 +1,11 @@
1
+ .actions {
2
+ margin: 1.5rem 1.25rem 1.25rem;
3
+
4
+ &.left {
5
+ justify-content: flex-start;
6
+ }
7
+
8
+ & * + * {
9
+ margin-left: 1.25rem;
10
+ }
11
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { DialogActions, Props } from './DialogActions';
3
+ import { render } from '@testing-library/react';
4
+
5
+ const initParams: Props = {
6
+ align: 'right',
7
+ children: 'Content',
8
+ };
9
+
10
+ describe('DialogActions', () => {
11
+ it('renders without crashing', () => {
12
+ const { container } = render(<DialogActions {...initParams} />);
13
+
14
+ const dialogActionsContainer = container.children[0];
15
+ expect(dialogActionsContainer).toHaveClass('actions');
16
+ expect(dialogActionsContainer).toHaveTextContent(initParams.children as string);
17
+ });
18
+
19
+ it('should align items to left', () => {
20
+ const { container } = render(<DialogActions {...initParams} align="left" />);
21
+
22
+ const dialogActionsContainer = container.children[0];
23
+ expect(dialogActionsContainer).toHaveClass('actions', 'left');
24
+ });
25
+ });
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import {
3
+ BaseModalActions,
4
+ Props as BaseModalActionsProps,
5
+ } from '../../BaseModal/BaseModalActions/BaseModalActions';
6
+ import classes from './DialogActions.module.scss';
7
+
8
+ export interface Props extends BaseModalActionsProps {
9
+ align: 'left' | 'right';
10
+ }
11
+
12
+ export const DialogActions = ({ children, align, ...restProps }: Props) => {
13
+ return (
14
+ <BaseModalActions
15
+ {...restProps}
16
+ className={`${classes['actions']}${align === 'left' ? ' ' + classes['left'] : ''}`}
17
+ >
18
+ {children}
19
+ </BaseModalActions>
20
+ );
21
+ };
@@ -0,0 +1,7 @@
1
+ .header {
2
+ margin: 1.5rem 1.25rem 1.25rem;
3
+ }
4
+
5
+ .title {
6
+ margin: 0;
7
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { DialogTitle, Props } from './DialogTitle';
3
+ import { render, getByText } from '@testing-library/react';
4
+
5
+ const initParams: Props = {
6
+ id: 'dialog-label',
7
+ title: 'Example title',
8
+ };
9
+
10
+ describe('DialogActions', () => {
11
+ it('renders without crashing', () => {
12
+ const { container } = render(<DialogTitle {...initParams} />);
13
+
14
+ const dialogTitleContainer = container.children[0];
15
+ expect(dialogTitleContainer).toHaveClass('header');
16
+ expect(getByText(container, initParams.title));
17
+ });
18
+ });
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Typography } from '../../Typography/Typography';
3
+ import classes from './DialogTitle.module.scss';
4
+
5
+ export interface Props {
6
+ id: string;
7
+ title: string;
8
+ }
9
+
10
+ export const DialogTitle = ({ id, title }: Props) => {
11
+ return (
12
+ <header className={classes['header']}>
13
+ <Typography id={id} className={classes['title']} tag="h1" variant="h4">
14
+ {title}
15
+ </Typography>
16
+ </header>
17
+ );
18
+ };