@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,217 @@
1
+ import classes from './Select.module.scss';
2
+
3
+ import React, {
4
+ HTMLProps,
5
+ ReactElement,
6
+ useEffect,
7
+ useLayoutEffect,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+ import { Input } from '../Input/Input';
12
+ import { Icon, Icons } from '../../Icon/Icon';
13
+ import { FormElement } from '../form.interfaces';
14
+ import { useBodyClick } from '../../hooks/useBodyClick';
15
+ import { Position } from '../../hooks/usePosition';
16
+ import { useScroll } from '../../hooks/useScroll';
17
+
18
+ export interface Props extends FormElement<HTMLSelectElement> {
19
+ children: ReactElement[];
20
+ name?: string;
21
+ labeledBy?: string;
22
+ describedBy?: string;
23
+ placeholder?: string;
24
+ searchPlaceholder?: string;
25
+ className?: string;
26
+ value?: string;
27
+ onChange?: (event: React.ChangeEvent<HTMLSelectElement>, child?: ReactElement) => void;
28
+ onClear?: (event: React.MouseEvent<HTMLDivElement>) => void;
29
+ }
30
+
31
+ export const Select = ({
32
+ children,
33
+ name,
34
+ disabled = false,
35
+ labeledBy,
36
+ placeholder,
37
+ describedBy,
38
+ searchPlaceholder = 'Search item',
39
+ className,
40
+ error = false,
41
+ value = '',
42
+ onChange,
43
+ onClear,
44
+ ...rest
45
+ }: Props) => {
46
+ const [expanded, setExpanded] = useState(false);
47
+ const [filter, setFilter] = useState('');
48
+ const [display, setDisplay] = useState('');
49
+ const [listPosition, setListPosition] = useState<Partial<Position>>({});
50
+ const containerReference = useRef<HTMLDivElement>(null);
51
+ const optionListReference = useRef<HTMLDivElement>(null);
52
+
53
+ useBodyClick(
54
+ (event: MouseEvent) => !(event.target as Element).closest('.custom-select') && expanded,
55
+ () => {
56
+ setExpanded(!expanded);
57
+ },
58
+ expanded
59
+ );
60
+
61
+ const rePositionList = () => {
62
+ if (!expanded || !optionListReference.current || !containerReference.current) {
63
+ return;
64
+ }
65
+ const windowHeight = window.innerHeight;
66
+ const containerTopWithListHeight =
67
+ containerReference.current.getBoundingClientRect().bottom -
68
+ containerReference.current.getBoundingClientRect().height +
69
+ optionListReference.current.getBoundingClientRect().height;
70
+
71
+ if (containerTopWithListHeight > windowHeight) {
72
+ setListPosition({ top: 'initial', bottom: 0 });
73
+ } else {
74
+ setListPosition({ top: 0, bottom: 'initial' });
75
+ }
76
+ };
77
+
78
+ useScroll(rePositionList, [expanded]);
79
+
80
+ useLayoutEffect(() => {
81
+ rePositionList();
82
+ }, [expanded]);
83
+
84
+ const onOptionChangeHandler = (child: ReactElement) => (event: React.ChangeEvent) => {
85
+ /**
86
+ * We expose this to the outside inside of the onChange function as a parameter along with an optional second
87
+ * parameter of the option that was clicked.
88
+ */
89
+
90
+ setDisplay(child.props.children);
91
+
92
+ let newValue;
93
+ let multiple = false; // Potential support for future multiple select. This should be a prop obviously.
94
+
95
+ if (multiple) {
96
+ /** We will implement the mulitple select in the next iteration */
97
+ } else {
98
+ newValue = child.props.value;
99
+ }
100
+
101
+ if (onChange) {
102
+ // Redefine target to allow name and value to be read.
103
+ // This allows seamless integration with the most popular form libraries.
104
+ // Clone the event to not override `target` of the original event.
105
+ // Don't know how to fix this any.. compiler whines that it can't construct it otherwise.
106
+ const nativeEvent: any = event.nativeEvent || event;
107
+ const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
108
+
109
+ Object.defineProperty(clonedEvent as React.ChangeEvent<HTMLSelectElement>, 'target', {
110
+ writable: true,
111
+ value: { value: newValue },
112
+ });
113
+
114
+ onChange(clonedEvent as React.ChangeEvent<HTMLSelectElement>, child);
115
+ }
116
+
117
+ setDisplay((event.currentTarget as HTMLElement).innerText);
118
+ setExpanded(false);
119
+ };
120
+
121
+ /**
122
+ * @description We have to modify the children (Option component) to have a additional props that allows us to keep track of which one is selected at all times and if a filter is active.
123
+ * The `children` prop can be either a single object (1 child) or an array of multiple children.
124
+ */
125
+ const renderOptions = () =>
126
+ React.Children.map(children, (child) =>
127
+ React.cloneElement(child, {
128
+ onOptionSelect: onOptionChangeHandler(child),
129
+ selected: child.props.value === value,
130
+ filter: filter,
131
+ })
132
+ );
133
+
134
+ const renderSearch = () => (
135
+ <Input
136
+ autoFocus
137
+ onChange={filterResults}
138
+ className={classes['select-search']}
139
+ wrapperProps={{ className: classes['select-search-wrapper'] }}
140
+ type="text"
141
+ name="search-option"
142
+ placeholder={searchPlaceholder}
143
+ />
144
+ );
145
+
146
+ const filterResults = (event: React.ChangeEvent<HTMLInputElement>) => {
147
+ setFilter(event.currentTarget.value);
148
+ };
149
+
150
+ const statusIcon = () => {
151
+ if (error) {
152
+ return <Icon className={classes['warning']} icon={Icons.Warning} />;
153
+ }
154
+ if (value.length !== 0 && onClear) {
155
+ return <Icon tag="div" icon={Icons.TimesThin} onClick={onClear} />;
156
+ }
157
+ return null;
158
+ };
159
+
160
+ /** Set initial display value */
161
+ useEffect(() => {
162
+ for (let child of children) {
163
+ if (child.props.value === value) {
164
+ setDisplay(child.props.children);
165
+ }
166
+ }
167
+ }, []);
168
+
169
+ const additionalClasses = [];
170
+ expanded && additionalClasses.push(classes.expanded);
171
+ error && additionalClasses.push(classes.error);
172
+ disabled && additionalClasses.push(classes.disabled);
173
+
174
+ return (
175
+ <div
176
+ {...(rest as HTMLProps<HTMLDivElement>)}
177
+ ref={containerReference}
178
+ className={`custom-select ${classes.select} ${additionalClasses.join(' ')} ${
179
+ className ?? ''
180
+ }`}
181
+ >
182
+ <button
183
+ onClick={() => setExpanded(!expanded)}
184
+ type="button"
185
+ name={name}
186
+ disabled={disabled}
187
+ aria-disabled={disabled}
188
+ aria-invalid={error}
189
+ aria-expanded={expanded}
190
+ aria-haspopup="listbox"
191
+ aria-labelledby={labeledBy}
192
+ aria-describedby={describedBy}
193
+ >
194
+ <div data-display className={classes['selected']}>
195
+ {value.length === 0 && placeholder && (
196
+ <span className={classes['placeholder']}>{placeholder}</span>
197
+ )}
198
+ {value.length > 0 && <span>{display}</span>}
199
+ </div>
200
+ <div className={classes['status']}>
201
+ {statusIcon()}
202
+ <Icon className={classes['triangle-down']} icon={Icons.TriangleDown} />
203
+ </div>
204
+ </button>
205
+ <div
206
+ ref={optionListReference}
207
+ className={`list-wrapper ${classes['list-wrapper']}`}
208
+ style={{ display: expanded ? 'block' : 'none', ...listPosition }}
209
+ >
210
+ {Array.isArray(children) && children.length > 10 && renderSearch()}
211
+ <ul role="listbox" tabIndex={-1}>
212
+ {renderOptions()}
213
+ </ul>
214
+ </div>
215
+ </div>
216
+ );
217
+ };
@@ -0,0 +1,53 @@
1
+ .textarea-wrapper {
2
+ position: relative;
3
+ box-sizing: border-box;
4
+ width: 100%;
5
+ }
6
+
7
+ .textarea {
8
+ padding: 0.75rem 3.75rem 0.75rem 1.25rem;
9
+ border-color: var(--input-border-color);
10
+ border-style: var(--input-border-style);
11
+ border-width: var(--input-border-width);
12
+ border-radius: var(--input-border-radius);
13
+ transition: all 0.2s ease-in-out;
14
+ font-family: var(--font-family);
15
+ font-size: var(--font-size);
16
+ width: 100%;
17
+ box-sizing: border-box;
18
+ vertical-align: top;
19
+ resize: vertical;
20
+
21
+ &:disabled {
22
+ color: var(--greyed-out);
23
+ background-color: var(--disabled);
24
+ border-color: #fff;
25
+ cursor: not-allowed;
26
+ }
27
+
28
+ &:active:not(:disabled) {
29
+ border-color: var(--color-primary);
30
+ }
31
+
32
+ &:hover:not(:disabled) {
33
+ border-color: var(--default);
34
+ }
35
+
36
+ &:focus:not(:disabled) {
37
+ border-color: var(--color-primary);
38
+ outline: none;
39
+ }
40
+ }
41
+
42
+ .error {
43
+ border-color: var(--error);
44
+ color: var(--error);
45
+ }
46
+
47
+ .warning {
48
+ color: var(--error);
49
+ position: absolute;
50
+ right: 1.25rem;
51
+ top: 0.75rem;
52
+ font-size: 1.25rem;
53
+ }
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import { Textarea, Props } from './Textarea';
3
+ import { render } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const createTextarea = (params?: Props) => {
7
+ const queries = render(<Textarea data-testid="textarea" {...params} />);
8
+ const textarea = queries.getByRole('textbox');
9
+
10
+ return {
11
+ ...queries,
12
+ textarea,
13
+ };
14
+ };
15
+
16
+ describe('Textarea should render', () => {
17
+ it('renders without crashing', () => {
18
+ const { textarea } = createTextarea();
19
+ expect(textarea).toBeTruthy();
20
+ });
21
+ });
22
+
23
+ describe('Textarea properties', () => {
24
+ it('is disabled', () => {
25
+ const { textarea } = createTextarea({ disabled: true });
26
+
27
+ expect(textarea).toHaveAttribute('disabled');
28
+ });
29
+
30
+ it('has 20 columns', () => {
31
+ const { textarea } = createTextarea({ cols: 20 });
32
+
33
+ expect(textarea).toHaveAttribute('cols', '20');
34
+ });
35
+
36
+ it('has autofocus', () => {
37
+ const { textarea } = createTextarea({ autoFocus: true });
38
+
39
+ setTimeout(() => {
40
+ expect(textarea).toHaveAttribute('autofocus');
41
+ }, 0);
42
+ });
43
+ });
44
+
45
+ describe('Textarea listeners', () => {
46
+ it('executes the functions', () => {
47
+ const onChangeHandler = jest.fn();
48
+ const onKeyUpHandler = jest.fn();
49
+ const onKeyDownHandler = jest.fn();
50
+
51
+ const { textarea } = createTextarea({
52
+ onKeyUp: onKeyUpHandler,
53
+ onKeyDown: onKeyDownHandler,
54
+ onChange: onChangeHandler,
55
+ });
56
+
57
+ textarea.focus();
58
+
59
+ expect(textarea).toHaveFocus();
60
+
61
+ userEvent.keyboard('test');
62
+
63
+ expect(onKeyUpHandler).toHaveBeenCalled();
64
+ expect(onKeyDownHandler).toHaveBeenCalled();
65
+ expect(onChangeHandler).toHaveBeenCalled();
66
+ });
67
+ });
68
+
69
+ describe('Error status', () => {
70
+ it('has error class, and an icon', () => {
71
+ const { textarea } = createTextarea({ error: true });
72
+
73
+ expect(textarea).toHaveClass('error');
74
+ expect(textarea.nextElementSibling).toHaveClass('icon-warning');
75
+ });
76
+ });
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { Icon, Icons } from '../../Icon/Icon';
3
+ import { FormElement } from '../form.interfaces';
4
+ import classes from './Textarea.module.scss';
5
+
6
+ export interface Props extends FormElement<HTMLTextAreaElement> {
7
+ wrapperClassName?: string;
8
+ errorClassName?: string;
9
+ }
10
+
11
+ export const Textarea = ({
12
+ error = false,
13
+ disabled = false,
14
+ className,
15
+ rows = 4,
16
+ wrapperClassName,
17
+ errorClassName,
18
+ ...rest
19
+ }: Props) => {
20
+ return (
21
+ <div className={`${classes['textarea-wrapper']} ${wrapperClassName ? wrapperClassName : ''}`}>
22
+ <textarea
23
+ {...rest}
24
+ rows={rows}
25
+ className={`${error ? classes['error'] : ''} ${classes['textarea']} ${className ?? ''}`}
26
+ disabled={disabled}
27
+ />
28
+ {error && (
29
+ <Icon className={`${classes['warning']} ${errorClassName ?? ''}`} icon={Icons.Warning} />
30
+ )}
31
+ </div>
32
+ );
33
+ };
@@ -0,0 +1,58 @@
1
+ $borderRadius: 2.5rem;
2
+
3
+ .toggle-wrapper {
4
+ display: flex;
5
+ align-items: center;
6
+ position: relative;
7
+ }
8
+
9
+ .toggle {
10
+ width: 2rem;
11
+ height: 1.25rem;
12
+ background-color: var(--default);
13
+ border-radius: $borderRadius;
14
+ position: relative;
15
+ transition: background-color 0.2s ease-in-out;
16
+ display: block;
17
+ pointer-events: none;
18
+
19
+ &:before {
20
+ content: '';
21
+ width: 1rem;
22
+ height: 1rem;
23
+ border-radius: 50%;
24
+ display: block;
25
+ background-color: #fff;
26
+ position: absolute;
27
+ top: 50%;
28
+ transform: translateY(-50%) translateX(0);
29
+ left: 0.125rem;
30
+ transition: transform 0.2s ease-in-out;
31
+ }
32
+
33
+ &.checked {
34
+ background-color: var(--color-primary);
35
+ &:before {
36
+ transform: translateY(-50%) translateX(calc(100% - 0.25rem));
37
+ }
38
+ }
39
+ }
40
+
41
+ .checkbox {
42
+ z-index: 10;
43
+ position: static;
44
+
45
+ input {
46
+ width: 2rem;
47
+ }
48
+
49
+ input ~ [data-icon][aria-hidden] {
50
+ display: none;
51
+ }
52
+
53
+ input:focus-visible {
54
+ + * {
55
+ border-radius: $borderRadius;
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { Toggle } from './Toggle';
3
+ import { render } from '@testing-library/react';
4
+
5
+ describe('Toggle should render', () => {
6
+ it('renders without crashing', () => {
7
+ const { getByTestId } = render(
8
+ <Toggle name="toggle" data-testid="component">
9
+ Toggle
10
+ </Toggle>
11
+ );
12
+ const component = getByTestId('component');
13
+ expect(component).toBeDefined();
14
+ });
15
+ });
16
+
17
+ describe('Toggle attributes', () => {
18
+ it('should be checked', () => {
19
+ const { getByTestId } = render(
20
+ <Toggle checked={true} name="toggle" data-testid="component">
21
+ Toggle
22
+ </Toggle>
23
+ );
24
+
25
+ const component = getByTestId('component');
26
+
27
+ expect(component).toHaveAttribute('aria-checked', 'true');
28
+ });
29
+ });
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { Checkbox, CheckboxProps } from '../Checkbox/Checkbox';
3
+ import classes from './Toggle.module.scss';
4
+
5
+ export interface Props
6
+ extends Omit<CheckboxProps, 'indeterminate' | 'errorMessage' | 'error' | 'children'> {
7
+ children: string;
8
+ }
9
+
10
+ export const Toggle = ({ children, checked, ...rest }: Props) => (
11
+ <div className={classes['toggle-wrapper']}>
12
+ <Checkbox {...rest} checked={checked} className={classes.checkbox} label={children}>
13
+ <span
14
+ data-toggle
15
+ aria-hidden="true"
16
+ className={`${classes.toggle} ${checked ? classes.checked : ''} `}
17
+ ></span>
18
+ </Checkbox>
19
+ </div>
20
+ );
@@ -0,0 +1,12 @@
1
+ .checkbox-wrapper-helper {
2
+ margin-top: 1.25rem;
3
+ }
4
+
5
+ .checkbox-wrapper-error span {
6
+ font-size: 1rem;
7
+ }
8
+
9
+ .checkbox-wrapper-error [data-icon] {
10
+ font-size: 1.375rem;
11
+ vertical-align: bottom;
12
+ }
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { CheckboxWrapper, Props } from './CheckboxWrapper';
3
+ import { Checkbox, CheckboxProps } from '../../Checkbox/Checkbox';
4
+ import { render } from '@testing-library/react';
5
+
6
+ const defaultParentParams: CheckboxProps = {
7
+ indeterminate: false,
8
+ name: 'parentcheckbox',
9
+ label: 'testlabel',
10
+ children: [
11
+ <Checkbox checked={true} name="checkbox2">
12
+ Checkbox 2
13
+ </Checkbox>,
14
+ <Checkbox name="checkbox3">Checkbox 3</Checkbox>,
15
+ ],
16
+ };
17
+
18
+ const defaultParams: Props = {
19
+ onChange: jest.fn(),
20
+ errorMessage: 'This is an error',
21
+ error: false,
22
+ helperText: 'Helpertext',
23
+ name: 'Checkboxwrapper',
24
+ fieldsetProps: { title: 'Example title' },
25
+ label: 'Label',
26
+ children: [],
27
+ };
28
+
29
+ const createCheckboxWrapper = (
30
+ params?: (defaultParams: Props) => Props,
31
+ parentCheckboxParams?: (defaultParentParams: CheckboxProps) => CheckboxProps
32
+ ) => {
33
+ let parameters: Props = defaultParams;
34
+ let parentCheckboxParameters: CheckboxProps = defaultParentParams;
35
+
36
+ if (params) {
37
+ parameters = params(defaultParams);
38
+ }
39
+
40
+ if (parentCheckboxParams) {
41
+ parentCheckboxParameters = parentCheckboxParams(defaultParentParams);
42
+ }
43
+
44
+ const queries = render(
45
+ <CheckboxWrapper {...parameters} data-testid="checkboxwrapper">
46
+ <Checkbox data-testid="parentcheckbox" {...parentCheckboxParameters} />
47
+ </CheckboxWrapper>
48
+ );
49
+ const checkboxwrapper = queries.getByTestId('checkboxwrapper');
50
+ const parentcheckbox = queries.getByTestId('parentcheckbox');
51
+
52
+ return {
53
+ ...queries,
54
+ checkboxwrapper,
55
+ parentcheckbox,
56
+ };
57
+ };
58
+
59
+ describe('checkboxwrapper should render', () => {
60
+ it('renders without crashing', () => {
61
+ const { checkboxwrapper } = createCheckboxWrapper();
62
+ expect(checkboxwrapper).toBeTruthy();
63
+ });
64
+ });
65
+
66
+ describe('CheckboxWrapper should have an error', () => {
67
+ it('should have an error and the children checkboxes should have aria-describedby of the error message of the group.', () => {
68
+ const { checkboxwrapper } = createCheckboxWrapper((defaultParams) => ({
69
+ ...defaultParams,
70
+ error: true,
71
+ errorMessage: 'This is an error',
72
+ }));
73
+
74
+ const checkboxes = checkboxwrapper?.querySelectorAll('.checkbox-container input');
75
+ const errorMessage = checkboxwrapper?.querySelector('.default-helper .error-message .message');
76
+
77
+ /** All of the children checkboxes should be described by the error message in the parent checkbox group. */
78
+ checkboxes?.forEach((box) => {
79
+ expect(box.getAttribute('aria-describedby')).toBe(errorMessage?.getAttribute('id'));
80
+ });
81
+
82
+ expect(checkboxes?.length).toBe(3);
83
+ expect(errorMessage).toHaveTextContent('This is an error');
84
+ });
85
+ });
86
+
87
+ describe('Parent checkbox attributes', () => {
88
+ it('is indeterminate, label is set', () => {
89
+ const { parentcheckbox, container } = createCheckboxWrapper(undefined, (defaultParams) => ({
90
+ ...defaultParams,
91
+ indeterminate: true,
92
+ }));
93
+
94
+ expect(parentcheckbox.getAttribute('aria-checked')).toBe('mixed');
95
+ expect(
96
+ container.querySelector(`label[for="${parentcheckbox.getAttribute('id')}"]`)
97
+ ).toHaveTextContent('testlabel');
98
+ });
99
+ });
@@ -0,0 +1,62 @@
1
+ import React, { ReactElement, useEffect } from 'react';
2
+ import classes from './CheckboxWrapper.module.scss';
3
+ import { useWrapper } from '../../../hooks/useWrapper';
4
+ import { Wrapper, WrapperProps } from '../Wrapper/Wrapper';
5
+ import { Icons } from '../../../Icon/Icon';
6
+ import { Fieldset, Props as FieldsetProps } from '../../../Form/Fieldset/Fieldset';
7
+
8
+ export interface Props extends WrapperProps {
9
+ children: ReactElement[] | ReactElement;
10
+ fieldsetProps: FieldsetProps;
11
+ }
12
+
13
+ export const CheckboxWrapper = ({
14
+ children,
15
+ error = false,
16
+ helperText,
17
+ helperProps,
18
+ fieldsetProps,
19
+ ...rest
20
+ }: Props) => {
21
+ const { errorId, helperId } = useWrapper();
22
+
23
+ useEffect(() => {
24
+ if (fieldsetProps.title === undefined) {
25
+ console.error(
26
+ `You should give your Fieldset component a title prop so a legend element is rendered. This error was thrown in CheckboxWrapper. You can add this title prop through the fieldsetProps prop by passing an object (fieldsetProps={{ title: "title here" }})`
27
+ );
28
+ }
29
+ }, []);
30
+
31
+ const renderChildren = () =>
32
+ React.Children.map(children, (child) =>
33
+ React.cloneElement(child, {
34
+ parentErrorId: errorId,
35
+ error: error,
36
+ parentHelperId: helperText ? helperId : false,
37
+ disabled: rest.disabled,
38
+ })
39
+ );
40
+
41
+ return (
42
+ <Fieldset {...fieldsetProps}>
43
+ <Wrapper
44
+ {...rest}
45
+ label=""
46
+ helperId={helperId}
47
+ helperText={helperText}
48
+ helperProps={{
49
+ ...helperProps,
50
+ className: `${classes['checkbox-wrapper-helper']} ${
51
+ error ? classes['checkbox-wrapper-error'] : ''
52
+ } ${helperProps?.className ?? ''}`,
53
+ }}
54
+ error={error}
55
+ errorMessageIcon={Icons.Warning}
56
+ errorId={errorId}
57
+ >
58
+ {renderChildren()}
59
+ </Wrapper>
60
+ </Fieldset>
61
+ );
62
+ };
@@ -0,0 +1,24 @@
1
+ .input-label {
2
+ left: calc(
3
+ 1.25rem + 2px
4
+ ); // 2px is for offset of the textarea border. It's relative to the parent div which doesn't have a border.
5
+
6
+ &.focus {
7
+ color: var(--color-primary);
8
+ }
9
+ }
10
+
11
+ input {
12
+ &.floating-label {
13
+ padding-top: 0.625rem;
14
+ }
15
+ }
16
+
17
+ .input-wrapper-helper {
18
+ margin-top: 0.25rem;
19
+ }
20
+
21
+ .input-wrapper [data-icon]:before {
22
+ transform: translateY(5px);
23
+ font-size: 1.125rem;
24
+ }