@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,198 @@
1
+ import React, { useState } from 'react';
2
+ import { Wizard, Props } from './Wizard';
3
+ import {
4
+ getAllByRole,
5
+ getByText,
6
+ render,
7
+ getByTestId,
8
+ queryByRole,
9
+ findByText,
10
+ } from '@testing-library/react';
11
+ import { WizardActions } from './WizardActions/WizardActions';
12
+ import { WizardSteps } from './WizardSteps/WizardSteps';
13
+ import userEvent from '@testing-library/user-event';
14
+
15
+ type initWizardPropsType = Omit<Omit<Props, 'onStepChange'>, 'children'>;
16
+
17
+ const initWizardParams: initWizardPropsType = {
18
+ steps: [
19
+ {
20
+ label: 'Step 1',
21
+ },
22
+ {
23
+ label: 'Step 2',
24
+ },
25
+ {
26
+ label: 'Step 3',
27
+ disabled: true,
28
+ },
29
+ {
30
+ label: 'Step 4',
31
+ },
32
+ ],
33
+ initialStepNo: 0,
34
+ stepScreenReaderLabel: 'Step',
35
+ mode: 'add',
36
+ };
37
+
38
+ const initWizardStepsProps = {
39
+ onStepClick: jest.fn(),
40
+ };
41
+
42
+ const initWizardActionsProps = {
43
+ onNext: jest.fn(),
44
+ onCancel: jest.fn(),
45
+ onSaveAndClose: jest.fn(),
46
+ cancelButtonLabel: 'Cancel',
47
+ previousButtonLabel: 'Previous',
48
+ nextButtonLabel: 'Next',
49
+ saveAndCloseButtonLabel: 'Save & close',
50
+ };
51
+
52
+ const renderWizard = (wizardProps?: initWizardPropsType) => {
53
+ const customWizardProps = wizardProps ? wizardProps : initWizardParams;
54
+
55
+ const Content = () => {
56
+ const [step, setStep] = useState(customWizardProps.initialStepNo || 0);
57
+
58
+ return (
59
+ <Wizard {...customWizardProps} onStepChange={(stepNo: number) => setStep(stepNo)}>
60
+ <WizardSteps data-testid="wizard-steps" {...initWizardStepsProps} />
61
+ <div data-testid="wizard-content">Step {step + 1}</div>
62
+ <div data-testid="wizard-actions">
63
+ <WizardActions {...initWizardActionsProps} />
64
+ </div>
65
+ </Wizard>
66
+ );
67
+ };
68
+
69
+ return render(<Content />);
70
+ };
71
+
72
+ const getWizardContent = (container: HTMLElement) => getByTestId(container, 'wizard-content');
73
+
74
+ const getStepButtons = (container: HTMLElement) =>
75
+ getAllByRole(getByTestId(container, 'wizard-steps'), 'button');
76
+
77
+ const getActionsButtons = (container: HTMLElement) => {
78
+ const actionsContainer = getByTestId(container, 'wizard-actions');
79
+ const cancel = queryByRole(actionsContainer, 'button', {
80
+ name: initWizardActionsProps.cancelButtonLabel,
81
+ });
82
+ const next = queryByRole(actionsContainer, 'button', {
83
+ name: initWizardActionsProps.nextButtonLabel,
84
+ });
85
+ const prev = queryByRole(actionsContainer, 'button', {
86
+ name: initWizardActionsProps.previousButtonLabel,
87
+ });
88
+ const save = queryByRole(actionsContainer, 'button', {
89
+ name: initWizardActionsProps.saveAndCloseButtonLabel,
90
+ });
91
+ return { cancel, next, prev, save };
92
+ };
93
+
94
+ describe('Wizard', () => {
95
+ it('renders without crashing', () => {
96
+ const { container } = renderWizard();
97
+ const { cancel, prev, next, save } = getActionsButtons(container);
98
+ const stepButtons = getStepButtons(container);
99
+ const wizardContent = getWizardContent(container);
100
+
101
+ expect(stepButtons).toHaveLength(4);
102
+ expect(getByText(stepButtons[0], '1')).toBeDefined();
103
+ expect(getByText(stepButtons[1], '2')).toBeDefined();
104
+ expect(getByText(stepButtons[2], '3')).toBeDefined();
105
+ expect(getByText(stepButtons[3], '4')).toBeDefined();
106
+ expect(wizardContent).toHaveTextContent('Step 1');
107
+ expect(cancel).toBeDefined();
108
+ expect(next).toBeDefined();
109
+ expect(prev).toBeNull();
110
+ expect(save).toBeNull();
111
+ });
112
+
113
+ it('should be able to click over whole wizard via action buttons', async () => {
114
+ const { container } = renderWizard();
115
+ const { next } = getActionsButtons(container);
116
+ const wizardContent = getWizardContent(container);
117
+ (
118
+ initWizardActionsProps.onNext as jest.MockedFunction<typeof initWizardActionsProps.onNext>
119
+ ).mockReturnValue(true);
120
+
121
+ await findByText(wizardContent, 'Step 1');
122
+ next && userEvent.click(next);
123
+ await findByText(wizardContent, 'Step 2');
124
+ next && userEvent.click(next);
125
+ await findByText(wizardContent, 'Step 4');
126
+ const { save, prev, cancel } = getActionsButtons(container);
127
+ save && userEvent.click(save);
128
+ expect(initWizardActionsProps.onSaveAndClose).toBeCalledWith(3);
129
+ prev && userEvent.click(prev);
130
+ await findByText(wizardContent, 'Step 2');
131
+ prev && userEvent.click(prev);
132
+ await findByText(wizardContent, 'Step 1');
133
+ cancel && userEvent.click(cancel);
134
+ expect(initWizardActionsProps.onCancel).toBeCalledTimes(1);
135
+ });
136
+
137
+ it('should not be able to click over whole wizard via steps buttons in add mode', async () => {
138
+ const { container } = renderWizard();
139
+ const { next } = getActionsButtons(container);
140
+ const stepsButtons = getStepButtons(container);
141
+ const wizardContent = getWizardContent(container);
142
+ (
143
+ initWizardActionsProps.onNext as jest.MockedFunction<typeof initWizardActionsProps.onNext>
144
+ ).mockReturnValue(true);
145
+ (
146
+ initWizardStepsProps.onStepClick as jest.MockedFunction<
147
+ typeof initWizardStepsProps.onStepClick
148
+ >
149
+ ).mockReturnValue(true);
150
+
151
+ userEvent.click(stepsButtons[1]);
152
+ userEvent.click(stepsButtons[2]);
153
+ userEvent.click(stepsButtons[3]);
154
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(0);
155
+ await findByText(wizardContent, 'Step 1');
156
+
157
+ next && userEvent.click(next);
158
+ next && userEvent.click(next);
159
+ await findByText(wizardContent, 'Step 4');
160
+ userEvent.click(stepsButtons[2]);
161
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(0);
162
+ userEvent.click(stepsButtons[1]);
163
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(1);
164
+ await findByText(wizardContent, 'Step 2');
165
+ userEvent.click(stepsButtons[0]);
166
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(2);
167
+ await findByText(wizardContent, 'Step 1');
168
+ });
169
+
170
+ it('should be able to click over whole wizard via steps buttons in edit mode', async () => {
171
+ const { container } = renderWizard({ ...initWizardParams, mode: 'edit' });
172
+ const stepsButtons = getStepButtons(container);
173
+ const wizardContent = getWizardContent(container);
174
+ (
175
+ initWizardStepsProps.onStepClick as jest.MockedFunction<
176
+ typeof initWizardStepsProps.onStepClick
177
+ >
178
+ ).mockReturnValue(true);
179
+
180
+ userEvent.click(stepsButtons[1]);
181
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(1);
182
+ await findByText(wizardContent, 'Step 2');
183
+ userEvent.click(stepsButtons[2]);
184
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(1);
185
+ userEvent.click(stepsButtons[3]);
186
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(2);
187
+ await findByText(wizardContent, 'Step 4');
188
+
189
+ userEvent.click(stepsButtons[2]);
190
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(2);
191
+ userEvent.click(stepsButtons[1]);
192
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(3);
193
+ await findByText(wizardContent, 'Step 2');
194
+ userEvent.click(stepsButtons[0]);
195
+ expect(initWizardStepsProps.onStepClick).toBeCalledTimes(4);
196
+ await findByText(wizardContent, 'Step 1');
197
+ });
198
+ });
@@ -0,0 +1,49 @@
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { Step } from './BaseWizardSteps/BaseWizardSteps';
3
+ import { WizardStateContext, WizardStateProvider } from './WizardStateProvider';
4
+ import { setWizardState } from './wizardStateReducer';
5
+
6
+ export type WizardMode = 'add' | 'edit';
7
+
8
+ export interface Props {
9
+ steps: Step[];
10
+ mode: WizardMode;
11
+ initialStepNo?: number;
12
+ onStepChange: (stepNo: number) => void;
13
+ stepScreenReaderLabel: string;
14
+ children: React.ReactNode;
15
+ }
16
+
17
+ const useSetWizardState = (steps: Step[], mode: WizardMode, stepScreenReaderLabel: string) => {
18
+ const { dispatch } = useContext(WizardStateContext);
19
+
20
+ useEffect(() => {
21
+ dispatch(setWizardState(steps, mode, stepScreenReaderLabel));
22
+ }, [steps, mode, stepScreenReaderLabel]);
23
+ };
24
+
25
+ const useStepChanging = (onStepChange: (stepNo: number) => void) => {
26
+ const {
27
+ state: { currentStepNo },
28
+ } = useContext(WizardStateContext);
29
+
30
+ useEffect(() => {
31
+ onStepChange(currentStepNo);
32
+ }, [currentStepNo]);
33
+ };
34
+
35
+ const WizardContent = ({ steps, mode, stepScreenReaderLabel, onStepChange, children }: Props) => {
36
+ useSetWizardState(steps, mode, stepScreenReaderLabel);
37
+ useStepChanging(onStepChange);
38
+
39
+ return <>{children}</>;
40
+ };
41
+
42
+ export const Wizard = (props: Props) => {
43
+ const { steps, initialStepNo: currentStepNo = 0, mode, stepScreenReaderLabel } = props;
44
+ return (
45
+ <WizardStateProvider initialState={{ steps, currentStepNo, mode, stepScreenReaderLabel }}>
46
+ <WizardContent {...props} />
47
+ </WizardStateProvider>
48
+ );
49
+ };
@@ -0,0 +1,168 @@
1
+ import React, { useReducer } from 'react';
2
+ import { WizardActions, Props } from './WizardActions';
3
+ import { queryByRole, render } from '@testing-library/react';
4
+ import { WizardStateContext, Props as ProviderProps } from '../WizardStateProvider';
5
+ import { WizardStateType, reducer, changeCurrentStepNo } from '../wizardStateReducer';
6
+ import userEvent from '@testing-library/user-event';
7
+
8
+ const initWizardState: WizardStateType = {
9
+ steps: [
10
+ {
11
+ label: 'Step 1',
12
+ },
13
+ {
14
+ label: 'Step 2',
15
+ },
16
+ {
17
+ label: 'Step 3',
18
+ disabled: true,
19
+ },
20
+ {
21
+ label: 'Step 4',
22
+ },
23
+ ],
24
+ currentStepNo: 0,
25
+ stepScreenReaderLabel: 'Step',
26
+ mode: 'add',
27
+ };
28
+
29
+ const initParams: Props = {
30
+ cancelButtonLabel: 'Cancel',
31
+ previousButtonLabel: 'Previous',
32
+ nextButtonLabel: 'Next',
33
+ saveAndCloseButtonLabel: 'Save & Close',
34
+ onCancel: jest.fn(),
35
+ onNext: jest.fn(),
36
+ onPrevious: jest.fn(),
37
+ onSaveAndClose: jest.fn(),
38
+ };
39
+
40
+ const renderWizardActions = (initReducerState?: WizardStateType, props?: Props) => {
41
+ const wizardState = initReducerState ? initReducerState : initWizardState;
42
+ const actionsProps = props ? props : initParams;
43
+ const dispatch = jest.fn();
44
+
45
+ const Provider = ({ children }: ProviderProps) => {
46
+ const [state] = useReducer(reducer, wizardState);
47
+ return (
48
+ <WizardStateContext.Provider value={{ state, dispatch }}>
49
+ {children}
50
+ </WizardStateContext.Provider>
51
+ );
52
+ };
53
+
54
+ const renderObj = render(
55
+ <Provider initialState={initWizardState}>
56
+ <WizardActions {...actionsProps} />
57
+ </Provider>
58
+ );
59
+
60
+ return {
61
+ ...renderObj,
62
+ dispatch,
63
+ };
64
+ };
65
+
66
+ const getActionsButtons = (container: HTMLElement) => {
67
+ const cancel = queryByRole(container, 'button', { name: initParams.cancelButtonLabel });
68
+ const next = queryByRole(container, 'button', { name: initParams.nextButtonLabel });
69
+ const prev = queryByRole(container, 'button', { name: initParams.previousButtonLabel });
70
+ const save = queryByRole(container, 'button', { name: initParams.saveAndCloseButtonLabel });
71
+ return { cancel, next, prev, save };
72
+ };
73
+
74
+ describe('WizardActions', () => {
75
+ it('renders without crashing', () => {
76
+ const { container, dispatch } = renderWizardActions();
77
+ const { cancel, prev, next, save } = getActionsButtons(container);
78
+ (initParams.onNext as jest.MockedFunction<typeof initParams.onNext>).mockReturnValueOnce(true);
79
+
80
+ expect(cancel).toBeDefined();
81
+ cancel && userEvent.click(cancel);
82
+ expect(next).toBeDefined();
83
+ next && userEvent.click(next);
84
+ expect(prev).toBeNull();
85
+ expect(save).toBeNull();
86
+
87
+ expect(initParams.onCancel).toBeCalledTimes(1);
88
+ expect(initParams.onNext).toHaveBeenCalledWith(0);
89
+ expect(dispatch).toBeCalledWith(changeCurrentStepNo(1));
90
+ });
91
+
92
+ it('should allow going prev and forward when there are prev step and next step (next step is disabled but next one can be used)', () => {
93
+ const { container, dispatch } = renderWizardActions({ ...initWizardState, currentStepNo: 1 });
94
+ const { cancel, prev, next, save } = getActionsButtons(container);
95
+ (initParams.onNext as jest.MockedFunction<typeof initParams.onNext>).mockReturnValueOnce(true);
96
+
97
+ expect(prev).toBeDefined();
98
+ prev && userEvent.click(prev);
99
+ expect(initParams.onPrevious).toBeCalled();
100
+ expect(dispatch).toBeCalledWith(changeCurrentStepNo(0));
101
+
102
+ expect(next).toBeDefined();
103
+ next && userEvent.click(next);
104
+ expect(initParams.onNext).toHaveBeenCalledWith(1);
105
+ expect(dispatch).toBeCalledWith(changeCurrentStepNo(3));
106
+
107
+ expect(cancel).toBeDefined();
108
+ expect(save).toBeNull();
109
+ });
110
+
111
+ it('should render save button but not next button when current step is the last step', () => {
112
+ const { container } = renderWizardActions({ ...initWizardState, currentStepNo: 3 });
113
+ const { cancel, prev, next, save } = getActionsButtons(container);
114
+
115
+ expect(cancel).toBeDefined();
116
+ expect(prev).toBeDefined();
117
+ expect(next).toBeNull();
118
+ expect(save).toBeDefined();
119
+
120
+ save && userEvent.click(save);
121
+ expect(initParams.onSaveAndClose).toHaveBeenCalledWith(3);
122
+ });
123
+
124
+ it('should show save and close when next steps are disabled', () => {
125
+ const steps = [
126
+ initWizardState.steps[0],
127
+ { ...initWizardState.steps[1], disabled: true },
128
+ { ...initWizardState.steps[2], disabled: true },
129
+ ];
130
+ const { container } = renderWizardActions({
131
+ ...initWizardState,
132
+ steps: steps,
133
+ currentStepNo: 0,
134
+ });
135
+ const { cancel, prev, next, save } = getActionsButtons(container);
136
+
137
+ expect(cancel).toBeDefined();
138
+ expect(prev).toBeNull();
139
+ expect(next).toBeNull();
140
+ expect(save).toBeDefined();
141
+
142
+ save && userEvent.click(save);
143
+ expect(initParams.onSaveAndClose).toHaveBeenCalledWith(0);
144
+ });
145
+
146
+ it('should show save button on middle step when mode is `edit`', () => {
147
+ const { container } = renderWizardActions({ ...initWizardState, mode: 'edit' });
148
+ const { cancel, prev, next, save } = getActionsButtons(container);
149
+
150
+ expect(cancel).toBeDefined();
151
+ expect(prev).toBeNull();
152
+ expect(next).toBeDefined();
153
+ expect(save).toBeDefined();
154
+
155
+ save && userEvent.click(save);
156
+ expect(initParams.onSaveAndClose).toHaveBeenCalledWith(0);
157
+ });
158
+
159
+ it('should not allow going forward when dev returns false on onNext callback', () => {
160
+ const { container, dispatch } = renderWizardActions();
161
+ const { next } = getActionsButtons(container);
162
+ (initParams.onNext as jest.MockedFunction<typeof initParams.onNext>).mockReturnValueOnce(false);
163
+
164
+ next && userEvent.click(next);
165
+ expect(initParams.onNext).toHaveBeenCalledWith(0);
166
+ expect(dispatch).not.toBeCalled();
167
+ });
168
+ });
@@ -0,0 +1,100 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { Button } from '../../Button/Button';
3
+ import { Step } from '../BaseWizardSteps/BaseWizardSteps';
4
+ import { WizardMode } from '../Wizard';
5
+ import { WizardStateContext } from '../WizardStateProvider';
6
+ import { changeCurrentStepNo } from '../wizardStateReducer';
7
+
8
+ export interface Props extends React.HTMLProps<HTMLDivElement> {
9
+ cancelButtonLabel: string;
10
+ previousButtonLabel: string;
11
+ nextButtonLabel: string;
12
+ saveAndCloseButtonLabel: string;
13
+ onCancel: () => void;
14
+ onNext: (currentStepNo: number) => boolean;
15
+ onPrevious?: () => void;
16
+ onSaveAndClose: (currentStepNo: number) => void;
17
+ }
18
+
19
+ const calculateNextStepNo = (steps: Step[], currentStepNo: number) => () =>
20
+ steps.findIndex((step, stepNo) => stepNo > currentStepNo && !step.disabled);
21
+
22
+ const calculatePrevStepNo = (steps: Step[], currentStepNo: number) => () => {
23
+ const reversedCurrentStepNo = steps.length - 1 - currentStepNo;
24
+ const reversedPrevStepNo = [...steps]
25
+ .reverse()
26
+ .findIndex((step, stepNo) => stepNo > reversedCurrentStepNo && !step.disabled);
27
+ if (reversedPrevStepNo > 0) {
28
+ return steps.length - 1 - reversedPrevStepNo;
29
+ }
30
+ return -1;
31
+ };
32
+
33
+ const useNextStepNo = (mode: WizardMode, currentStepNo: number, steps: Step[]) =>
34
+ useMemo(calculateNextStepNo(steps, currentStepNo), [mode, currentStepNo, steps]);
35
+
36
+ const usePreviousStepNo = (mode: WizardMode, currentStepNo: number, steps: Step[]) =>
37
+ useMemo(calculatePrevStepNo(steps, currentStepNo), [mode, currentStepNo, steps]);
38
+
39
+ export const WizardActions = ({
40
+ onCancel,
41
+ onNext,
42
+ onPrevious,
43
+ onSaveAndClose,
44
+ cancelButtonLabel,
45
+ previousButtonLabel,
46
+ nextButtonLabel,
47
+ saveAndCloseButtonLabel,
48
+ }: Props) => {
49
+ const {
50
+ state: { mode, steps, currentStepNo },
51
+ dispatch,
52
+ } = useContext(WizardStateContext);
53
+ const nextStepNo = useNextStepNo(mode, currentStepNo, steps);
54
+ const hasNextStep = nextStepNo !== -1;
55
+ const previousStepNo = usePreviousStepNo(mode, currentStepNo, steps);
56
+ const hasPreviousStep = previousStepNo !== -1;
57
+ const isLastStepOrEditMode = !hasNextStep || mode === 'edit';
58
+
59
+ const changeStepNo = (direction: 'forward' | 'backward') => {
60
+ if (direction === 'forward') {
61
+ hasNextStep && dispatch(changeCurrentStepNo(nextStepNo));
62
+ } else {
63
+ hasPreviousStep && dispatch(changeCurrentStepNo(previousStepNo));
64
+ }
65
+ };
66
+
67
+ const onNextWrapper = () => {
68
+ onNext(currentStepNo) && changeStepNo('forward');
69
+ };
70
+
71
+ const onPreviousWrapper = () => {
72
+ onPrevious && onPrevious();
73
+ changeStepNo('backward');
74
+ };
75
+
76
+ const onSaveAndCloseWrapper = () => {
77
+ onSaveAndClose(currentStepNo);
78
+ };
79
+
80
+ return (
81
+ <>
82
+ <Button variant="text" onClick={onCancel}>
83
+ {cancelButtonLabel}
84
+ </Button>
85
+ {hasPreviousStep && (
86
+ <Button variant="outline" onClick={onPreviousWrapper}>
87
+ {previousButtonLabel}
88
+ </Button>
89
+ )}
90
+ {hasNextStep && (
91
+ <Button variant={mode === 'edit' ? 'outline' : 'fill'} onClick={onNextWrapper}>
92
+ {nextButtonLabel}
93
+ </Button>
94
+ )}
95
+ {isLastStepOrEditMode && (
96
+ <Button onClick={onSaveAndCloseWrapper}>{saveAndCloseButtonLabel}</Button>
97
+ )}
98
+ </>
99
+ );
100
+ };
@@ -0,0 +1,26 @@
1
+ import React, { createContext, Dispatch, useReducer } from 'react';
2
+ import { WizardActions, WizardStateType, reducer } from './wizardStateReducer';
3
+
4
+ const WizardStateContext = createContext<{
5
+ state: WizardStateType;
6
+ dispatch: Dispatch<WizardActions>;
7
+ }>({
8
+ state: {} as WizardStateType,
9
+ dispatch: () => null,
10
+ });
11
+
12
+ export interface Props {
13
+ initialState: WizardStateType;
14
+ children?: React.ReactNode;
15
+ }
16
+
17
+ const WizardStateProvider = ({ children, initialState }: Props) => {
18
+ const [state, dispatch] = useReducer(reducer, initialState);
19
+ return (
20
+ <WizardStateContext.Provider value={{ state, dispatch }}>
21
+ {children}
22
+ </WizardStateContext.Provider>
23
+ );
24
+ };
25
+
26
+ export { WizardStateProvider, WizardStateContext };
@@ -0,0 +1,110 @@
1
+ import React, { useReducer } from 'react';
2
+ import { WizardSteps, Props } from './WizardSteps';
3
+ import { getAllByRole, getByText, render } from '@testing-library/react';
4
+ import { WizardStateContext, Props as ProviderProps } from '../WizardStateProvider';
5
+ import { WizardStateType, reducer, changeCurrentStepNo } from '../wizardStateReducer';
6
+ import userEvent from '@testing-library/user-event';
7
+
8
+ const initWizardState: WizardStateType = {
9
+ steps: [
10
+ {
11
+ label: 'Step 1',
12
+ },
13
+ {
14
+ label: 'Step 2',
15
+ },
16
+ {
17
+ label: 'Step 3',
18
+ disabled: true,
19
+ },
20
+ {
21
+ label: 'Step 4',
22
+ },
23
+ ],
24
+ currentStepNo: 1,
25
+ stepScreenReaderLabel: 'Step',
26
+ mode: 'add',
27
+ };
28
+
29
+ const initParams: Props = {
30
+ onStepClick: jest.fn(),
31
+ };
32
+
33
+ const renderWizardSteps = (initReducerState?: WizardStateType) => {
34
+ const wizardState = initReducerState ? initReducerState : initWizardState;
35
+ const dispatch = jest.fn();
36
+
37
+ const Provider = ({ children }: ProviderProps) => {
38
+ const [state] = useReducer(reducer, wizardState);
39
+ return (
40
+ <WizardStateContext.Provider value={{ state, dispatch }}>
41
+ {children}
42
+ </WizardStateContext.Provider>
43
+ );
44
+ };
45
+
46
+ const renderObj = render(
47
+ <Provider initialState={initWizardState}>
48
+ <WizardSteps {...initParams} />
49
+ </Provider>
50
+ );
51
+
52
+ return {
53
+ ...renderObj,
54
+ dispatch,
55
+ };
56
+ };
57
+
58
+ const getStepButtons = (container: HTMLElement) => getAllByRole(container, 'button');
59
+
60
+ describe('WizardSteps', () => {
61
+ it('renders without crashing', () => {
62
+ const { container, dispatch } = renderWizardSteps();
63
+ const buttons = getStepButtons(container);
64
+ (
65
+ initParams.onStepClick as jest.MockedFunction<typeof initParams.onStepClick>
66
+ ).mockReturnValueOnce(true);
67
+
68
+ expect(buttons).toHaveLength(4);
69
+ userEvent.click(buttons[0]);
70
+ expect(initParams.onStepClick).toHaveBeenCalledWith(initWizardState.currentStepNo, 0);
71
+ expect(dispatch).toBeCalledWith(changeCurrentStepNo(0));
72
+ expect(buttons[0].querySelector('.checkmark')).toBeDefined();
73
+ expect(getByText(buttons[1], '2')).toBeDefined();
74
+ expect(getByText(buttons[2], '3')).toBeDefined();
75
+ expect(getByText(buttons[3], '4')).toBeDefined();
76
+
77
+ userEvent.click(buttons[1]);
78
+ userEvent.click(buttons[2]);
79
+ userEvent.click(buttons[3]);
80
+ expect(initParams.onStepClick).toHaveBeenCalledTimes(1);
81
+ });
82
+
83
+ it('should allow to click on future and prev steps but not on current and disabled steps when mode is set to `edit`', () => {
84
+ const { container, dispatch } = renderWizardSteps({ ...initWizardState, mode: 'edit' });
85
+ const buttons = getStepButtons(container);
86
+ (
87
+ initParams.onStepClick as jest.MockedFunction<typeof initParams.onStepClick>
88
+ ).mockReturnValueOnce(true);
89
+
90
+ userEvent.click(buttons[3]);
91
+ expect(initParams.onStepClick).toHaveBeenCalledWith(initWizardState.currentStepNo, 3);
92
+ expect(dispatch).toBeCalledWith(changeCurrentStepNo(3));
93
+
94
+ userEvent.click(buttons[1]);
95
+ userEvent.click(buttons[2]);
96
+ expect(initParams.onStepClick).toHaveBeenCalledTimes(1);
97
+ });
98
+
99
+ it('should do not change step when `onStepClick` callback returns false', () => {
100
+ const { container, dispatch } = renderWizardSteps({ ...initWizardState, mode: 'edit' });
101
+ const buttons = getStepButtons(container);
102
+ (
103
+ initParams.onStepClick as jest.MockedFunction<typeof initParams.onStepClick>
104
+ ).mockReturnValueOnce(false);
105
+
106
+ userEvent.click(buttons[3]);
107
+ expect(initParams.onStepClick).toHaveBeenCalledWith(initWizardState.currentStepNo, 3);
108
+ expect(dispatch).not.toBeCalled();
109
+ });
110
+ });
@@ -0,0 +1,30 @@
1
+ import React, { useContext } from 'react';
2
+ import { WizardStateContext } from '../WizardStateProvider';
3
+ import { BaseWizardSteps } from '../BaseWizardSteps/BaseWizardSteps';
4
+ import { changeCurrentStepNo } from '../wizardStateReducer';
5
+
6
+ export interface Props extends Omit<React.HTMLProps<HTMLDivElement>, 'onClick'> {
7
+ onStepClick: (currentStepNo: number, selectedStepNo: number) => boolean;
8
+ }
9
+
10
+ export const WizardSteps = ({ onStepClick, ...restProps }: Props) => {
11
+ const {
12
+ state: { currentStepNo, mode, stepScreenReaderLabel, steps },
13
+ dispatch,
14
+ } = useContext(WizardStateContext);
15
+
16
+ const onClick = (selectedStepNo: number) => {
17
+ onStepClick(currentStepNo, selectedStepNo) && dispatch(changeCurrentStepNo(selectedStepNo));
18
+ };
19
+
20
+ return (
21
+ <BaseWizardSteps
22
+ {...restProps}
23
+ onClick={onClick}
24
+ steps={steps}
25
+ currentStepNo={currentStepNo}
26
+ stepScreenReaderLabel={stepScreenReaderLabel}
27
+ futureStepsClickable={mode === 'edit'}
28
+ />
29
+ );
30
+ };