@onewelcome/react-lib-components 0.1.1-alpha → 0.1.4-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 (180) hide show
  1. package/README.md +16 -1
  2. package/dist/Breadcrumbs/Breadcrumbs.d.ts +3 -3
  3. package/dist/Button/BaseButton.d.ts +3 -4
  4. package/dist/Button/Button.d.ts +3 -4
  5. package/dist/Button/IconButton.d.ts +3 -4
  6. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  7. package/dist/Form/Checkbox/Checkbox.d.ts +5 -5
  8. package/dist/Form/Fieldset/Fieldset.d.ts +4 -4
  9. package/dist/Form/FormControl/FormControl.d.ts +5 -5
  10. package/dist/Form/FormGroup/FormGroup.d.ts +4 -4
  11. package/dist/Form/FormHelperText/FormHelperText.d.ts +4 -5
  12. package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +8 -12
  13. package/dist/Form/Input/Input.d.ts +7 -6
  14. package/dist/Form/Label/Label.d.ts +4 -5
  15. package/dist/Form/Radio/Radio.d.ts +5 -5
  16. package/dist/Form/Select/Option.d.ts +3 -4
  17. package/dist/Form/Select/Select.d.ts +4 -4
  18. package/dist/Form/Textarea/Textarea.d.ts +9 -5
  19. package/dist/Form/Toggle/Toggle.d.ts +3 -3
  20. package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +4 -3
  21. package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +5 -5
  22. package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +4 -4
  23. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +7 -4
  24. package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +3 -3
  25. package/dist/Form/Wrapper/Wrapper/Wrapper.d.ts +6 -6
  26. package/dist/Form/form.interfaces.d.ts +4 -3
  27. package/dist/Icon/Icon.d.ts +4 -4
  28. package/dist/Link/Link.d.ts +4 -6
  29. package/dist/Notifications/BaseModal/BaseModal.d.ts +3 -4
  30. package/dist/Notifications/BaseModal/BaseModalActions/BaseModalActions.d.ts +3 -3
  31. package/dist/Notifications/BaseModal/BaseModalContent/BaseModalContent.d.ts +3 -3
  32. package/dist/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.d.ts +3 -3
  33. package/dist/Notifications/Dialog/Dialog.d.ts +3 -3
  34. package/dist/Notifications/Dialog/DialogActions/DialogActions.d.ts +3 -3
  35. package/dist/Notifications/Dialog/DialogTitle/DialogTitle.d.ts +3 -3
  36. package/dist/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.d.ts +5 -4
  37. package/dist/Notifications/DiscardChangesModal/DiscardChangesModal.d.ts +3 -1
  38. package/dist/Pagination/Pagination.d.ts +19 -0
  39. package/dist/Popover/Popover.d.ts +3 -3
  40. package/dist/Tabs/Tab.d.ts +11 -0
  41. package/dist/Tabs/TabButton.d.ts +10 -0
  42. package/dist/Tabs/TabPanel.d.ts +8 -0
  43. package/dist/Tabs/Tabs.d.ts +9 -0
  44. package/dist/TextEllipsis/TextEllipsis.d.ts +6 -0
  45. package/dist/Tiles/Tile.d.ts +3 -3
  46. package/dist/Tiles/Tiles.d.ts +3 -3
  47. package/dist/Tooltip/Tooltip.d.ts +3 -3
  48. package/dist/Typography/Typography.d.ts +6 -4
  49. package/dist/Wizard/BaseWizardSteps/BaseWizardSteps.d.ts +3 -3
  50. package/dist/Wizard/WizardSteps/WizardSteps.d.ts +3 -3
  51. package/dist/_BaseStyling_/BaseStyling.d.ts +9 -0
  52. package/dist/hooks/useRepeater.d.ts +10 -0
  53. package/dist/hooks/useSpacing.d.ts +2 -2
  54. package/dist/hooks/useWrapper.d.ts +1 -1
  55. package/dist/index.d.ts +6 -0
  56. package/dist/interfaces.d.ts +2 -11
  57. package/dist/react-lib-components.cjs.development.js +2395 -1696
  58. package/dist/react-lib-components.cjs.development.js.map +1 -1
  59. package/dist/react-lib-components.cjs.production.min.js +1 -1
  60. package/dist/react-lib-components.cjs.production.min.js.map +1 -1
  61. package/dist/react-lib-components.esm.js +2391 -1698
  62. package/dist/react-lib-components.esm.js.map +1 -1
  63. package/dist/util/helper.d.ts +6 -1
  64. package/package.json +30 -24
  65. package/src/Breadcrumbs/Breadcrumbs.tsx +39 -37
  66. package/src/Button/BaseButton.test.tsx +65 -19
  67. package/src/Button/BaseButton.tsx +2 -3
  68. package/src/Button/Button.test.tsx +63 -17
  69. package/src/Button/Button.tsx +15 -4
  70. package/src/Button/IconButton.test.tsx +57 -22
  71. package/src/Button/IconButton.tsx +14 -9
  72. package/src/ContextMenu/ContextMenu.test.tsx +27 -1
  73. package/src/ContextMenu/ContextMenu.tsx +70 -65
  74. package/src/Form/Checkbox/Checkbox.test.tsx +28 -2
  75. package/src/Form/Checkbox/Checkbox.tsx +132 -122
  76. package/src/Form/Fieldset/Fieldset.test.tsx +28 -2
  77. package/src/Form/Fieldset/Fieldset.tsx +96 -50
  78. package/src/Form/FormControl/FormControl.test.tsx +27 -1
  79. package/src/Form/FormControl/FormControl.tsx +36 -39
  80. package/src/Form/FormGroup/FormGroup.test.tsx +51 -1
  81. package/src/Form/FormGroup/FormGroup.tsx +64 -58
  82. package/src/Form/FormHelperText/FormHelperText.test.tsx +27 -1
  83. package/src/Form/FormHelperText/FormHelperText.tsx +20 -16
  84. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.test.tsx +78 -0
  85. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.tsx +61 -55
  86. package/src/Form/Input/Input.module.scss +34 -15
  87. package/src/Form/Input/Input.test.tsx +27 -1
  88. package/src/Form/Input/Input.tsx +88 -47
  89. package/src/Form/Label/Label.test.tsx +27 -1
  90. package/src/Form/Label/Label.tsx +18 -14
  91. package/src/Form/Radio/Radio.test.tsx +28 -2
  92. package/src/Form/Radio/Radio.tsx +98 -90
  93. package/src/Form/Select/Option.test.tsx +27 -1
  94. package/src/Form/Select/Option.tsx +49 -42
  95. package/src/Form/Select/Select.module.scss +5 -1
  96. package/src/Form/Select/Select.test.tsx +224 -30
  97. package/src/Form/Select/Select.tsx +248 -182
  98. package/src/Form/Textarea/Textarea.module.scss +2 -1
  99. package/src/Form/Textarea/Textarea.test.tsx +28 -2
  100. package/src/Form/Textarea/Textarea.tsx +44 -29
  101. package/src/Form/Toggle/Toggle.module.scss +9 -0
  102. package/src/Form/Toggle/Toggle.test.tsx +27 -1
  103. package/src/Form/Toggle/Toggle.tsx +25 -12
  104. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +27 -1
  105. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.tsx +45 -48
  106. package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +17 -1
  107. package/src/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +89 -1
  108. package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +134 -74
  109. package/src/Form/Wrapper/RadioWrapper/RadioWrapper.tsx +64 -59
  110. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.module.scss +1 -1
  111. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +43 -1
  112. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +54 -44
  113. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +5 -7
  114. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.test.tsx +43 -1
  115. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +100 -85
  116. package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +1 -1
  117. package/src/Form/Wrapper/Wrapper/Wrapper.test.tsx +27 -1
  118. package/src/Form/Wrapper/Wrapper/Wrapper.tsx +76 -71
  119. package/src/Form/form.interfaces.ts +4 -3
  120. package/src/Icon/Icon.module.scss +4 -0
  121. package/src/Icon/Icon.test.tsx +30 -2
  122. package/src/Icon/Icon.tsx +5 -5
  123. package/src/Link/Link.test.tsx +27 -1
  124. package/src/Link/Link.tsx +10 -7
  125. package/src/Notifications/BaseModal/BaseModal.test.tsx +27 -1
  126. package/src/Notifications/BaseModal/BaseModal.tsx +59 -54
  127. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.test.tsx +26 -1
  128. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.tsx +11 -9
  129. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.test.tsx +27 -1
  130. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.tsx +27 -26
  131. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +29 -1
  132. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.tsx +18 -16
  133. package/src/Notifications/Dialog/Dialog.test.tsx +39 -1
  134. package/src/Notifications/Dialog/Dialog.tsx +84 -78
  135. package/src/Notifications/Dialog/DialogActions/DialogActions.test.tsx +27 -1
  136. package/src/Notifications/Dialog/DialogActions/DialogActions.tsx +15 -12
  137. package/src/Notifications/Dialog/DialogTitle/DialogTitle.test.tsx +28 -2
  138. package/src/Notifications/Dialog/DialogTitle/DialogTitle.tsx +13 -11
  139. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.test.tsx +41 -1
  140. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.tsx +43 -36
  141. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.test.tsx +52 -1
  142. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.tsx +8 -3
  143. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +1 -1
  144. package/src/Pagination/Pagination.module.scss +120 -0
  145. package/src/Pagination/Pagination.test.tsx +176 -0
  146. package/src/Pagination/Pagination.tsx +205 -0
  147. package/src/Popover/Popover.tsx +3 -3
  148. package/src/Tabs/Tab.test.tsx +71 -0
  149. package/src/Tabs/Tab.tsx +17 -0
  150. package/src/Tabs/TabButton.module.scss +36 -0
  151. package/src/Tabs/TabButton.test.tsx +77 -0
  152. package/src/Tabs/TabButton.tsx +58 -0
  153. package/src/Tabs/TabPanel.module.scss +7 -0
  154. package/src/Tabs/TabPanel.test.tsx +76 -0
  155. package/src/Tabs/TabPanel.tsx +27 -0
  156. package/src/Tabs/Tabs.module.scss +41 -0
  157. package/src/Tabs/Tabs.test.tsx +268 -0
  158. package/src/Tabs/Tabs.tsx +149 -0
  159. package/src/TextEllipsis/TextEllipsis.module.scss +18 -0
  160. package/src/TextEllipsis/TextEllipsis.test.tsx +80 -0
  161. package/src/TextEllipsis/TextEllipsis.tsx +55 -0
  162. package/src/Tiles/Tile.test.tsx +27 -1
  163. package/src/Tiles/Tile.tsx +59 -62
  164. package/src/Tiles/Tiles.test.tsx +27 -1
  165. package/src/Tiles/Tiles.tsx +42 -39
  166. package/src/Tooltip/Tooltip.test.tsx +27 -1
  167. package/src/Tooltip/Tooltip.tsx +104 -92
  168. package/src/Typography/Typography.test.tsx +27 -1
  169. package/src/Typography/Typography.tsx +66 -68
  170. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.tsx +67 -62
  171. package/src/Wizard/WizardSteps/WizardSteps.tsx +24 -21
  172. package/src/_BaseStyling_/BaseStyling.tsx +19 -1
  173. package/src/hooks/useRepeater.test.tsx +139 -0
  174. package/src/hooks/useRepeater.ts +34 -0
  175. package/src/hooks/useSpacing.ts +1 -1
  176. package/src/hooks/useWrapper.ts +7 -2
  177. package/src/index.ts +15 -1
  178. package/src/interfaces.ts +2 -12
  179. package/src/util/helper.test.tsx +38 -1
  180. package/src/util/helper.tsx +21 -0
@@ -1,108 +1,116 @@
1
- import React from 'react';
1
+ import React, { ComponentPropsWithRef } from 'react';
2
2
  import { Icon, Icons } from '../../Icon/Icon';
3
3
  import { Props as HelperProps } from '../FormHelperText/FormHelperText';
4
4
  import classes from './Radio.module.scss';
5
5
  import { useFormSelector } from '../../hooks/useFormSelector';
6
6
  import { FormSelector } from '../form.interfaces';
7
- import { HTMLProps } from '../../interfaces';
8
- import { FormSelectorWrapper } from '../FormSelectorWrapper/FormSelectorWrapper';
7
+ import {
8
+ FormSelectorWrapper,
9
+ Props as FormSelectorWrapperProps,
10
+ } from '../FormSelectorWrapper/FormSelectorWrapper';
9
11
 
10
- export interface Props extends FormSelector<HTMLInputElement> {
12
+ export interface Props extends ComponentPropsWithRef<'input'>, FormSelector {
11
13
  children: string;
12
14
  value: string;
13
- wrapperProps?: HTMLProps<HTMLDivElement>;
15
+ formSelectorWrapperProps?: FormSelectorWrapperProps;
14
16
  helperProps?: HelperProps;
15
17
  }
16
18
 
17
- export const Radio = ({
18
- children,
19
- disabled,
20
- className,
21
- value,
22
- name,
23
- helperText,
24
- parentErrorId,
25
- parentHelperId,
26
- error,
27
- errorMessage,
28
- checked = false,
29
- wrapperProps,
30
- helperProps,
31
- onChange,
32
- ...rest
33
- }: Props) => {
34
- const { errorId, identifier, describedBy } = useFormSelector({
35
- name,
36
- helperText,
37
- parentErrorId,
38
- errorMessage,
39
- error,
40
- parentHelperId,
41
- });
19
+ export const Radio = React.forwardRef<HTMLInputElement, Props>(
20
+ (
21
+ {
22
+ children,
23
+ disabled,
24
+ className,
25
+ value,
26
+ name,
27
+ helperText,
28
+ parentErrorId,
29
+ parentHelperId,
30
+ error,
31
+ errorMessage,
32
+ checked = false,
33
+ formSelectorWrapperProps,
34
+ helperProps,
35
+ onChange,
36
+ ...rest
37
+ }: Props,
38
+ ref
39
+ ) => {
40
+ const { errorId, identifier, describedBy } = useFormSelector({
41
+ name,
42
+ helperText,
43
+ parentErrorId,
44
+ errorMessage,
45
+ error,
46
+ parentHelperId,
47
+ });
42
48
 
43
- const onChangeHandler = (event: React.ChangeEvent<HTMLInputElement> | React.MouseEvent) => {
44
- if (disabled) {
45
- return;
46
- }
47
- /** We have to clone the native event we got here and change the "target" property to the value. Otherwise, this regular event has "on" as it's event.target.value, which is useless. */
48
- const nativeEvent: any = event.nativeEvent || event;
49
- const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
49
+ const onChangeHandler = (event: React.ChangeEvent<HTMLInputElement> | React.MouseEvent) => {
50
+ if (disabled) {
51
+ return;
52
+ }
53
+ /** We have to clone the native event we got here and change the "target" property to the value. Otherwise, this regular event has "on" as it's event.target.value, which is useless. */
54
+ const nativeEvent: any = event.nativeEvent || event;
55
+ const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
50
56
 
51
- Object.defineProperty(clonedEvent, 'target', {
52
- writable: true,
53
- value: { value: value },
54
- });
57
+ Object.defineProperty(clonedEvent, 'target', {
58
+ writable: true,
59
+ value: { value: value },
60
+ });
55
61
 
56
- onChange && onChange(clonedEvent);
57
- };
62
+ onChange && onChange(clonedEvent);
63
+ };
58
64
 
59
- /** Default return value is the default radio */
60
- return (
61
- <FormSelectorWrapper
62
- {...wrapperProps}
63
- className={`${classes['radio-wrapper']} ${className ?? ''}`}
64
- containerProps={{ className: classes['radio-container'] }}
65
- helperText={helperText}
66
- helperProps={helperProps}
67
- parentErrorId={parentErrorId}
68
- errorId={errorId}
69
- errorMessage={errorMessage}
70
- error={error}
71
- disabled={disabled}
72
- identifier={identifier}
73
- >
74
- <input
75
- {...rest}
65
+ /** Default return value is the default radio */
66
+ return (
67
+ <FormSelectorWrapper
68
+ {...formSelectorWrapperProps}
69
+ className={`${classes['radio-wrapper']} ${className ?? ''}`}
70
+ containerProps={{ className: classes['radio-container'] }}
71
+ helperText={helperText}
72
+ helperProps={helperProps}
73
+ parentErrorId={parentErrorId}
74
+ errorId={errorId}
75
+ errorMessage={errorMessage}
76
+ error={error}
76
77
  disabled={disabled}
77
- tabIndex={0}
78
- className={`${classes['native-input']} ${error ? classes['error'] : ''}`}
79
- onChange={onChangeHandler}
80
- checked={checked}
81
- aria-invalid={error ? true : false}
82
- aria-checked={checked}
83
- aria-describedby={describedBy}
84
- name={name}
85
- value={value}
86
- id={`${identifier}-radio`}
87
- type="radio"
88
- />
89
-
90
- {checked && (
91
- <Icon
92
- className={`${classes['input']} ${disabled ? classes['disabled'] : ''}`}
93
- icon={Icons.Radio}
78
+ identifier={identifier}
79
+ >
80
+ <input
81
+ {...rest}
82
+ ref={ref}
83
+ disabled={disabled}
84
+ tabIndex={0}
85
+ className={`${classes['native-input']} ${error ? classes['error'] : ''}`}
86
+ onChange={onChangeHandler}
87
+ checked={checked}
88
+ aria-invalid={error ? true : false}
89
+ aria-checked={checked}
90
+ aria-describedby={describedBy}
91
+ name={name}
92
+ value={value}
93
+ id={`${identifier}-radio`}
94
+ type="radio"
94
95
  />
95
- )}
96
- {!checked && (
97
- <Icon
98
- className={`${classes['input']} ${disabled ? classes['disabled'] : ''}`}
99
- icon={Icons.Circle}
100
- />
101
- )}
102
96
 
103
- <label onClick={onChangeHandler} htmlFor={`${identifier}-radio`}>
104
- {children}
105
- </label>
106
- </FormSelectorWrapper>
107
- );
108
- };
97
+ {checked && (
98
+ <Icon
99
+ className={`${classes['input']} ${disabled ? classes['disabled'] : ''}`}
100
+ icon={Icons.Radio}
101
+ />
102
+ )}
103
+ {!checked && (
104
+ <Icon
105
+ className={`${classes['input']} ${disabled ? classes['disabled'] : ''}`}
106
+ icon={Icons.Circle}
107
+ />
108
+ )}
109
+
110
+ <label onClick={onChangeHandler} htmlFor={`${identifier}-radio`}>
111
+ {children}
112
+ </label>
113
+ </FormSelectorWrapper>
114
+ );
115
+ }
116
+ );
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Option } from './Option';
3
3
  import { render } from '@testing-library/react';
4
4
 
@@ -13,3 +13,29 @@ describe('Option should render', () => {
13
13
  expect(component).toBeDefined();
14
14
  });
15
15
  });
16
+
17
+ describe('ref should work', () => {
18
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
19
+ const ExampleComponent = ({
20
+ propagateRef,
21
+ }: {
22
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
23
+ }) => {
24
+ const ref = useRef(null);
25
+
26
+ useEffect(() => {
27
+ if (ref.current) {
28
+ propagateRef && propagateRef(ref);
29
+ }
30
+ }, [ref]);
31
+
32
+ return <Option value="test" children="Test" data-ref="testing" ref={ref} />;
33
+ };
34
+
35
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
36
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
37
+ };
38
+
39
+ render(<ExampleComponent propagateRef={refCheck} />);
40
+ });
41
+ });
@@ -1,8 +1,7 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { ComponentPropsWithRef, useEffect, useState } from 'react';
2
2
  import classes from './Select.module.scss';
3
- import { HTMLProps } from '../../interfaces';
4
3
 
5
- export interface Props extends HTMLProps<HTMLLIElement> {
4
+ export interface Props extends ComponentPropsWithRef<'li'> {
6
5
  children: string;
7
6
  value: string;
8
7
  disabled?: boolean;
@@ -12,46 +11,54 @@ export interface Props extends HTMLProps<HTMLLIElement> {
12
11
  onOptionSelect?: (event: React.SyntheticEvent<HTMLLIElement>) => void;
13
12
  }
14
13
 
15
- export const Option = ({
16
- children,
17
- className,
18
- selected = false,
19
- onOptionSelect,
20
- disabled,
21
- filter,
22
- ...rest
23
- }: Props) => {
24
- const [showOption, setShowOption] = useState(true);
14
+ export const Option = React.forwardRef<HTMLLIElement, Props>(
15
+ (
16
+ {
17
+ children,
18
+ className,
19
+ selected = false,
20
+ onOptionSelect,
21
+ disabled,
22
+ filter,
23
+ value,
24
+ ...rest
25
+ }: Props,
26
+ ref
27
+ ) => {
28
+ const [showOption, setShowOption] = useState(true);
25
29
 
26
- const onSelectHandler = (event: React.SyntheticEvent<HTMLLIElement>) => {
27
- if (onOptionSelect) onOptionSelect(event);
28
- };
30
+ const onSelectHandler = (event: React.SyntheticEvent<HTMLLIElement>) => {
31
+ if (onOptionSelect) onOptionSelect(event);
32
+ };
29
33
 
30
- useEffect(() => {
31
- if (filter) {
32
- setShowOption(children.toLowerCase().match(filter.toLowerCase()) !== null);
33
- } else {
34
- setShowOption(true);
35
- }
36
- }, [filter]);
34
+ useEffect(() => {
35
+ if (filter) {
36
+ setShowOption(children.toLowerCase().match(filter.toLowerCase()) !== null);
37
+ } else {
38
+ setShowOption(true);
39
+ }
40
+ }, [filter]);
37
41
 
38
- if (!showOption) return null;
42
+ if (!showOption) return null;
39
43
 
40
- return (
41
- <li
42
- {...rest}
43
- className={`${selected ? classes['selected-option'] : ''} ${
44
- disabled ? classes.disabled : ''
45
- } ${className ?? ''}`}
46
- onClick={onSelectHandler}
47
- onKeyPress={(e) => {
48
- e.key === 'Enter' && onSelectHandler(e);
49
- }}
50
- aria-selected={selected}
51
- role="option"
52
- tabIndex={disabled ? -1 : 0}
53
- >
54
- {children}
55
- </li>
56
- );
57
- };
44
+ return (
45
+ <li
46
+ {...rest}
47
+ ref={ref}
48
+ data-value={value}
49
+ className={`${selected ? classes['selected-option'] : ''} ${
50
+ disabled ? classes.disabled : ''
51
+ } ${className ?? ''}`}
52
+ onClick={onSelectHandler}
53
+ onKeyPress={(e) => {
54
+ e.key === 'Enter' && onSelectHandler(e);
55
+ }}
56
+ aria-selected={selected}
57
+ role="option"
58
+ tabIndex={disabled ? -1 : 0}
59
+ >
60
+ {children}
61
+ </li>
62
+ );
63
+ }
64
+ );
@@ -13,6 +13,10 @@ $listItemHeight: 2.125rem;
13
13
  background-color: #fff;
14
14
  font-size: var(--font-size);
15
15
 
16
+ [data-display] {
17
+ color: var(--greyed-out);
18
+ }
19
+
16
20
  &.expanded {
17
21
  border: var(--input-border-width) solid transparent;
18
22
 
@@ -62,6 +66,7 @@ $listItemHeight: 2.125rem;
62
66
  width: 100%;
63
67
  opacity: 0;
64
68
  pointer-events: none;
69
+ overflow: auto;
65
70
  }
66
71
 
67
72
  ul {
@@ -74,7 +79,6 @@ $listItemHeight: 2.125rem;
74
79
  border-radius: var(--input-border-radius);
75
80
  color: var(--default);
76
81
  text-align: left;
77
- overflow: auto;
78
82
  max-height: calc($listItemHeight * 10);
79
83
 
80
84
  li {
@@ -1,32 +1,41 @@
1
- import React, { ReactElement } from 'react';
2
- import { Select, Props } from './Select';
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Select as SelectComponent, Props } from './Select';
3
3
  import { render } from '@testing-library/react';
4
4
  import { Option } from './Option';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
- const createSelect = (amountOfOptions = 5, params?: Props) => {
8
- const renderOptions = (amount: number): ReactElement[] => {
9
- const returnArr: ReactElement[] = [];
10
-
11
- for (let i = 0; i < amount; i++) {
12
- returnArr.push(<Option key={i} value={`option${i}`}>{`Option${i}`}</Option>);
13
- }
14
-
15
- return returnArr;
16
- };
17
-
18
- const queries = render(
19
- <Select onChange={jest.fn()} {...params} data-testid="custom-select">
20
- {renderOptions(amountOfOptions)}
21
- </Select>
22
- );
23
- const select = queries.getByTestId('custom-select')!;
24
- const button = select.querySelector('button');
7
+ const defaultParams: Props = {
8
+ name: 'Example select',
9
+ children: [
10
+ <Option value="option1">Test</Option>,
11
+ <Option value="option2">Test2</Option>,
12
+ <Option value="option3">Test3</Option>,
13
+ <Option value="option4">Test4</Option>,
14
+ <Option value="option5">Test5</Option>,
15
+ <Option value="option6">Test6</Option>,
16
+ <Option value="option7">Test7</Option>,
17
+ <Option value="option8">Test8</Option>,
18
+ <Option value="option9">Test9</Option>,
19
+ <Option value="option10">Test10</Option>,
20
+ <Option value="option11">Test11</Option>,
21
+ <Option value="option12">Test12</Option>,
22
+ <Option value="option13">Test13</Option>,
23
+ <Option value="option14">Test14</Option>,
24
+ <Option value="option15">Test15</Option>,
25
+ <Option value="option16">Test16</Option>,
26
+ <Option value="option17">Test17</Option>,
27
+ ],
28
+ value: '',
29
+ };
25
30
 
26
- if (button) {
27
- userEvent.click(button);
31
+ const createSelect = (params?: (defaultParams: Props) => Props) => {
32
+ let parameters: Props = defaultParams;
33
+ if (params) {
34
+ parameters = params(defaultParams);
28
35
  }
29
-
36
+ const queries = render(<SelectComponent {...parameters} data-testid="select" />);
37
+ const select = queries.getByTestId('select');
38
+ const button = select.querySelector('button');
30
39
  const list = select.querySelector('ul[role="listbox"]');
31
40
  const dropdownWrapper = select.querySelector('.list-wrapper');
32
41
 
@@ -41,39 +50,112 @@ const createSelect = (amountOfOptions = 5, params?: Props) => {
41
50
 
42
51
  describe('Select should render', () => {
43
52
  it('renders with 5 options and proper attributes', () => {
44
- const { select, button, list } = createSelect(5);
53
+ const { select, button, list } = createSelect((defaultParams) => ({
54
+ ...defaultParams,
55
+ children: [
56
+ <Option value="option1">Test</Option>,
57
+ <Option value="option2">Test2</Option>,
58
+ <Option value="option3">Test3</Option>,
59
+ <Option value="option4">Test4</Option>,
60
+ <Option value="option5">Test5</Option>,
61
+ ],
62
+ placeholder: 'Placeholder',
63
+ }));
64
+
65
+ if (button) {
66
+ userEvent.click(button);
67
+ }
45
68
 
46
69
  expect(select).toBeDefined();
70
+ expect(select.querySelector('.placeholder')).toBeInTheDocument();
47
71
  expect(button?.getAttribute('aria-expanded')).toBe('true');
48
72
  expect(button?.getAttribute('aria-disabled')).toBe('false');
49
73
  expect(list).toBeDefined();
50
74
  expect(list?.querySelectorAll("li[role='option']").length).toBe(5);
51
75
  });
76
+
77
+ it('should be disabled', () => {
78
+ const { select, button } = createSelect((defaultParams) => ({
79
+ ...defaultParams,
80
+ disabled: true,
81
+ }));
82
+
83
+ expect(select).toHaveClass('disabled');
84
+ expect(button).toHaveAttribute('aria-disabled', 'true');
85
+ expect(button).toHaveAttribute('disabled');
86
+ });
87
+
88
+ it('should have an error', () => {
89
+ const { select, button } = createSelect((defaultParams) => ({
90
+ ...defaultParams,
91
+ error: true,
92
+ value: 'option4',
93
+ }));
94
+
95
+ expect(select).toHaveClass('error');
96
+ expect(button).toHaveAttribute('aria-invalid', 'true');
97
+ expect(select.querySelector('[data-clear]')).not.toBeInTheDocument();
98
+ });
99
+ });
100
+
101
+ describe('ref should work', () => {
102
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
103
+ const ExampleComponent = ({
104
+ propagateRef,
105
+ }: {
106
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
107
+ }) => {
108
+ const ref = useRef(null);
109
+
110
+ useEffect(() => {
111
+ if (ref.current) {
112
+ propagateRef && propagateRef(ref);
113
+ }
114
+ }, [ref]);
115
+
116
+ return <SelectComponent {...defaultParams} data-ref="testing" ref={ref} />;
117
+ };
118
+
119
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
120
+ expect(ref.current!.nodeName).toBe('SELECT');
121
+ };
122
+
123
+ render(<ExampleComponent propagateRef={refCheck} />);
124
+ });
52
125
  });
53
126
 
54
127
  describe('Select should render with search', () => {
55
128
  it('shows the search and filtering works', () => {
56
- const { select, list, dropdownWrapper } = createSelect(20);
129
+ const { select, list, button, dropdownWrapper } = createSelect();
130
+
131
+ if (button) {
132
+ userEvent.click(button);
133
+ }
57
134
 
58
135
  const search = dropdownWrapper?.querySelector('input');
59
136
 
60
137
  expect(select).toBeTruthy();
61
138
  expect(search).toBeTruthy();
62
- expect(list?.querySelectorAll("li[role='option']").length).toBe(20);
139
+ expect(list?.querySelectorAll("li[role='option']").length).toBe(17);
63
140
 
64
141
  if (search) {
65
- userEvent.type(search, '19');
142
+ userEvent.type(search, '17');
66
143
  }
67
144
 
68
145
  expect(list?.querySelectorAll("li[role='option']").length).toBe(1);
69
- expect(list?.querySelector("li[role='option']")?.innerHTML).toBe('Option19');
146
+ expect(list?.querySelector("li[role='option']")?.innerHTML).toBe('Test17');
70
147
  });
71
148
  });
72
149
 
73
150
  describe('Selecting options using keyboard', () => {
74
151
  it('should focus through list items and select on enterpress', () => {
75
- const { select, list } = createSelect(7);
152
+ const { select, button, list } = createSelect();
76
153
 
154
+ if (button) {
155
+ userEvent.click(button);
156
+ }
157
+
158
+ userEvent.tab();
77
159
  userEvent.tab();
78
160
  expect(list?.querySelectorAll('li')[0]).toHaveFocus();
79
161
  userEvent.tab();
@@ -90,7 +172,119 @@ describe('Selecting options using keyboard', () => {
90
172
  userEvent.keyboard('{enter}');
91
173
 
92
174
  setTimeout(() => {
93
- expect(select.querySelector('button > span > span')?.innerHTML).toBe('Option5');
175
+ expect(select.querySelector('button > span > span')?.innerHTML).toBe('Test5');
94
176
  }, 50);
95
177
  });
96
178
  });
179
+
180
+ describe('Expanded should be false whenever we click the body', () => {
181
+ it('closes select on body click', () => {
182
+ const { button } = createSelect();
183
+
184
+ if (button) {
185
+ userEvent.click(button);
186
+ }
187
+
188
+ expect(button).toHaveAttribute('aria-expanded', 'true');
189
+ userEvent.click(document.body);
190
+ expect(button).toHaveAttribute('aria-expanded', 'false');
191
+ });
192
+ });
193
+
194
+ describe('List expansion', () => {
195
+ it('should expand upwards', () => {
196
+ const { select, button } = createSelect();
197
+
198
+ Object.defineProperty(window, 'innerHeight', { value: 500, writable: true });
199
+
200
+ select.getBoundingClientRect = () => ({
201
+ x: 50,
202
+ y: 50,
203
+ width: 500,
204
+ height: 50,
205
+ top: 250,
206
+ left: 250,
207
+ right: 750,
208
+ bottom: 750,
209
+ toJSON: () => jest.fn(),
210
+ });
211
+
212
+ if (button) {
213
+ userEvent.click(button);
214
+ }
215
+
216
+ const listWrapper = select.querySelector('.list-wrapper');
217
+
218
+ expect(listWrapper).toHaveStyle({ bottom: '0px' });
219
+ });
220
+
221
+ it('should expand downwards with a max height set', () => {
222
+ const { select, getByRole } = createSelect();
223
+ const listWrapper = select.querySelector('.list-wrapper');
224
+
225
+ listWrapper!.getBoundingClientRect = () => ({
226
+ x: 50,
227
+ y: 50,
228
+ width: 500,
229
+ height: 600,
230
+ top: 10,
231
+ left: 250,
232
+ right: 750,
233
+ bottom: 50,
234
+ toJSON: () => jest.fn(),
235
+ });
236
+
237
+ Object.defineProperty(window, 'innerHeight', { value: 500, writable: true });
238
+
239
+ select.getBoundingClientRect = () => ({
240
+ x: 50,
241
+ y: 50,
242
+ width: 500,
243
+ height: 40,
244
+ top: 10,
245
+ left: 250,
246
+ right: 750,
247
+ bottom: 50,
248
+ toJSON: () => jest.fn(),
249
+ });
250
+
251
+ userEvent.click(document.body);
252
+ const button = getByRole('button');
253
+ userEvent.click(button);
254
+
255
+ expect(listWrapper).toHaveStyle({ maxHeight: '474px' });
256
+ expect(listWrapper).toHaveStyle({ top: '0px' });
257
+ });
258
+ });
259
+
260
+ describe('onClear method', () => {
261
+ it('should show a cross and fire the passed onClear function', async () => {
262
+ const onClearHandler = jest.fn();
263
+ const onChangeHandler = jest.fn();
264
+
265
+ const { button, container } = createSelect((defaultParams) => ({
266
+ ...defaultParams,
267
+ onClear: onClearHandler,
268
+ onChange: onChangeHandler,
269
+ value: 'option4',
270
+ }));
271
+
272
+ if (button) {
273
+ userEvent.click(button);
274
+ }
275
+
276
+ const optionToClick = container.querySelector('li[data-value="option5"]')!;
277
+ const onClearButton = container.querySelector('[data-clear]')!;
278
+
279
+ userEvent.click(optionToClick);
280
+ userEvent.click(onClearButton);
281
+
282
+ expect(onClearHandler).toHaveBeenCalled();
283
+ expect(onClearButton).toBeInTheDocument();
284
+ expect(container.querySelector('li[aria-selected="true"]')).toHaveTextContent('Test4');
285
+ expect(container.querySelector('.selected-option')).toHaveTextContent('Test4');
286
+ expect(onChangeHandler).toBeCalledWith(
287
+ expect.objectContaining({ target: expect.objectContaining({ value: 'option5' }) })
288
+ );
289
+ });
290
+ });