@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.
- package/README.md +16 -1
- package/dist/Breadcrumbs/Breadcrumbs.d.ts +3 -3
- package/dist/Button/BaseButton.d.ts +3 -4
- package/dist/Button/Button.d.ts +3 -4
- package/dist/Button/IconButton.d.ts +3 -4
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/Form/Checkbox/Checkbox.d.ts +5 -5
- package/dist/Form/Fieldset/Fieldset.d.ts +4 -4
- package/dist/Form/FormControl/FormControl.d.ts +5 -5
- package/dist/Form/FormGroup/FormGroup.d.ts +4 -4
- package/dist/Form/FormHelperText/FormHelperText.d.ts +4 -5
- package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +8 -12
- package/dist/Form/Input/Input.d.ts +7 -6
- package/dist/Form/Label/Label.d.ts +4 -5
- package/dist/Form/Radio/Radio.d.ts +5 -5
- package/dist/Form/Select/Option.d.ts +3 -4
- package/dist/Form/Select/Select.d.ts +4 -4
- package/dist/Form/Textarea/Textarea.d.ts +9 -5
- package/dist/Form/Toggle/Toggle.d.ts +3 -3
- package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +4 -3
- package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +5 -5
- package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +4 -4
- package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +7 -4
- package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +3 -3
- package/dist/Form/Wrapper/Wrapper/Wrapper.d.ts +6 -6
- package/dist/Form/form.interfaces.d.ts +4 -3
- package/dist/Icon/Icon.d.ts +4 -4
- package/dist/Link/Link.d.ts +4 -6
- package/dist/Notifications/BaseModal/BaseModal.d.ts +3 -4
- package/dist/Notifications/BaseModal/BaseModalActions/BaseModalActions.d.ts +3 -3
- package/dist/Notifications/BaseModal/BaseModalContent/BaseModalContent.d.ts +3 -3
- package/dist/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.d.ts +3 -3
- package/dist/Notifications/Dialog/Dialog.d.ts +3 -3
- package/dist/Notifications/Dialog/DialogActions/DialogActions.d.ts +3 -3
- package/dist/Notifications/Dialog/DialogTitle/DialogTitle.d.ts +3 -3
- package/dist/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.d.ts +5 -4
- package/dist/Notifications/DiscardChangesModal/DiscardChangesModal.d.ts +3 -1
- package/dist/Pagination/Pagination.d.ts +19 -0
- package/dist/Popover/Popover.d.ts +3 -3
- package/dist/Tabs/Tab.d.ts +11 -0
- package/dist/Tabs/TabButton.d.ts +10 -0
- package/dist/Tabs/TabPanel.d.ts +8 -0
- package/dist/Tabs/Tabs.d.ts +9 -0
- package/dist/TextEllipsis/TextEllipsis.d.ts +6 -0
- package/dist/Tiles/Tile.d.ts +3 -3
- package/dist/Tiles/Tiles.d.ts +3 -3
- package/dist/Tooltip/Tooltip.d.ts +3 -3
- package/dist/Typography/Typography.d.ts +6 -4
- package/dist/Wizard/BaseWizardSteps/BaseWizardSteps.d.ts +3 -3
- package/dist/Wizard/WizardSteps/WizardSteps.d.ts +3 -3
- package/dist/_BaseStyling_/BaseStyling.d.ts +9 -0
- package/dist/hooks/useRepeater.d.ts +10 -0
- package/dist/hooks/useSpacing.d.ts +2 -2
- package/dist/hooks/useWrapper.d.ts +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/interfaces.d.ts +2 -11
- package/dist/react-lib-components.cjs.development.js +2395 -1696
- package/dist/react-lib-components.cjs.development.js.map +1 -1
- package/dist/react-lib-components.cjs.production.min.js +1 -1
- package/dist/react-lib-components.cjs.production.min.js.map +1 -1
- package/dist/react-lib-components.esm.js +2391 -1698
- package/dist/react-lib-components.esm.js.map +1 -1
- package/dist/util/helper.d.ts +6 -1
- package/package.json +30 -24
- package/src/Breadcrumbs/Breadcrumbs.tsx +39 -37
- package/src/Button/BaseButton.test.tsx +65 -19
- package/src/Button/BaseButton.tsx +2 -3
- package/src/Button/Button.test.tsx +63 -17
- package/src/Button/Button.tsx +15 -4
- package/src/Button/IconButton.test.tsx +57 -22
- package/src/Button/IconButton.tsx +14 -9
- package/src/ContextMenu/ContextMenu.test.tsx +27 -1
- package/src/ContextMenu/ContextMenu.tsx +70 -65
- package/src/Form/Checkbox/Checkbox.test.tsx +28 -2
- package/src/Form/Checkbox/Checkbox.tsx +132 -122
- package/src/Form/Fieldset/Fieldset.test.tsx +28 -2
- package/src/Form/Fieldset/Fieldset.tsx +96 -50
- package/src/Form/FormControl/FormControl.test.tsx +27 -1
- package/src/Form/FormControl/FormControl.tsx +36 -39
- package/src/Form/FormGroup/FormGroup.test.tsx +51 -1
- package/src/Form/FormGroup/FormGroup.tsx +64 -58
- package/src/Form/FormHelperText/FormHelperText.test.tsx +27 -1
- package/src/Form/FormHelperText/FormHelperText.tsx +20 -16
- package/src/Form/FormSelectorWrapper/FormSelectorWrapper.test.tsx +78 -0
- package/src/Form/FormSelectorWrapper/FormSelectorWrapper.tsx +61 -55
- package/src/Form/Input/Input.module.scss +34 -15
- package/src/Form/Input/Input.test.tsx +27 -1
- package/src/Form/Input/Input.tsx +88 -47
- package/src/Form/Label/Label.test.tsx +27 -1
- package/src/Form/Label/Label.tsx +18 -14
- package/src/Form/Radio/Radio.test.tsx +28 -2
- package/src/Form/Radio/Radio.tsx +98 -90
- package/src/Form/Select/Option.test.tsx +27 -1
- package/src/Form/Select/Option.tsx +49 -42
- package/src/Form/Select/Select.module.scss +5 -1
- package/src/Form/Select/Select.test.tsx +224 -30
- package/src/Form/Select/Select.tsx +248 -182
- package/src/Form/Textarea/Textarea.module.scss +2 -1
- package/src/Form/Textarea/Textarea.test.tsx +28 -2
- package/src/Form/Textarea/Textarea.tsx +44 -29
- package/src/Form/Toggle/Toggle.module.scss +9 -0
- package/src/Form/Toggle/Toggle.test.tsx +27 -1
- package/src/Form/Toggle/Toggle.tsx +25 -12
- package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +27 -1
- package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.tsx +45 -48
- package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +17 -1
- package/src/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +89 -1
- package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +134 -74
- package/src/Form/Wrapper/RadioWrapper/RadioWrapper.tsx +64 -59
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.module.scss +1 -1
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +43 -1
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +54 -44
- package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +5 -7
- package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.test.tsx +43 -1
- package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +100 -85
- package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +1 -1
- package/src/Form/Wrapper/Wrapper/Wrapper.test.tsx +27 -1
- package/src/Form/Wrapper/Wrapper/Wrapper.tsx +76 -71
- package/src/Form/form.interfaces.ts +4 -3
- package/src/Icon/Icon.module.scss +4 -0
- package/src/Icon/Icon.test.tsx +30 -2
- package/src/Icon/Icon.tsx +5 -5
- package/src/Link/Link.test.tsx +27 -1
- package/src/Link/Link.tsx +10 -7
- package/src/Notifications/BaseModal/BaseModal.test.tsx +27 -1
- package/src/Notifications/BaseModal/BaseModal.tsx +59 -54
- package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.test.tsx +26 -1
- package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.tsx +11 -9
- package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.test.tsx +27 -1
- package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.tsx +27 -26
- package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +29 -1
- package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.tsx +18 -16
- package/src/Notifications/Dialog/Dialog.test.tsx +39 -1
- package/src/Notifications/Dialog/Dialog.tsx +84 -78
- package/src/Notifications/Dialog/DialogActions/DialogActions.test.tsx +27 -1
- package/src/Notifications/Dialog/DialogActions/DialogActions.tsx +15 -12
- package/src/Notifications/Dialog/DialogTitle/DialogTitle.test.tsx +28 -2
- package/src/Notifications/Dialog/DialogTitle/DialogTitle.tsx +13 -11
- package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.test.tsx +41 -1
- package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.tsx +43 -36
- package/src/Notifications/DiscardChangesModal/DiscardChangesModal.test.tsx +52 -1
- package/src/Notifications/DiscardChangesModal/DiscardChangesModal.tsx +8 -3
- package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +1 -1
- package/src/Pagination/Pagination.module.scss +120 -0
- package/src/Pagination/Pagination.test.tsx +176 -0
- package/src/Pagination/Pagination.tsx +205 -0
- package/src/Popover/Popover.tsx +3 -3
- package/src/Tabs/Tab.test.tsx +71 -0
- package/src/Tabs/Tab.tsx +17 -0
- package/src/Tabs/TabButton.module.scss +36 -0
- package/src/Tabs/TabButton.test.tsx +77 -0
- package/src/Tabs/TabButton.tsx +58 -0
- package/src/Tabs/TabPanel.module.scss +7 -0
- package/src/Tabs/TabPanel.test.tsx +76 -0
- package/src/Tabs/TabPanel.tsx +27 -0
- package/src/Tabs/Tabs.module.scss +41 -0
- package/src/Tabs/Tabs.test.tsx +268 -0
- package/src/Tabs/Tabs.tsx +149 -0
- package/src/TextEllipsis/TextEllipsis.module.scss +18 -0
- package/src/TextEllipsis/TextEllipsis.test.tsx +80 -0
- package/src/TextEllipsis/TextEllipsis.tsx +55 -0
- package/src/Tiles/Tile.test.tsx +27 -1
- package/src/Tiles/Tile.tsx +59 -62
- package/src/Tiles/Tiles.test.tsx +27 -1
- package/src/Tiles/Tiles.tsx +42 -39
- package/src/Tooltip/Tooltip.test.tsx +27 -1
- package/src/Tooltip/Tooltip.tsx +104 -92
- package/src/Typography/Typography.test.tsx +27 -1
- package/src/Typography/Typography.tsx +66 -68
- package/src/Wizard/BaseWizardSteps/BaseWizardSteps.tsx +67 -62
- package/src/Wizard/WizardSteps/WizardSteps.tsx +24 -21
- package/src/_BaseStyling_/BaseStyling.tsx +19 -1
- package/src/hooks/useRepeater.test.tsx +139 -0
- package/src/hooks/useRepeater.ts +34 -0
- package/src/hooks/useSpacing.ts +1 -1
- package/src/hooks/useWrapper.ts +7 -2
- package/src/index.ts +15 -1
- package/src/interfaces.ts +2 -12
- package/src/util/helper.test.tsx +38 -1
- package/src/util/helper.tsx +21 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import classes from './Select.module.scss';
|
|
2
2
|
|
|
3
3
|
import React, {
|
|
4
|
-
|
|
4
|
+
ComponentPropsWithRef,
|
|
5
|
+
Fragment,
|
|
5
6
|
ReactElement,
|
|
7
|
+
RefObject,
|
|
6
8
|
useEffect,
|
|
7
|
-
useLayoutEffect,
|
|
8
9
|
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
} from 'react';
|
|
@@ -12,10 +13,10 @@ import { Input } from '../Input/Input';
|
|
|
12
13
|
import { Icon, Icons } from '../../Icon/Icon';
|
|
13
14
|
import { FormElement } from '../form.interfaces';
|
|
14
15
|
import { useBodyClick } from '../../hooks/useBodyClick';
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
16
|
+
import readyclasses from '../../readyclasses.module.scss';
|
|
17
|
+
import { filterProps } from '../../util/helper';
|
|
17
18
|
|
|
18
|
-
export interface Props extends
|
|
19
|
+
export interface Props extends ComponentPropsWithRef<'select'>, FormElement {
|
|
19
20
|
children: ReactElement[];
|
|
20
21
|
name?: string;
|
|
21
22
|
labeledBy?: string;
|
|
@@ -23,195 +24,260 @@ export interface Props extends FormElement<HTMLSelectElement> {
|
|
|
23
24
|
placeholder?: string;
|
|
24
25
|
searchPlaceholder?: string;
|
|
25
26
|
className?: string;
|
|
26
|
-
value
|
|
27
|
+
value: string;
|
|
27
28
|
onChange?: (event: React.ChangeEvent<HTMLSelectElement>, child?: ReactElement) => void;
|
|
28
29
|
onClear?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
32
|
+
type Position = {
|
|
33
|
+
top: 0 | 'initial';
|
|
34
|
+
bottom: 0 | 'initial';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
38
|
+
(
|
|
39
|
+
{
|
|
40
|
+
children,
|
|
41
|
+
name,
|
|
42
|
+
disabled = false,
|
|
43
|
+
labeledBy,
|
|
44
|
+
placeholder,
|
|
45
|
+
describedBy,
|
|
46
|
+
searchPlaceholder = 'Search item',
|
|
47
|
+
className,
|
|
48
|
+
error = false,
|
|
49
|
+
value,
|
|
50
|
+
onChange,
|
|
51
|
+
onClear,
|
|
52
|
+
...rest
|
|
53
|
+
}: Props,
|
|
54
|
+
ref
|
|
55
|
+
) => {
|
|
56
|
+
const [expanded, setExpanded] = useState(false);
|
|
57
|
+
const [opacity, setOpacity] = useState(0); // We set opacity because other wise if we calculate the max height you see the list full height for a split second and then it shortens.
|
|
58
|
+
const [filter, setFilter] = useState('');
|
|
59
|
+
const [display, setDisplay] = useState('');
|
|
60
|
+
const [listPosition, setListPosition] = useState<Partial<Position>>({});
|
|
61
|
+
const [optionsListMaxHeight, setOptionsListMaxHeight] = useState('none');
|
|
62
|
+
const containerReference = useRef<HTMLDivElement>(null);
|
|
63
|
+
const optionListReference = useRef<HTMLDivElement>(null);
|
|
64
|
+
|
|
65
|
+
const nativeSelect = useRef<HTMLSelectElement>(null);
|
|
66
|
+
|
|
67
|
+
const syncDisplayValue = (val: string) => {
|
|
68
|
+
React.Children.forEach(children, (child) => {
|
|
69
|
+
if (child.props.value === val) {
|
|
70
|
+
setDisplay(child.props.children);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const rePositionList = () => {
|
|
76
|
+
if (!expanded || !optionListReference.current || !containerReference.current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check whether there is more space above or below the select
|
|
81
|
+
// Check space between the bottom of select and top of viewport
|
|
82
|
+
const spaceOnTopOfSelect = containerReference.current.getBoundingClientRect().bottom;
|
|
83
|
+
|
|
84
|
+
// Check space between the top of the select and bottom of viewport
|
|
85
|
+
const spaceOnBottomOfSelect =
|
|
86
|
+
window.innerHeight - containerReference.current.getBoundingClientRect().top;
|
|
87
|
+
|
|
88
|
+
// Set position as if there's more space on the bottom
|
|
89
|
+
let position: Position = { top: 0, bottom: 'initial' };
|
|
90
|
+
|
|
91
|
+
// Set the position of the select
|
|
92
|
+
if (spaceOnTopOfSelect > spaceOnBottomOfSelect) {
|
|
93
|
+
position = { top: 'initial', bottom: 0 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setListPosition(position);
|
|
97
|
+
|
|
98
|
+
// Calculate the potential max height of the options list
|
|
99
|
+
calculateOptionListMaxHeight(position);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const calculateOptionListMaxHeight = (position: Position) => {
|
|
103
|
+
// Calculate max height if there's more space below the select
|
|
104
|
+
const listHeight = optionListReference.current!.getBoundingClientRect().height;
|
|
105
|
+
const transformOrigin = position.top !== 'initial' ? 'top' : 'bottom';
|
|
106
|
+
|
|
107
|
+
const availableSpace =
|
|
108
|
+
transformOrigin === 'top'
|
|
109
|
+
? window.innerHeight -
|
|
110
|
+
containerReference.current!.getBoundingClientRect()[transformOrigin] -
|
|
111
|
+
16
|
|
112
|
+
: containerReference.current!.getBoundingClientRect()[transformOrigin] - 16;
|
|
113
|
+
|
|
114
|
+
if (availableSpace < listHeight) {
|
|
115
|
+
setOptionsListMaxHeight(`${availableSpace}px`);
|
|
116
|
+
setOpacity(100);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setOptionsListMaxHeight('none');
|
|
121
|
+
setOpacity(100);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const onOptionChangeHandler = (event: React.MouseEvent<HTMLLIElement>) => {
|
|
125
|
+
// We need to set value and the fire change event. If a custom ref has been given we pass that value, otherwise we use the ref we've created ourselves when the component was instantiated.
|
|
126
|
+
if (nativeSelect.current) {
|
|
127
|
+
nativeSelect.current.value = event.currentTarget.dataset.value!;
|
|
128
|
+
nativeSelect.current.dispatchEvent(new Event('change', { bubbles: true }));
|
|
129
|
+
} else if (ref) {
|
|
130
|
+
(ref as RefObject<HTMLSelectElement>).current!.value = event.currentTarget.dataset.value!;
|
|
131
|
+
(ref as RefObject<HTMLSelectElement>).current!.dispatchEvent(
|
|
132
|
+
new Event('change', { bubbles: true })
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
setExpanded(false);
|
|
136
|
+
};
|
|
137
|
+
|
|
85
138
|
/**
|
|
86
|
-
* We
|
|
87
|
-
*
|
|
139
|
+
* @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.
|
|
140
|
+
* The `children` prop can be either a single object (1 child) or an array of multiple children.
|
|
88
141
|
*/
|
|
142
|
+
const renderOptions = () =>
|
|
143
|
+
React.Children.map(children, (child) =>
|
|
144
|
+
React.cloneElement(child, {
|
|
145
|
+
onOptionSelect: onOptionChangeHandler,
|
|
146
|
+
selected: child.props.value === value,
|
|
147
|
+
filter: filter,
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const renderSearch = () => (
|
|
152
|
+
<Input
|
|
153
|
+
autoFocus
|
|
154
|
+
onChange={filterResults}
|
|
155
|
+
className={classes['select-search']}
|
|
156
|
+
wrapperProps={{ className: classes['select-search-wrapper'] }}
|
|
157
|
+
type="text"
|
|
158
|
+
name="search-option"
|
|
159
|
+
placeholder={searchPlaceholder}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const filterResults = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
164
|
+
setFilter(event.currentTarget.value);
|
|
165
|
+
};
|
|
89
166
|
|
|
90
|
-
|
|
167
|
+
const statusIcon = () => {
|
|
168
|
+
if (error) {
|
|
169
|
+
return <Icon className={classes['warning']} icon={Icons.Warning} />;
|
|
170
|
+
}
|
|
91
171
|
|
|
92
|
-
|
|
93
|
-
|
|
172
|
+
if (value?.length !== 0 && onClear) {
|
|
173
|
+
return (
|
|
174
|
+
<Icon
|
|
175
|
+
tag="div"
|
|
176
|
+
data-clear
|
|
177
|
+
icon={Icons.TimesThin}
|
|
178
|
+
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
onClear(e);
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
};
|
|
94
188
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
newValue = child.props.value;
|
|
99
|
-
}
|
|
189
|
+
const nativeOnChangeHandler = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
190
|
+
onChange && onChange(event);
|
|
191
|
+
};
|
|
100
192
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
syncDisplayValue(value);
|
|
195
|
+
}, [value]);
|
|
108
196
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
rePositionList();
|
|
199
|
+
}, [expanded]);
|
|
113
200
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
})
|
|
201
|
+
useBodyClick(
|
|
202
|
+
(event: MouseEvent) => !(event.target as Element).closest('.custom-select') && expanded,
|
|
203
|
+
() => {
|
|
204
|
+
setExpanded(!expanded);
|
|
205
|
+
setListPosition({ top: 0, bottom: 'initial' });
|
|
206
|
+
setOpacity(0);
|
|
207
|
+
},
|
|
208
|
+
expanded
|
|
132
209
|
);
|
|
133
210
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<Icon className={classes['triangle-down']} icon={Icons.TriangleDown} />
|
|
211
|
+
const additionalClasses = [];
|
|
212
|
+
expanded && additionalClasses.push(classes.expanded);
|
|
213
|
+
error && additionalClasses.push(classes.error);
|
|
214
|
+
disabled && additionalClasses.push(classes.disabled);
|
|
215
|
+
className && additionalClasses.push(className);
|
|
216
|
+
|
|
217
|
+
/** The native select is purely for external form libraries. We use it to emit an onChange with native select event object so they know exactly what's happening. */
|
|
218
|
+
return (
|
|
219
|
+
<Fragment>
|
|
220
|
+
<select
|
|
221
|
+
{...filterProps(rest, /^data-/, false)}
|
|
222
|
+
tabIndex={-1}
|
|
223
|
+
aria-hidden="true"
|
|
224
|
+
ref={ref || nativeSelect}
|
|
225
|
+
name={name}
|
|
226
|
+
onChange={nativeOnChangeHandler}
|
|
227
|
+
className={readyclasses['sr-only']}
|
|
228
|
+
>
|
|
229
|
+
<option value=""></option>
|
|
230
|
+
{React.Children.map(children, (child) => (
|
|
231
|
+
<option value={child.props.value}></option>
|
|
232
|
+
))}
|
|
233
|
+
</select>
|
|
234
|
+
<div
|
|
235
|
+
{...filterProps(rest, /^data-/)}
|
|
236
|
+
ref={containerReference}
|
|
237
|
+
className={`custom-select ${classes.select} ${additionalClasses.join(' ')} ${
|
|
238
|
+
className ?? ''
|
|
239
|
+
}`}
|
|
240
|
+
>
|
|
241
|
+
<button
|
|
242
|
+
onClick={() => setExpanded(!expanded)}
|
|
243
|
+
type="button"
|
|
244
|
+
name={name}
|
|
245
|
+
disabled={disabled}
|
|
246
|
+
aria-disabled={disabled}
|
|
247
|
+
aria-invalid={error}
|
|
248
|
+
aria-expanded={expanded}
|
|
249
|
+
aria-haspopup="listbox"
|
|
250
|
+
aria-labelledby={labeledBy}
|
|
251
|
+
aria-describedby={describedBy}
|
|
252
|
+
>
|
|
253
|
+
<div data-display className={classes['selected']}>
|
|
254
|
+
{!value && placeholder && (
|
|
255
|
+
<span className={classes['placeholder']}>{placeholder}</span>
|
|
256
|
+
)}
|
|
257
|
+
{value?.length > 0 && <span>{display}</span>}
|
|
258
|
+
</div>
|
|
259
|
+
<div className={classes['status']}>
|
|
260
|
+
{statusIcon()}
|
|
261
|
+
<Icon className={classes['triangle-down']} icon={Icons.TriangleDown} />
|
|
262
|
+
</div>
|
|
263
|
+
</button>
|
|
264
|
+
<div
|
|
265
|
+
ref={optionListReference}
|
|
266
|
+
className={`list-wrapper ${classes['list-wrapper']}`}
|
|
267
|
+
style={{
|
|
268
|
+
display: expanded ? 'block' : 'none',
|
|
269
|
+
opacity: opacity,
|
|
270
|
+
maxHeight: optionsListMaxHeight,
|
|
271
|
+
...listPosition,
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
{Array.isArray(children) && children.length > 10 && renderSearch()}
|
|
275
|
+
<ul role="listbox" tabIndex={-1}>
|
|
276
|
+
{renderOptions()}
|
|
277
|
+
</ul>
|
|
278
|
+
</div>
|
|
203
279
|
</div>
|
|
204
|
-
</
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
};
|
|
280
|
+
</Fragment>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
.textarea {
|
|
8
8
|
padding: 0.75rem 3.75rem 0.75rem 1.25rem;
|
|
9
|
+
box-sizing: border-box;
|
|
9
10
|
border-color: var(--input-border-color);
|
|
10
11
|
border-style: var(--input-border-style);
|
|
11
12
|
border-width: var(--input-border-width);
|
|
@@ -13,8 +14,8 @@
|
|
|
13
14
|
transition: all 0.2s ease-in-out;
|
|
14
15
|
font-family: var(--font-family);
|
|
15
16
|
font-size: var(--font-size);
|
|
17
|
+
color: var(--greyed-out);
|
|
16
18
|
width: 100%;
|
|
17
|
-
box-sizing: border-box;
|
|
18
19
|
vertical-align: top;
|
|
19
20
|
resize: vertical;
|
|
20
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { Textarea, Props } from './Textarea';
|
|
3
3
|
import { render } from '@testing-library/react';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
@@ -20,6 +20,32 @@ describe('Textarea should render', () => {
|
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
describe('ref should work', () => {
|
|
24
|
+
it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
|
|
25
|
+
const ExampleComponent = ({
|
|
26
|
+
propagateRef,
|
|
27
|
+
}: {
|
|
28
|
+
propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
|
|
29
|
+
}) => {
|
|
30
|
+
const ref = useRef(null);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (ref.current) {
|
|
34
|
+
propagateRef && propagateRef(ref);
|
|
35
|
+
}
|
|
36
|
+
}, [ref]);
|
|
37
|
+
|
|
38
|
+
return <Textarea data-ref="testing" ref={ref} />;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const refCheck = (ref: React.RefObject<HTMLElement>) => {
|
|
42
|
+
expect(ref.current).toHaveAttribute('data-ref', 'testing');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
render(<ExampleComponent propagateRef={refCheck} />);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
23
49
|
describe('Textarea properties', () => {
|
|
24
50
|
it('is disabled', () => {
|
|
25
51
|
const { textarea } = createTextarea({ disabled: true });
|
|
@@ -71,6 +97,6 @@ describe('Error status', () => {
|
|
|
71
97
|
const { textarea } = createTextarea({ error: true });
|
|
72
98
|
|
|
73
99
|
expect(textarea).toHaveClass('error');
|
|
74
|
-
expect(textarea.nextElementSibling).toHaveClass('icon-
|
|
100
|
+
expect(textarea.nextElementSibling).toHaveClass('icon-error-circle');
|
|
75
101
|
});
|
|
76
102
|
});
|
|
@@ -1,33 +1,48 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Icon, Icons } from '../../Icon/Icon';
|
|
3
|
-
import { FormElement } from '../form.interfaces';
|
|
1
|
+
import React, { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { Icon, Props as IconProps, Icons } from '../../Icon/Icon';
|
|
4
3
|
import classes from './Textarea.module.scss';
|
|
4
|
+
import { FormElement } from '../form.interfaces';
|
|
5
|
+
|
|
6
|
+
interface IconPropsPartial extends Omit<Partial<IconProps>, 'ref'> {}
|
|
5
7
|
|
|
6
|
-
export interface Props extends
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export interface Props extends ComponentPropsWithRef<'textarea'>, FormElement {
|
|
9
|
+
wrapperProps?: ComponentPropsWithRef<'div'>;
|
|
10
|
+
errorProps?: IconPropsPartial;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export const Textarea = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
13
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, Props>(
|
|
14
|
+
(
|
|
15
|
+
{
|
|
16
|
+
error = false,
|
|
17
|
+
disabled = false,
|
|
18
|
+
className,
|
|
19
|
+
rows = 4,
|
|
20
|
+
wrapperProps,
|
|
21
|
+
errorProps,
|
|
22
|
+
...rest
|
|
23
|
+
}: Props,
|
|
24
|
+
ref
|
|
25
|
+
) => {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
{...wrapperProps}
|
|
29
|
+
className={`${classes['textarea-wrapper']} ${wrapperProps?.className ?? ''}`}
|
|
30
|
+
>
|
|
31
|
+
<textarea
|
|
32
|
+
{...rest}
|
|
33
|
+
ref={ref}
|
|
34
|
+
rows={rows}
|
|
35
|
+
className={`${error ? classes['error'] : ''} ${classes['textarea']} ${className ?? ''}`}
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
/>
|
|
38
|
+
{error && (
|
|
39
|
+
<Icon
|
|
40
|
+
{...errorProps}
|
|
41
|
+
className={`${classes['warning']} ${errorProps?.className ?? ''}`}
|
|
42
|
+
icon={Icons.Error}
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
);
|
|
@@ -16,6 +16,11 @@ $borderRadius: 2.5rem;
|
|
|
16
16
|
display: block;
|
|
17
17
|
pointer-events: none;
|
|
18
18
|
|
|
19
|
+
&.disabled {
|
|
20
|
+
opacity: 0.25;
|
|
21
|
+
cursor: not-allowed;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
&:before {
|
|
20
25
|
content: '';
|
|
21
26
|
width: 1rem;
|
|
@@ -38,6 +43,10 @@ $borderRadius: 2.5rem;
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
.toggle-helper {
|
|
47
|
+
margin-left: 2.5rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
.checkbox {
|
|
42
51
|
z-index: 10;
|
|
43
52
|
position: static;
|