@onewelcome/react-lib-components 0.1.6-alpha → 0.1.9-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/dist/Button/IconButton.d.ts +2 -1
- package/dist/ContextMenu/ContextMenu.d.ts +2 -3
- package/dist/ContextMenu/ContextMenuItem.d.ts +10 -3
- package/dist/DataGrid/DataGrid.d.ts +32 -0
- package/dist/DataGrid/DataGridActions/DataGridActions.d.ts +14 -0
- package/dist/DataGrid/DataGridActions/DataGridColumnsToggle.d.ts +13 -0
- package/dist/DataGrid/DataGridBody/DataGridBody.d.ts +17 -0
- package/dist/DataGrid/DataGridBody/DataGridCell.d.ts +10 -0
- package/dist/DataGrid/DataGridBody/DataGridRow.d.ts +9 -0
- package/dist/DataGrid/DataGridHeader/DataGridHeader.d.ts +11 -0
- package/dist/DataGrid/DataGridHeader/DataGridHeaderCell.d.ts +10 -0
- package/dist/DataGrid/datagrid.interfaces.d.ts +13 -0
- package/dist/Form/Checkbox/Checkbox.d.ts +2 -2
- package/dist/Form/Select/Option.d.ts +9 -4
- package/dist/Form/Select/Select.d.ts +8 -2
- package/dist/Form/Toggle/Toggle.d.ts +1 -1
- package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +1 -1
- package/dist/Icon/Icon.d.ts +1 -0
- package/dist/Link/Link.d.ts +4 -3
- package/dist/Notifications/BaseModal/BaseModal.d.ts +4 -2
- package/dist/Notifications/SlideInModal/SlideInModal.d.ts +4 -0
- package/dist/StatusIndicator/StatusIndicator.d.ts +9 -0
- package/dist/_BaseStyling_/BaseStyling.d.ts +4 -0
- package/dist/index.d.ts +48 -43
- package/dist/react-lib-components.cjs.development.js +3097 -2157
- 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 +3094 -2158
- package/dist/react-lib-components.esm.js.map +1 -1
- package/package.json +11 -13
- package/src/Button/BaseButton.module.scss +3 -18
- package/src/Button/Button.module.scss +4 -311
- package/src/Button/IconButton.module.scss +21 -128
- package/src/Button/IconButton.test.tsx +24 -0
- package/src/Button/IconButton.tsx +6 -1
- package/src/ContextMenu/ContextMenu.test.tsx +121 -6
- package/src/ContextMenu/ContextMenu.tsx +99 -6
- package/src/ContextMenu/ContextMenuItem.tsx +57 -9
- package/src/DataGrid/DataGrid.module.scss +25 -0
- package/src/DataGrid/DataGrid.test.tsx +421 -0
- package/src/DataGrid/DataGrid.tsx +157 -0
- package/src/DataGrid/DataGridActions/DataGridActions.module.scss +35 -0
- package/src/DataGrid/DataGridActions/DataGridActions.test.tsx +184 -0
- package/src/DataGrid/DataGridActions/DataGridActions.tsx +109 -0
- package/src/DataGrid/DataGridActions/DataGridColumnsToggle.module.scss +41 -0
- package/src/DataGrid/DataGridActions/DataGridColumnsToggle.test.tsx +83 -0
- package/src/DataGrid/DataGridActions/DataGridColumnsToggle.tsx +88 -0
- package/src/DataGrid/DataGridBody/DataGridBody.module.scss +10 -0
- package/src/DataGrid/DataGridBody/DataGridBody.test.tsx +123 -0
- package/src/DataGrid/DataGridBody/DataGridBody.tsx +80 -0
- package/src/DataGrid/DataGridBody/DataGridCell.module.scss +33 -0
- package/src/DataGrid/DataGridBody/DataGridCell.test.tsx +74 -0
- package/src/DataGrid/DataGridBody/DataGridCell.tsx +58 -0
- package/src/DataGrid/DataGridBody/DataGridRow.module.scss +7 -0
- package/src/DataGrid/DataGridBody/DataGridRow.test.tsx +101 -0
- package/src/DataGrid/DataGridBody/DataGridRow.tsx +42 -0
- package/src/DataGrid/DataGridBody/__snapshots__/DataGridBody.test.tsx.snap +258 -0
- package/src/DataGrid/DataGridHeader/DataGridHeader.module.scss +26 -0
- package/src/DataGrid/DataGridHeader/DataGridHeader.test.tsx +255 -0
- package/src/DataGrid/DataGridHeader/DataGridHeader.tsx +103 -0
- package/src/DataGrid/DataGridHeader/DataGridHeaderCell.module.scss +68 -0
- package/src/DataGrid/DataGridHeader/DataGridHeaderCell.test.tsx +128 -0
- package/src/DataGrid/DataGridHeader/DataGridHeaderCell.tsx +72 -0
- package/src/DataGrid/datagrid.interfaces.ts +14 -0
- package/src/Form/Checkbox/Checkbox.test.tsx +144 -8
- package/src/Form/Checkbox/Checkbox.tsx +8 -8
- package/src/Form/Select/Option.tsx +39 -21
- package/src/Form/Select/Select.module.scss +1 -1
- package/src/Form/Select/Select.test.tsx +235 -56
- package/src/Form/Select/Select.tsx +194 -34
- package/src/Form/Toggle/Toggle.module.scss +1 -0
- package/src/Form/Toggle/Toggle.tsx +1 -1
- package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +1 -1
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +44 -0
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +4 -2
- package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +1 -0
- package/src/Icon/Icon.module.scss +4 -0
- package/src/Icon/Icon.tsx +1 -0
- package/src/Link/Link.module.scss +20 -0
- package/src/Link/Link.test.tsx +33 -0
- package/src/Link/Link.tsx +8 -2
- package/src/Notifications/BaseModal/BaseModal.module.scss +1 -1
- package/src/Notifications/BaseModal/BaseModal.test.tsx +77 -12
- package/src/Notifications/BaseModal/BaseModal.tsx +27 -6
- package/src/Notifications/Dialog/Dialog.module.scss +1 -1
- package/src/Notifications/Dialog/Dialog.tsx +1 -1
- package/src/Notifications/SlideInModal/SlideInModal.module.scss +36 -0
- package/src/Notifications/SlideInModal/SlideInModal.test.tsx +69 -0
- package/src/Notifications/SlideInModal/SlideInModal.tsx +31 -0
- package/src/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +1 -1
- package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +1 -1
- package/src/Pagination/Pagination.module.scss +74 -74
- package/src/StatusIndicator/StatusIndicator.module.scss +27 -0
- package/src/StatusIndicator/StatusIndicator.test.tsx +127 -0
- package/src/StatusIndicator/StatusIndicator.tsx +25 -0
- package/src/Tiles/Tile.module.scss +1 -1
- package/src/Tiles/Tile.test.tsx +4 -4
- package/src/_BaseStyling_/BaseStyling.tsx +14 -6
- package/src/index.ts +85 -48
- package/src/mixins.module.scss +171 -0
- package/src/readyclasses.module.scss +0 -30
|
@@ -2,20 +2,22 @@ import classes from './Select.module.scss';
|
|
|
2
2
|
|
|
3
3
|
import React, {
|
|
4
4
|
ComponentPropsWithRef,
|
|
5
|
+
createRef,
|
|
5
6
|
Fragment,
|
|
6
7
|
ReactElement,
|
|
7
|
-
RefObject,
|
|
8
8
|
useEffect,
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
} from 'react';
|
|
12
|
-
import { Input } from '../Input/Input';
|
|
12
|
+
import { Input, Props as InputProps } from '../Input/Input';
|
|
13
13
|
import { Icon, Icons } from '../../Icon/Icon';
|
|
14
14
|
import { FormElement } from '../form.interfaces';
|
|
15
15
|
import { useBodyClick } from '../../hooks/useBodyClick';
|
|
16
16
|
import readyclasses from '../../readyclasses.module.scss';
|
|
17
17
|
import { filterProps } from '../../util/helper';
|
|
18
18
|
|
|
19
|
+
type PartialInputProps = Partial<InputProps>;
|
|
20
|
+
|
|
19
21
|
export interface Props extends ComponentPropsWithRef<'select'>, FormElement {
|
|
20
22
|
children: ReactElement[];
|
|
21
23
|
name?: string;
|
|
@@ -23,10 +25,13 @@ export interface Props extends ComponentPropsWithRef<'select'>, FormElement {
|
|
|
23
25
|
describedBy?: string;
|
|
24
26
|
placeholder?: string;
|
|
25
27
|
searchPlaceholder?: string;
|
|
28
|
+
searchInputProps?: PartialInputProps;
|
|
29
|
+
selectButtonProps?: ComponentPropsWithRef<'button'>;
|
|
26
30
|
className?: string;
|
|
27
31
|
value: string;
|
|
32
|
+
clearLabel?: string;
|
|
28
33
|
onChange?: (event: React.ChangeEvent<HTMLSelectElement>, child?: ReactElement) => void;
|
|
29
|
-
onClear?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
34
|
+
onClear?: (event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
type Position = {
|
|
@@ -34,6 +39,9 @@ type Position = {
|
|
|
34
39
|
bottom: 0 | 'initial';
|
|
35
40
|
};
|
|
36
41
|
|
|
42
|
+
/** Amount of items to be rendered before a search input is rendered */
|
|
43
|
+
const renderSearchCondition = 10;
|
|
44
|
+
|
|
37
45
|
export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
38
46
|
(
|
|
39
47
|
{
|
|
@@ -44,9 +52,12 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
44
52
|
placeholder,
|
|
45
53
|
describedBy,
|
|
46
54
|
searchPlaceholder = 'Search item',
|
|
55
|
+
searchInputProps,
|
|
56
|
+
selectButtonProps,
|
|
47
57
|
className,
|
|
48
58
|
error = false,
|
|
49
59
|
value,
|
|
60
|
+
clearLabel = 'Clear selection',
|
|
50
61
|
onChange,
|
|
51
62
|
onClear,
|
|
52
63
|
...rest
|
|
@@ -61,8 +72,115 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
61
72
|
const [optionsListMaxHeight, setOptionsListMaxHeight] = useState('none');
|
|
62
73
|
const containerReference = useRef<HTMLDivElement>(null);
|
|
63
74
|
const optionListReference = useRef<HTMLDivElement>(null);
|
|
75
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
76
|
+
const [focusedSelectItem, setFocusedSelectItem] = useState(-1);
|
|
77
|
+
const [shouldClick, setShouldClick] =
|
|
78
|
+
useState(
|
|
79
|
+
false
|
|
80
|
+
); /** We need this, because whenever we use the arrow keys to select the select item, and we focus the currently selected item it fires the "click" listener in Option component. Instead, we only want this to fire if we press "enter" or "spacebar" so we set this to true whenever that is the case, and back to false when it has been executed. */
|
|
81
|
+
const [childrenCount] = useState(React.Children.count(children));
|
|
82
|
+
|
|
83
|
+
const nativeSelect = (ref as React.RefObject<HTMLSelectElement>) || createRef();
|
|
84
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
85
|
+
|
|
86
|
+
const onArrowNavigation = (event: React.KeyboardEvent) => {
|
|
87
|
+
const codesToPreventDefault = [
|
|
88
|
+
'ArrowDown',
|
|
89
|
+
'ArrowUp',
|
|
90
|
+
'ArrowLeft',
|
|
91
|
+
'ArrowRight',
|
|
92
|
+
'Space',
|
|
93
|
+
'Escape',
|
|
94
|
+
'End',
|
|
95
|
+
'Home',
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const codesToPreventDefaultWhenSearching = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape'];
|
|
99
|
+
|
|
100
|
+
/** If the select is expanded, we also want to control the Tab key */
|
|
101
|
+
if (expanded) {
|
|
102
|
+
codesToPreventDefault.push('Tab');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** We will handle the way certain key strokes affect the Select, unless we're searching */
|
|
106
|
+
if (codesToPreventDefault.includes(event.code) && !isSearching) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isSearching && codesToPreventDefaultWhenSearching.includes(event.code)) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isSearching) {
|
|
115
|
+
switch (event.code) {
|
|
116
|
+
case 'ArrowDown':
|
|
117
|
+
case 'Enter':
|
|
118
|
+
setIsSearching(false);
|
|
119
|
+
setFocusedSelectItem(0);
|
|
120
|
+
return;
|
|
121
|
+
case 'ArrowUp':
|
|
122
|
+
setIsSearching(false);
|
|
123
|
+
setFocusedSelectItem(childrenCount - 1);
|
|
124
|
+
return;
|
|
125
|
+
case 'Escape':
|
|
126
|
+
case 'Tab':
|
|
127
|
+
setIsSearching(false);
|
|
128
|
+
setExpanded(false);
|
|
129
|
+
containerReference.current &&
|
|
130
|
+
containerReference.current.querySelector('button')!.focus();
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
switch (event.code) {
|
|
134
|
+
case 'ArrowDown':
|
|
135
|
+
if (!expanded) {
|
|
136
|
+
setExpanded(true);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
setFocusedSelectItem((prevState) => {
|
|
140
|
+
return prevState + 1 > childrenCount - 1 ? 0 : prevState + 1;
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
case 'ArrowUp':
|
|
144
|
+
setFocusedSelectItem((prevState) => {
|
|
145
|
+
return prevState - 1 < 0 ? childrenCount - 1 : prevState - 1;
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
case 'Space':
|
|
149
|
+
if (!expanded) {
|
|
150
|
+
setExpanded(true);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setShouldClick(true);
|
|
155
|
+
setExpanded(false);
|
|
156
|
+
containerReference.current &&
|
|
157
|
+
containerReference.current.querySelector('button')!.focus();
|
|
158
|
+
return;
|
|
159
|
+
case 'Tab':
|
|
160
|
+
if (childrenCount >= renderSearchCondition && expanded) {
|
|
161
|
+
setIsSearching(true);
|
|
162
|
+
searchInputRef.current && searchInputRef.current.focus();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
setExpanded(false);
|
|
64
166
|
|
|
65
|
-
|
|
167
|
+
return;
|
|
168
|
+
case 'Escape':
|
|
169
|
+
if (expanded) {
|
|
170
|
+
setExpanded(false);
|
|
171
|
+
containerReference.current &&
|
|
172
|
+
containerReference.current.querySelector('button')!.focus();
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
case 'End':
|
|
176
|
+
setFocusedSelectItem(childrenCount - 1);
|
|
177
|
+
return;
|
|
178
|
+
case 'Home':
|
|
179
|
+
setFocusedSelectItem(0);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
66
184
|
|
|
67
185
|
const syncDisplayValue = (val: string) => {
|
|
68
186
|
React.Children.forEach(children, (child) => {
|
|
@@ -121,39 +239,65 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
121
239
|
setOpacity(100);
|
|
122
240
|
};
|
|
123
241
|
|
|
124
|
-
const onOptionChangeHandler = (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
nativeSelect.current.value = event.currentTarget.dataset.value!;
|
|
242
|
+
const onOptionChangeHandler = (optionRef: React.RefObject<HTMLLIElement>) => {
|
|
243
|
+
if (nativeSelect.current && optionRef.current) {
|
|
244
|
+
nativeSelect.current.value = optionRef.current.getAttribute('data-value')!;
|
|
128
245
|
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
246
|
}
|
|
247
|
+
|
|
135
248
|
setExpanded(false);
|
|
249
|
+
|
|
250
|
+
containerReference.current && containerReference.current.querySelector('button')!.focus();
|
|
136
251
|
};
|
|
137
252
|
|
|
138
253
|
/**
|
|
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.
|
|
254
|
+
* @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 and focused at all times and if a filter is active.
|
|
140
255
|
* The `children` prop can be either a single object (1 child) or an array of multiple children.
|
|
141
256
|
*/
|
|
142
|
-
const renderOptions = () =>
|
|
143
|
-
|
|
144
|
-
React.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
257
|
+
const renderOptions = () => {
|
|
258
|
+
if (isSearching || filter !== '') {
|
|
259
|
+
const filteredChildren = React.Children.toArray(children).filter(
|
|
260
|
+
(child) =>
|
|
261
|
+
(child as ReactElement).props.children.toLowerCase().match(filter.toLowerCase()) !==
|
|
262
|
+
null
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return _internalRenderChildren(filteredChildren as ReactElement[]);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return _internalRenderChildren(children);
|
|
269
|
+
|
|
270
|
+
function _internalRenderChildren(internalChildren: ReactElement[]) {
|
|
271
|
+
return React.Children.map(internalChildren, (child, index) => {
|
|
272
|
+
return React.cloneElement(child, {
|
|
273
|
+
onFocusChange: (childIndex: number) => setFocusedSelectItem(childIndex),
|
|
274
|
+
onOptionSelect: (optionRef: React.RefObject<HTMLLIElement>) => {
|
|
275
|
+
onOptionChangeHandler(optionRef);
|
|
276
|
+
setShouldClick(false);
|
|
277
|
+
},
|
|
278
|
+
isSelected: child.props.value === value,
|
|
279
|
+
isSearching: isSearching,
|
|
280
|
+
selectOpened: expanded,
|
|
281
|
+
childIndex: index,
|
|
282
|
+
hasFocus: focusedSelectItem === index,
|
|
283
|
+
shouldClick: shouldClick,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
};
|
|
150
288
|
|
|
151
289
|
const renderSearch = () => (
|
|
152
290
|
<Input
|
|
291
|
+
{...searchInputProps}
|
|
153
292
|
autoFocus
|
|
293
|
+
ref={searchInputRef}
|
|
294
|
+
onFocus={() => setIsSearching(true)}
|
|
295
|
+
onBlur={() => setIsSearching(false)}
|
|
154
296
|
onChange={filterResults}
|
|
155
297
|
className={classes['select-search']}
|
|
156
|
-
wrapperProps={{
|
|
298
|
+
wrapperProps={{
|
|
299
|
+
className: `${classes['select-search-wrapper']} ${searchInputProps?.wrapperProps?.className}`,
|
|
300
|
+
}}
|
|
157
301
|
type="text"
|
|
158
302
|
name="search-option"
|
|
159
303
|
placeholder={searchPlaceholder}
|
|
@@ -171,16 +315,29 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
171
315
|
|
|
172
316
|
if (value?.length !== 0 && onClear) {
|
|
173
317
|
return (
|
|
174
|
-
<
|
|
175
|
-
|
|
318
|
+
<div
|
|
319
|
+
aria-hidden={false}
|
|
320
|
+
role="button"
|
|
321
|
+
tabIndex={0}
|
|
176
322
|
data-clear
|
|
177
|
-
icon={Icons.TimesThin}
|
|
178
323
|
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
179
324
|
e.preventDefault();
|
|
180
325
|
e.stopPropagation();
|
|
181
326
|
onClear(e);
|
|
182
327
|
}}
|
|
183
|
-
|
|
328
|
+
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
329
|
+
if (e.code === 'Enter' || e.code === 'Space') {
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
e.stopPropagation();
|
|
332
|
+
onClear(e);
|
|
333
|
+
containerReference.current &&
|
|
334
|
+
containerReference.current.querySelector('button')!.focus();
|
|
335
|
+
}
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
<span className={readyclasses['sr-only']}>{clearLabel}</span>
|
|
339
|
+
<Icon tag="span" icon={Icons.TimesThin} />
|
|
340
|
+
</div>
|
|
184
341
|
);
|
|
185
342
|
}
|
|
186
343
|
return null;
|
|
@@ -221,7 +378,7 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
221
378
|
{...filterProps(rest, /^data-/, false)}
|
|
222
379
|
tabIndex={-1}
|
|
223
380
|
aria-hidden="true"
|
|
224
|
-
ref={
|
|
381
|
+
ref={nativeSelect}
|
|
225
382
|
name={name}
|
|
226
383
|
onChange={nativeOnChangeHandler}
|
|
227
384
|
className={readyclasses['sr-only']}
|
|
@@ -234,12 +391,16 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
234
391
|
<div
|
|
235
392
|
{...filterProps(rest, /^data-/)}
|
|
236
393
|
ref={containerReference}
|
|
394
|
+
onKeyDown={onArrowNavigation}
|
|
237
395
|
className={`custom-select ${classes.select} ${additionalClasses.join(' ')} ${
|
|
238
396
|
className ?? ''
|
|
239
397
|
}`}
|
|
240
398
|
>
|
|
241
399
|
<button
|
|
242
|
-
|
|
400
|
+
{...selectButtonProps}
|
|
401
|
+
onClick={() => {
|
|
402
|
+
setExpanded(!expanded);
|
|
403
|
+
}}
|
|
243
404
|
type="button"
|
|
244
405
|
name={name}
|
|
245
406
|
disabled={disabled}
|
|
@@ -249,12 +410,13 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
249
410
|
aria-haspopup="listbox"
|
|
250
411
|
aria-labelledby={labeledBy}
|
|
251
412
|
aria-describedby={describedBy}
|
|
413
|
+
className={classes['custom-select']}
|
|
252
414
|
>
|
|
253
415
|
<div data-display className={classes['selected']}>
|
|
254
416
|
{!value && placeholder && (
|
|
255
417
|
<span className={classes['placeholder']}>{placeholder}</span>
|
|
256
418
|
)}
|
|
257
|
-
{value?.length > 0 && <span>{display}</span>}
|
|
419
|
+
{value?.length > 0 && <span data-display-inner>{display}</span>}
|
|
258
420
|
</div>
|
|
259
421
|
<div className={classes['status']}>
|
|
260
422
|
{statusIcon()}
|
|
@@ -271,10 +433,8 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
|
|
|
271
433
|
...listPosition,
|
|
272
434
|
}}
|
|
273
435
|
>
|
|
274
|
-
{Array.isArray(children) && children.length >
|
|
275
|
-
<ul role="listbox"
|
|
276
|
-
{renderOptions()}
|
|
277
|
-
</ul>
|
|
436
|
+
{Array.isArray(children) && children.length > renderSearchCondition && renderSearch()}
|
|
437
|
+
<ul role="listbox">{renderOptions()}</ul>
|
|
278
438
|
</div>
|
|
279
439
|
</div>
|
|
280
440
|
</Fragment>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { CheckboxWrapper, Props } from './CheckboxWrapper';
|
|
3
|
-
import { Checkbox, CheckboxProps } from '../../Checkbox/Checkbox';
|
|
3
|
+
import { Checkbox, Props as CheckboxProps } from '../../Checkbox/Checkbox';
|
|
4
4
|
import { render } from '@testing-library/react';
|
|
5
5
|
|
|
6
6
|
const defaultParentParams: CheckboxProps = {
|
|
@@ -5,6 +5,7 @@ import { Option } from '../../Select/Option';
|
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
|
|
7
7
|
const onChangeHandler = jest.fn();
|
|
8
|
+
const onClearHandler = jest.fn();
|
|
8
9
|
|
|
9
10
|
const defaultParams: Props = {
|
|
10
11
|
children: [
|
|
@@ -29,6 +30,7 @@ const defaultParams: Props = {
|
|
|
29
30
|
error: false,
|
|
30
31
|
value: 'option1',
|
|
31
32
|
onChange: onChangeHandler,
|
|
33
|
+
onClear: onClearHandler,
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
const createSelectWrapper = (params?: (defaultParams: Props) => Props) => {
|
|
@@ -117,6 +119,32 @@ describe('SelectWrapper & Select have the right attributes', () => {
|
|
|
117
119
|
expect(helpertext).toHaveTextContent('helpertext');
|
|
118
120
|
});
|
|
119
121
|
|
|
122
|
+
it('Passes the proper helperProps class', () => {
|
|
123
|
+
const { getByTestId } = createSelectWrapper((defaultParams) => ({
|
|
124
|
+
...defaultParams,
|
|
125
|
+
helperProps: { ...defaultParams.helperProps, className: 'example-helper-classname' },
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const helpertext = getByTestId('helpertext');
|
|
129
|
+
|
|
130
|
+
expect(helpertext.parentElement).toHaveClass('example-helper-classname');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('Passes the proper selectProps class', () => {
|
|
134
|
+
const { getByTestId } = createSelectWrapper((defaultParams) => ({
|
|
135
|
+
...defaultParams,
|
|
136
|
+
selectProps: {
|
|
137
|
+
...defaultParams.selectProps,
|
|
138
|
+
'data-testid': 'select-element',
|
|
139
|
+
className: 'example-select-classname',
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
const select = getByTestId('select-element');
|
|
144
|
+
|
|
145
|
+
expect(select).toHaveClass('example-select-classname');
|
|
146
|
+
});
|
|
147
|
+
|
|
120
148
|
it('SelectWrapper has the right errormessage', async () => {
|
|
121
149
|
const { findByText, select } = createSelectWrapper((defaultParams) => ({
|
|
122
150
|
...defaultParams,
|
|
@@ -140,4 +168,20 @@ describe('SelectWrapper & Select have the right attributes', () => {
|
|
|
140
168
|
|
|
141
169
|
expect(onChangeHandler).toHaveBeenCalled();
|
|
142
170
|
});
|
|
171
|
+
|
|
172
|
+
it('Fires the onClear event', async () => {
|
|
173
|
+
const { select, findByText } = createSelectWrapper();
|
|
174
|
+
|
|
175
|
+
userEvent.click(select as Element);
|
|
176
|
+
|
|
177
|
+
const option3 = await findByText('Option 3');
|
|
178
|
+
|
|
179
|
+
userEvent.click(option3 as Element);
|
|
180
|
+
|
|
181
|
+
const clearButton = select!.querySelector('[data-clear]')!;
|
|
182
|
+
|
|
183
|
+
userEvent.click(clearButton);
|
|
184
|
+
|
|
185
|
+
expect(onClearHandler).toHaveBeenCalled();
|
|
186
|
+
});
|
|
143
187
|
});
|
|
@@ -15,7 +15,7 @@ export interface Props
|
|
|
15
15
|
error?: boolean;
|
|
16
16
|
selectProps?: PartialSelectProps;
|
|
17
17
|
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
18
|
-
onClear?: () => void;
|
|
18
|
+
onClear?: (event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export const SelectWrapper = React.forwardRef<HTMLDivElement, Props>(
|
|
@@ -56,7 +56,9 @@ export const SelectWrapper = React.forwardRef<HTMLDivElement, Props>(
|
|
|
56
56
|
error={error}
|
|
57
57
|
describedBy={error ? errorId : helperId}
|
|
58
58
|
onChange={onChange}
|
|
59
|
-
onClear={
|
|
59
|
+
onClear={(e) => {
|
|
60
|
+
onClear && onClear(e);
|
|
61
|
+
}}
|
|
60
62
|
placeholder={placeholder}
|
|
61
63
|
className={`${floatingLabelActive ? classes['floating-label-active'] : ''} ${
|
|
62
64
|
selectProps?.className ?? ''
|
package/src/Icon/Icon.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import '../mixins.module.scss';
|
|
2
|
+
|
|
1
3
|
.link {
|
|
2
4
|
font-family: var(--font-family);
|
|
3
5
|
font-size: var(--font-size);
|
|
@@ -44,3 +46,21 @@
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
.button {
|
|
51
|
+
@include buttonBase('link');
|
|
52
|
+
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
|
|
55
|
+
&.fill {
|
|
56
|
+
@include button('fill', 'link');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.outline {
|
|
60
|
+
@include button('outline', 'link');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&.text {
|
|
64
|
+
@include button('text', 'link');
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/Link/Link.test.tsx
CHANGED
|
@@ -99,6 +99,39 @@ describe('Link should render', () => {
|
|
|
99
99
|
|
|
100
100
|
expect(link).toHaveClass('classname');
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
it('should render as a filled button ', () => {
|
|
104
|
+
const { link } = createLink((defaultParams) => ({
|
|
105
|
+
...defaultParams,
|
|
106
|
+
display: 'button',
|
|
107
|
+
buttonVariant: 'fill',
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
expect(link).toHaveClass('button');
|
|
111
|
+
expect(link).toHaveClass('fill');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should render as a text button ', () => {
|
|
115
|
+
const { link } = createLink((defaultParams) => ({
|
|
116
|
+
...defaultParams,
|
|
117
|
+
display: 'button',
|
|
118
|
+
buttonVariant: 'text',
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
expect(link).toHaveClass('button');
|
|
122
|
+
expect(link).toHaveClass('text');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should render as an outline button ', () => {
|
|
126
|
+
const { link } = createLink((defaultParams) => ({
|
|
127
|
+
...defaultParams,
|
|
128
|
+
display: 'button',
|
|
129
|
+
buttonVariant: 'outline',
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
expect(link).toHaveClass('button');
|
|
133
|
+
expect(link).toHaveClass('outline');
|
|
134
|
+
});
|
|
102
135
|
});
|
|
103
136
|
|
|
104
137
|
describe('ref should work', () => {
|
package/src/Link/Link.tsx
CHANGED
|
@@ -7,11 +7,13 @@ import React, {
|
|
|
7
7
|
import classes from './Link.module.scss';
|
|
8
8
|
import { LinkProps } from './types';
|
|
9
9
|
|
|
10
|
-
type AnchorType = 'external' | 'internal' | 'download';
|
|
10
|
+
export type AnchorType = 'external' | 'internal' | 'download';
|
|
11
11
|
|
|
12
12
|
export interface Props extends ComponentPropsWithRef<'a'> {
|
|
13
13
|
children?: ReactNode;
|
|
14
14
|
color?: 'primary' | 'secondary' | 'tertiary';
|
|
15
|
+
display?: 'link' | 'button';
|
|
16
|
+
buttonVariant?: 'outline' | 'text' | 'fill';
|
|
15
17
|
type?: AnchorType;
|
|
16
18
|
to: string;
|
|
17
19
|
disabled?: boolean;
|
|
@@ -27,6 +29,8 @@ export const Link = React.forwardRef<HTMLAnchorElement, Props>(
|
|
|
27
29
|
to,
|
|
28
30
|
color = 'primary',
|
|
29
31
|
type = 'internal',
|
|
32
|
+
display = 'link',
|
|
33
|
+
buttonVariant = 'fill',
|
|
30
34
|
component,
|
|
31
35
|
...rest
|
|
32
36
|
}: Props,
|
|
@@ -44,7 +48,9 @@ export const Link = React.forwardRef<HTMLAnchorElement, Props>(
|
|
|
44
48
|
return '';
|
|
45
49
|
};
|
|
46
50
|
|
|
47
|
-
const classNames = [classes[
|
|
51
|
+
const classNames = [classes[color]];
|
|
52
|
+
display === 'link' && classNames.push(classes['link']);
|
|
53
|
+
display === 'button' && classNames.push(classes['button'], classes[buttonVariant]);
|
|
48
54
|
disabled && classNames.push(classes['disabled']);
|
|
49
55
|
className && classNames.push(className);
|
|
50
56
|
|