@ultraviolet/ui 1.47.0 → 1.48.0
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/index.d.ts +127 -3
- package/dist/src/components/Checkbox/index.js +4 -2
- package/dist/src/components/MenuV2/index.js +2 -2
- package/dist/src/components/SelectInput/index.js +1 -0
- package/dist/src/components/SelectInputV2/Dropdown.js +595 -0
- package/dist/src/components/SelectInputV2/DropdownOption.js +135 -0
- package/dist/src/components/SelectInputV2/SearchBarDropdown.js +119 -0
- package/dist/src/components/SelectInputV2/SelectBar.js +269 -0
- package/dist/src/components/SelectInputV2/SelectInputProvider.js +153 -0
- package/dist/src/components/SelectInputV2/index.js +138 -0
- package/dist/src/components/SelectInputV2/types.js +11 -0
- package/dist/src/components/TextInputV2/index.js +4 -2
- package/dist/src/index.js +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import _styled from '@emotion/styled/base';
|
|
2
|
+
import { Stack } from '../Stack/index.js';
|
|
3
|
+
import { Text } from '../Text/index.js';
|
|
4
|
+
import { jsx, jsxs } from '@emotion/react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
|
|
7
|
+
const StyledInfo = /*#__PURE__*/_styled("div", {
|
|
8
|
+
target: "e18yw4y80"
|
|
9
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
10
|
+
name: "13vy23x",
|
|
11
|
+
styles: "align-content:center"
|
|
12
|
+
} : {
|
|
13
|
+
name: "13vy23x",
|
|
14
|
+
styles: "align-content:center",
|
|
15
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
16
|
+
});
|
|
17
|
+
const DisplayOption = ({
|
|
18
|
+
option,
|
|
19
|
+
multiselect,
|
|
20
|
+
optionalInfoPlacement,
|
|
21
|
+
descriptionDirection
|
|
22
|
+
}) => {
|
|
23
|
+
if (descriptionDirection === 'row' && optionalInfoPlacement === 'left') {
|
|
24
|
+
return jsx(Stack, {
|
|
25
|
+
gap: 0.5,
|
|
26
|
+
direction: "row",
|
|
27
|
+
justifyContent: "left",
|
|
28
|
+
onClick: event => multiselect ? event.stopPropagation() : null,
|
|
29
|
+
"data-testid": `option-stack-${option.value}`,
|
|
30
|
+
children: jsxs(Stack, {
|
|
31
|
+
gap: 0.5,
|
|
32
|
+
direction: "row",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
children: [option.optionalInfo ?? null, jsx(Text, {
|
|
35
|
+
as: "span",
|
|
36
|
+
variant: "body",
|
|
37
|
+
placement: "left",
|
|
38
|
+
children: option.label
|
|
39
|
+
}), option.description ? jsx(Text, {
|
|
40
|
+
as: "span",
|
|
41
|
+
variant: "bodySmall",
|
|
42
|
+
sentiment: "neutral",
|
|
43
|
+
placement: "left",
|
|
44
|
+
prominence: "weak",
|
|
45
|
+
children: option.description
|
|
46
|
+
}) : null]
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (descriptionDirection === 'row' && optionalInfoPlacement === 'right') {
|
|
51
|
+
return jsxs(Stack, {
|
|
52
|
+
gap: 0.5,
|
|
53
|
+
direction: "row",
|
|
54
|
+
justifyContent: "space-between",
|
|
55
|
+
onClick: event => multiselect ? event.stopPropagation() : null,
|
|
56
|
+
alignItems: "baseline",
|
|
57
|
+
"data-testid": `option-stack-${option.value}`,
|
|
58
|
+
children: [jsxs(Stack, {
|
|
59
|
+
gap: 0.5,
|
|
60
|
+
direction: "row",
|
|
61
|
+
children: [jsx(Text, {
|
|
62
|
+
as: "span",
|
|
63
|
+
variant: "body",
|
|
64
|
+
placement: "left",
|
|
65
|
+
children: option.label
|
|
66
|
+
}), option.description ? jsx(Text, {
|
|
67
|
+
as: "span",
|
|
68
|
+
variant: "bodySmall",
|
|
69
|
+
sentiment: "neutral",
|
|
70
|
+
placement: "left",
|
|
71
|
+
prominence: "weak",
|
|
72
|
+
children: option.description
|
|
73
|
+
}) : null]
|
|
74
|
+
}), option.optionalInfo ? jsx(StyledInfo, {
|
|
75
|
+
children: option.optionalInfo
|
|
76
|
+
}) : null]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (descriptionDirection === 'column' && optionalInfoPlacement === 'left') {
|
|
80
|
+
return jsxs(Stack, {
|
|
81
|
+
gap: 0.5,
|
|
82
|
+
direction: "row",
|
|
83
|
+
justifyContent: option.optionalInfo ? 'left' : 'space-between',
|
|
84
|
+
alignItems: "normal",
|
|
85
|
+
children: [option.optionalInfo ?? null, jsxs(Stack, {
|
|
86
|
+
gap: 0.5,
|
|
87
|
+
direction: "column",
|
|
88
|
+
onClick: event => multiselect ? event.stopPropagation() : null,
|
|
89
|
+
"data-testid": `option-stack-${option.value}`,
|
|
90
|
+
children: [jsx(Text, {
|
|
91
|
+
as: "span",
|
|
92
|
+
variant: "body",
|
|
93
|
+
placement: "left",
|
|
94
|
+
children: option.label
|
|
95
|
+
}), option.description ? jsx(Text, {
|
|
96
|
+
as: "span",
|
|
97
|
+
variant: "bodySmall",
|
|
98
|
+
sentiment: "neutral",
|
|
99
|
+
placement: "left",
|
|
100
|
+
prominence: "weak",
|
|
101
|
+
children: option.description
|
|
102
|
+
}) : null]
|
|
103
|
+
})]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return jsxs(Stack, {
|
|
107
|
+
gap: 0.5,
|
|
108
|
+
direction: "column",
|
|
109
|
+
alignItems: "normal",
|
|
110
|
+
onClick: event => multiselect ? event.stopPropagation() : null,
|
|
111
|
+
"data-testid": `option-stack-${option.value}`,
|
|
112
|
+
children: [jsxs(Stack, {
|
|
113
|
+
gap: 0.5,
|
|
114
|
+
direction: "row",
|
|
115
|
+
justifyContent: "space-between",
|
|
116
|
+
children: [jsx(Text, {
|
|
117
|
+
as: "span",
|
|
118
|
+
variant: "body",
|
|
119
|
+
placement: "left",
|
|
120
|
+
children: option.label
|
|
121
|
+
}), option.optionalInfo ? jsx(StyledInfo, {
|
|
122
|
+
children: option.optionalInfo
|
|
123
|
+
}) : null]
|
|
124
|
+
}), option.description ? jsx(Text, {
|
|
125
|
+
as: "span",
|
|
126
|
+
variant: "bodySmall",
|
|
127
|
+
sentiment: "neutral",
|
|
128
|
+
placement: "left",
|
|
129
|
+
prominence: "weak",
|
|
130
|
+
children: option.description
|
|
131
|
+
}) : null]
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export { DisplayOption };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import _styled from '@emotion/styled/base';
|
|
2
|
+
import { Icon } from '@ultraviolet/icons';
|
|
3
|
+
import { TextInputV2 } from '../TextInputV2/index.js';
|
|
4
|
+
import { useSelectInput } from './SelectInputProvider.js';
|
|
5
|
+
import { jsx } from '@emotion/react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
const StyledInput = /*#__PURE__*/_styled(TextInputV2, {
|
|
8
|
+
target: "e1ag0nf0"
|
|
9
|
+
})("padding-top:", ({
|
|
10
|
+
theme
|
|
11
|
+
}) => theme.space[1.5], ";padding-bottom:", ({
|
|
12
|
+
theme
|
|
13
|
+
}) => theme.space[1.5], ";padding-left:", ({
|
|
14
|
+
theme
|
|
15
|
+
}) => theme.space[2], ";padding-right:", ({
|
|
16
|
+
theme
|
|
17
|
+
}) => theme.space[2], ";");
|
|
18
|
+
const findClosestOption = (options, searchInput) => {
|
|
19
|
+
if (searchInput) {
|
|
20
|
+
if (!Array.isArray(options)) {
|
|
21
|
+
const possibleOptions = {
|
|
22
|
+
...options
|
|
23
|
+
};
|
|
24
|
+
Object.keys(possibleOptions).map(group => {
|
|
25
|
+
possibleOptions[group] = possibleOptions[group].filter(option => !option.disabled);
|
|
26
|
+
return null;
|
|
27
|
+
});
|
|
28
|
+
if (Object.keys(possibleOptions).some(group => possibleOptions[group].length > 0)) {
|
|
29
|
+
const firstFit = Object.keys(possibleOptions).map(group => possibleOptions[group][0]).filter(value => !!value)[0];
|
|
30
|
+
return firstFit;
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
const possibleOptions = [...options].filter(option => !option.disabled);
|
|
34
|
+
if (possibleOptions.length > 0) {
|
|
35
|
+
return possibleOptions[0];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
const SearchBarDropdown = ({
|
|
42
|
+
placeholder,
|
|
43
|
+
displayedOptions,
|
|
44
|
+
setSearchBarActive,
|
|
45
|
+
onChange
|
|
46
|
+
}) => {
|
|
47
|
+
const {
|
|
48
|
+
onSearch,
|
|
49
|
+
setSearchInput,
|
|
50
|
+
searchInput,
|
|
51
|
+
options,
|
|
52
|
+
multiselect,
|
|
53
|
+
setSelectedData,
|
|
54
|
+
selectedData
|
|
55
|
+
} = useSelectInput();
|
|
56
|
+
const handleChange = search => {
|
|
57
|
+
if (search.length > 0) {
|
|
58
|
+
// case insensitive search
|
|
59
|
+
const regex = RegExp(search, 'i');
|
|
60
|
+
if (!Array.isArray(options)) {
|
|
61
|
+
const filteredOptions = {
|
|
62
|
+
...options
|
|
63
|
+
};
|
|
64
|
+
Object.keys(filteredOptions).map(group => {
|
|
65
|
+
filteredOptions[group] = filteredOptions[group].filter(option => option.searchText ? option.searchText.match(regex) : option.value.match(regex));
|
|
66
|
+
return null;
|
|
67
|
+
});
|
|
68
|
+
onSearch(filteredOptions);
|
|
69
|
+
} else {
|
|
70
|
+
const filteredOptions = [...options].filter(option => option.searchText ? option.searchText.match(regex) : option.value.match(regex));
|
|
71
|
+
onSearch(filteredOptions);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
onSearch(options);
|
|
75
|
+
}
|
|
76
|
+
setSearchInput(search);
|
|
77
|
+
};
|
|
78
|
+
const handleKeyDown = (key, search) => {
|
|
79
|
+
if (key === 'Enter') {
|
|
80
|
+
const closestOption = findClosestOption(displayedOptions, search);
|
|
81
|
+
if (closestOption) {
|
|
82
|
+
if (multiselect) {
|
|
83
|
+
setSelectedData({
|
|
84
|
+
type: 'selectOption',
|
|
85
|
+
clickedOption: closestOption,
|
|
86
|
+
group: !Array.isArray(options) ? Object.keys(options).filter(group => options[group].includes(closestOption))[0] : undefined
|
|
87
|
+
});
|
|
88
|
+
onChange?.(selectedData.selectedValues.includes(closestOption) ? selectedData.selectedValues.map(val => val?.value) : [...selectedData.selectedValues, closestOption].map(val => val?.value));
|
|
89
|
+
setSearchInput(closestOption.searchText ?? closestOption.value);
|
|
90
|
+
} else {
|
|
91
|
+
setSelectedData({
|
|
92
|
+
type: 'selectOption',
|
|
93
|
+
clickedOption: closestOption
|
|
94
|
+
});
|
|
95
|
+
onChange?.(selectedData.selectedValues.map(val => val?.value));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return jsx(StyledInput, {
|
|
101
|
+
value: searchInput,
|
|
102
|
+
onChange: event => handleChange(event),
|
|
103
|
+
placeholder: placeholder,
|
|
104
|
+
onFocus: () => setSearchBarActive(true),
|
|
105
|
+
onBlur: () => setSearchBarActive(false),
|
|
106
|
+
"data-testid": "search-bar",
|
|
107
|
+
prefix: jsx(Icon, {
|
|
108
|
+
name: "search",
|
|
109
|
+
size: "small",
|
|
110
|
+
sentiment: "neutral"
|
|
111
|
+
}),
|
|
112
|
+
onKeyDown: event => handleKeyDown(event.key, searchInput),
|
|
113
|
+
autoFocus: true,
|
|
114
|
+
size: "medium",
|
|
115
|
+
"aria-label": "search-bar"
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export { SearchBarDropdown };
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import _styled from '@emotion/styled/base';
|
|
2
|
+
import { Icon } from '@ultraviolet/icons';
|
|
3
|
+
import { useRef, useState, useMemo, useEffect } from 'react';
|
|
4
|
+
import { Button } from '../Button/index.js';
|
|
5
|
+
import { Stack } from '../Stack/index.js';
|
|
6
|
+
import { Tag } from '../Tag/index.js';
|
|
7
|
+
import { Text } from '../Text/index.js';
|
|
8
|
+
import { useSelectInput } from './SelectInputProvider.js';
|
|
9
|
+
import { INPUT_SIZE_HEIGHT, SIZES_TAG } from './types.js';
|
|
10
|
+
import { jsxs, jsx } from '@emotion/react/jsx-runtime';
|
|
11
|
+
|
|
12
|
+
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
|
|
13
|
+
const StateStack = /*#__PURE__*/_styled(Stack, {
|
|
14
|
+
target: "ejy0aca3"
|
|
15
|
+
})("padding-right:", ({
|
|
16
|
+
theme
|
|
17
|
+
}) => theme.space['2'], ";display:flex;");
|
|
18
|
+
const StyledInputWrapper = /*#__PURE__*/_styled(Stack, {
|
|
19
|
+
target: "ejy0aca2"
|
|
20
|
+
})("display:flex;padding:", ({
|
|
21
|
+
theme
|
|
22
|
+
}) => theme.space[1], ";padding-right:0;padding-left:", ({
|
|
23
|
+
theme
|
|
24
|
+
}) => theme.space[2], ";cursor:pointer;box-shadow:none;background:", ({
|
|
25
|
+
theme
|
|
26
|
+
}) => theme.colors.neutral.background, ";border-radius:", ({
|
|
27
|
+
theme
|
|
28
|
+
}) => theme.radii.default, ";&:hover,:focus{border-color:", ({
|
|
29
|
+
theme
|
|
30
|
+
}) => theme.colors.primary.borderHover, ";outline:none;}&[data-readonly='true']{background:", ({
|
|
31
|
+
theme
|
|
32
|
+
}) => theme.colors.neutral.backgroundWeak, ";border-color:", ({
|
|
33
|
+
theme
|
|
34
|
+
}) => theme.colors.neutral.border, ";cursor:default;}&[data-disabled='true']{background:", ({
|
|
35
|
+
theme
|
|
36
|
+
}) => theme.colors.neutral.backgroundDisabled, ";border-color:", ({
|
|
37
|
+
theme
|
|
38
|
+
}) => theme.colors.neutral.borderDisabled, ";cursor:not-allowed;}&[data-size='small']{height:", INPUT_SIZE_HEIGHT.small, "px;}&[data-size='medium']{height:", INPUT_SIZE_HEIGHT.medium, "px;}&[data-size='larger']{height:", INPUT_SIZE_HEIGHT.large, "px;}&[data-state='neutral']{border:1px solid ", ({
|
|
39
|
+
theme
|
|
40
|
+
}) => theme.colors.neutral.border, ";}&[data-state='success']{border:1px solid ", ({
|
|
41
|
+
theme
|
|
42
|
+
}) => theme.colors.success.border, ";}&[data-state='danger']{border:1px solid ", ({
|
|
43
|
+
theme
|
|
44
|
+
}) => theme.colors.danger.border, ";}&:not([data-disabled='true']):not([data-readonly]):hover{border-color:", ({
|
|
45
|
+
theme
|
|
46
|
+
}) => theme.colors.primary.border, ";}&:not([data-disabled='true']):not([data-readonly]):active{box-shadow:", ({
|
|
47
|
+
theme
|
|
48
|
+
}) => theme.shadows.focusPrimary, ";}&[data-dropdownvisible='true']{box-shadow:", ({
|
|
49
|
+
theme
|
|
50
|
+
}) => theme.shadows.focusPrimary, ";border-color:", ({
|
|
51
|
+
theme
|
|
52
|
+
}) => theme.colors.primary.borderHover, ";}");
|
|
53
|
+
const CustomTag = /*#__PURE__*/_styled(Tag, {
|
|
54
|
+
target: "ejy0aca1"
|
|
55
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
56
|
+
name: "1snt5jp",
|
|
57
|
+
styles: "height:fit-content;width:fit-content"
|
|
58
|
+
} : {
|
|
59
|
+
name: "1snt5jp",
|
|
60
|
+
styles: "height:fit-content;width:fit-content",
|
|
61
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
62
|
+
});
|
|
63
|
+
const StyledPlaceholder = /*#__PURE__*/_styled(Text, {
|
|
64
|
+
target: "ejy0aca0"
|
|
65
|
+
})("color:", ({
|
|
66
|
+
theme
|
|
67
|
+
}) => theme.colors.neutral.textWeak, ";text-size:", ({
|
|
68
|
+
theme
|
|
69
|
+
}) => theme.typography.body.fontSize, ";display:flex;flex:1;align-self:center;&[data-disabled='true']{color:", ({
|
|
70
|
+
theme
|
|
71
|
+
}) => theme.colors.neutral.textWeakDisabled, ";}");
|
|
72
|
+
const isValidSelectedValue = (selectedValue, options) => !Array.isArray(options) ? Object.keys(options).some(group => options[group].some(option => option === selectedValue && !option.disabled)) : options.some(option => option === selectedValue && !option.disabled);
|
|
73
|
+
const DisplayValues = ({
|
|
74
|
+
multiselect,
|
|
75
|
+
refTag,
|
|
76
|
+
nonOverflowedValues,
|
|
77
|
+
disabled,
|
|
78
|
+
readOnly,
|
|
79
|
+
onChange,
|
|
80
|
+
overflowed,
|
|
81
|
+
overflowAmount,
|
|
82
|
+
setSelectedData,
|
|
83
|
+
selectedData,
|
|
84
|
+
size
|
|
85
|
+
}) => multiselect ? jsxs(Stack, {
|
|
86
|
+
direction: "row",
|
|
87
|
+
gap: "1",
|
|
88
|
+
wrap: "nowrap",
|
|
89
|
+
ref: refTag,
|
|
90
|
+
alignItems: "center",
|
|
91
|
+
children: [nonOverflowedValues.map(option => jsx(CustomTag, {
|
|
92
|
+
"data-testid": "selected-options-tags",
|
|
93
|
+
sentiment: "neutral",
|
|
94
|
+
disabled: disabled,
|
|
95
|
+
onClose: !readOnly ? event => {
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
setSelectedData({
|
|
98
|
+
type: 'selectOption',
|
|
99
|
+
clickedOption: option
|
|
100
|
+
});
|
|
101
|
+
const newSelectedValues = selectedData.selectedValues?.filter(val => val !== option);
|
|
102
|
+
onChange?.(newSelectedValues.map(val => val?.value));
|
|
103
|
+
} : undefined,
|
|
104
|
+
children: option?.label
|
|
105
|
+
}, option?.value)), overflowed ? jsxs(Tag, {
|
|
106
|
+
sentiment: "neutral",
|
|
107
|
+
disabled: disabled,
|
|
108
|
+
"data-testid": "plus-tag",
|
|
109
|
+
"aria-label": "Plus tag",
|
|
110
|
+
children: [jsx(Icon, {
|
|
111
|
+
name: "plus"
|
|
112
|
+
}), overflowAmount]
|
|
113
|
+
}, "+") : null]
|
|
114
|
+
}) : jsx(Text, {
|
|
115
|
+
as: "p",
|
|
116
|
+
variant: size === 'large' ? 'body' : 'bodySmall',
|
|
117
|
+
children: selectedData.selectedValues[0]?.label
|
|
118
|
+
});
|
|
119
|
+
const SelectBar = ({
|
|
120
|
+
size,
|
|
121
|
+
clearable,
|
|
122
|
+
disabled,
|
|
123
|
+
readOnly,
|
|
124
|
+
placeholder,
|
|
125
|
+
success,
|
|
126
|
+
error,
|
|
127
|
+
onChange,
|
|
128
|
+
autoFocus,
|
|
129
|
+
innerRef
|
|
130
|
+
}) => {
|
|
131
|
+
const {
|
|
132
|
+
isDropdownVisible,
|
|
133
|
+
setIsDropdownVisible,
|
|
134
|
+
options,
|
|
135
|
+
multiselect,
|
|
136
|
+
selectedData,
|
|
137
|
+
setSelectedData
|
|
138
|
+
} = useSelectInput();
|
|
139
|
+
const openable = !(readOnly || disabled);
|
|
140
|
+
const refTag = useRef(null);
|
|
141
|
+
const width = innerRef.current?.offsetWidth;
|
|
142
|
+
const [overflowed, setOverflowed] = useState(false);
|
|
143
|
+
const [overflowAmount, setOverflowAmount] = useState(0);
|
|
144
|
+
const [nonOverflowedValues, setNonOverFlowedValues] = useState(selectedData.selectedValues[0] ? [selectedData.selectedValues[0]] : []);
|
|
145
|
+
const state = useMemo(() => {
|
|
146
|
+
if (error) {
|
|
147
|
+
return 'danger';
|
|
148
|
+
}
|
|
149
|
+
if (success) {
|
|
150
|
+
return 'success';
|
|
151
|
+
}
|
|
152
|
+
return 'neutral';
|
|
153
|
+
}, [error, success]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
// When too many items are selected, too avoid overflow, compute the number of tags to display and add a + tag
|
|
156
|
+
let tagsWidth = 0;
|
|
157
|
+
let computedOverflowAmount = 0;
|
|
158
|
+
let computedNonOverflowedValues = [];
|
|
159
|
+
const newSelectedValues = selectedData.selectedValues.filter(selectedValue => isValidSelectedValue(selectedValue, options));
|
|
160
|
+
for (const selectedValue of newSelectedValues) {
|
|
161
|
+
if (selectedValue && selectedValue.label && width && isValidSelectedValue(selectedValue, options)) {
|
|
162
|
+
const lengthValue = selectedValue.value.length; // Find a better way to find the number of displayed characters?
|
|
163
|
+
const totalTagWidth = SIZES_TAG.tagWidth + SIZES_TAG.letterWidth * lengthValue;
|
|
164
|
+
if (totalTagWidth + tagsWidth > width - 100) {
|
|
165
|
+
computedOverflowAmount += 1;
|
|
166
|
+
setOverflowAmount(computedOverflowAmount);
|
|
167
|
+
} else {
|
|
168
|
+
computedNonOverflowedValues = [...computedNonOverflowedValues, selectedValue];
|
|
169
|
+
setNonOverFlowedValues(computedNonOverflowedValues);
|
|
170
|
+
tagsWidth += totalTagWidth;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (computedOverflowAmount === 0) {
|
|
175
|
+
setOverflowed(false);
|
|
176
|
+
} else {
|
|
177
|
+
setOverflowed(true);
|
|
178
|
+
}
|
|
179
|
+
setOverflowAmount(computedOverflowAmount);
|
|
180
|
+
}, [options, selectedData.selectedValues, width]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
setSelectedData({
|
|
183
|
+
type: 'update'
|
|
184
|
+
});
|
|
185
|
+
}, [setSelectedData, options]);
|
|
186
|
+
return jsxs(StyledInputWrapper, {
|
|
187
|
+
"data-disabled": disabled,
|
|
188
|
+
"data-readonly": readOnly,
|
|
189
|
+
"data-size": size,
|
|
190
|
+
"data-dropdownvisible": isDropdownVisible,
|
|
191
|
+
"data-state": state,
|
|
192
|
+
direction: "row",
|
|
193
|
+
wrap: "nowrap",
|
|
194
|
+
gap: "1",
|
|
195
|
+
justifyContent: "space-between",
|
|
196
|
+
alignItems: "center",
|
|
197
|
+
onClick: openable ? () => setIsDropdownVisible(!isDropdownVisible) : undefined,
|
|
198
|
+
"data-testid": "select-bar",
|
|
199
|
+
autoFocus: autoFocus,
|
|
200
|
+
onKeyDown: event => {
|
|
201
|
+
if (event.key === 'ArrowDown') {
|
|
202
|
+
if (!isDropdownVisible) {
|
|
203
|
+
setIsDropdownVisible(true);
|
|
204
|
+
} else {
|
|
205
|
+
document.getElementById(`option-0`)?.focus();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return ['Enter', ' '].includes(event.key) && openable ? setIsDropdownVisible(!isDropdownVisible) : null;
|
|
209
|
+
},
|
|
210
|
+
ref: innerRef,
|
|
211
|
+
"aria-labelledby": "select bar",
|
|
212
|
+
"aria-haspopup": "listbox",
|
|
213
|
+
"aria-expanded": isDropdownVisible,
|
|
214
|
+
"aria-controls": "select-dropdown",
|
|
215
|
+
tabIndex: 0,
|
|
216
|
+
children: [selectedData.selectedValues.length > 0 ? jsx(DisplayValues, {
|
|
217
|
+
multiselect: multiselect,
|
|
218
|
+
refTag: refTag,
|
|
219
|
+
nonOverflowedValues: nonOverflowedValues,
|
|
220
|
+
disabled: disabled,
|
|
221
|
+
readOnly: readOnly,
|
|
222
|
+
selectedData: selectedData,
|
|
223
|
+
setSelectedData: setSelectedData,
|
|
224
|
+
onChange: onChange,
|
|
225
|
+
overflowed: overflowed,
|
|
226
|
+
overflowAmount: overflowAmount,
|
|
227
|
+
size: size
|
|
228
|
+
}) : jsx(StyledPlaceholder, {
|
|
229
|
+
as: "p",
|
|
230
|
+
variant: size === 'large' ? 'body' : 'bodySmall',
|
|
231
|
+
"data-disabled": disabled,
|
|
232
|
+
children: placeholder
|
|
233
|
+
}), jsxs(StateStack, {
|
|
234
|
+
direction: "row",
|
|
235
|
+
gap: 1,
|
|
236
|
+
alignItems: "center",
|
|
237
|
+
children: [error ? jsx(Icon, {
|
|
238
|
+
name: "alert",
|
|
239
|
+
sentiment: "danger"
|
|
240
|
+
}) : null, success && !error ? jsx(Icon, {
|
|
241
|
+
name: "checkbox-circle-outline",
|
|
242
|
+
sentiment: "success"
|
|
243
|
+
}) : null, clearable && selectedData.selectedValues.length > 0 ? jsx(Button, {
|
|
244
|
+
"aria-label": "clear value",
|
|
245
|
+
disabled: disabled || ![selectedData.selectedValues[0]] || readOnly,
|
|
246
|
+
variant: "ghost",
|
|
247
|
+
size: "small",
|
|
248
|
+
icon: "close",
|
|
249
|
+
onClick: event => {
|
|
250
|
+
event.stopPropagation();
|
|
251
|
+
setSelectedData({
|
|
252
|
+
type: 'clearAll'
|
|
253
|
+
});
|
|
254
|
+
onChange?.([]);
|
|
255
|
+
},
|
|
256
|
+
sentiment: "neutral",
|
|
257
|
+
"data-testid": "clear-all"
|
|
258
|
+
}) : null, jsx(Icon, {
|
|
259
|
+
"aria-label": "show dropdown",
|
|
260
|
+
size: "small",
|
|
261
|
+
name: "arrow-down",
|
|
262
|
+
sentiment: "neutral",
|
|
263
|
+
disabled: disabled || readOnly
|
|
264
|
+
})]
|
|
265
|
+
})]
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export { SelectBar };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useState, useReducer, useMemo, createContext, useContext } from 'react';
|
|
2
|
+
import { jsx } from '@emotion/react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
const SelectInputContext = /*#__PURE__*/createContext({
|
|
5
|
+
options: [],
|
|
6
|
+
multiselect: false,
|
|
7
|
+
onSearch: () => {},
|
|
8
|
+
isDropdownVisible: false,
|
|
9
|
+
setIsDropdownVisible: () => {},
|
|
10
|
+
searchInput: '',
|
|
11
|
+
setSearchInput: () => {},
|
|
12
|
+
selectAll: {
|
|
13
|
+
label: ''
|
|
14
|
+
},
|
|
15
|
+
selectAllGroup: false,
|
|
16
|
+
numberOfOptions: 0,
|
|
17
|
+
displayedOptions: [],
|
|
18
|
+
selectedData: {
|
|
19
|
+
allSelected: false,
|
|
20
|
+
selectedGroups: [],
|
|
21
|
+
selectedValues: []
|
|
22
|
+
},
|
|
23
|
+
setSelectedData: () => {}
|
|
24
|
+
});
|
|
25
|
+
const useSelectInput = () => useContext(SelectInputContext);
|
|
26
|
+
const SelectInputProvider = ({
|
|
27
|
+
options,
|
|
28
|
+
multiselect,
|
|
29
|
+
selectAll,
|
|
30
|
+
value,
|
|
31
|
+
selectAllGroup,
|
|
32
|
+
numberOfOptions,
|
|
33
|
+
children
|
|
34
|
+
}) => {
|
|
35
|
+
const defaultValue = value ? [value] : [];
|
|
36
|
+
const [displayedOptions, setDisplayedOptions] = useState(options);
|
|
37
|
+
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
|
38
|
+
const [searchInput, setSearchInput] = useState('');
|
|
39
|
+
const allValues = [];
|
|
40
|
+
const allGroups = [];
|
|
41
|
+
if (!Array.isArray(options)) {
|
|
42
|
+
Object.keys(options).map(group => options[group].map(option => {
|
|
43
|
+
if (!option.disabled) {
|
|
44
|
+
allValues.push(option);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}));
|
|
48
|
+
Object.keys(options).forEach(group => allGroups.push(group));
|
|
49
|
+
} else {
|
|
50
|
+
options.map(option => allValues.push(option));
|
|
51
|
+
}
|
|
52
|
+
const reducer = (state, action) => {
|
|
53
|
+
switch (action.type) {
|
|
54
|
+
case 'selectAll':
|
|
55
|
+
if (state.allSelected) {
|
|
56
|
+
return {
|
|
57
|
+
selectedValues: [],
|
|
58
|
+
allSelected: false,
|
|
59
|
+
selectedGroups: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
selectedValues: allValues,
|
|
64
|
+
allSelected: true,
|
|
65
|
+
selectedGroups: allGroups
|
|
66
|
+
};
|
|
67
|
+
case 'selectGroup':
|
|
68
|
+
if (!Array.isArray(options)) {
|
|
69
|
+
if (state.selectedGroups.includes(action.selectedGroup)) {
|
|
70
|
+
return {
|
|
71
|
+
selectedValues: [...state.selectedValues].filter(selectedValue => !options[action.selectedGroup].includes(selectedValue)),
|
|
72
|
+
allSelected: false,
|
|
73
|
+
selectedGroups: state.selectedGroups.filter(selectedGroup => selectedGroup !== action.selectedGroup)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const newSelectedValues = [...state.selectedValues];
|
|
77
|
+
options[action.selectedGroup].map(option => newSelectedValues.includes(option) || option.disabled ? null : newSelectedValues.push(option));
|
|
78
|
+
return {
|
|
79
|
+
selectedValues: newSelectedValues,
|
|
80
|
+
allSelected: newSelectedValues.length === numberOfOptions,
|
|
81
|
+
selectedGroups: [...state.selectedGroups, action.selectedGroup]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return state;
|
|
85
|
+
case 'selectOption':
|
|
86
|
+
if (multiselect) {
|
|
87
|
+
if (state.selectedValues.includes(action.clickedOption)) {
|
|
88
|
+
return {
|
|
89
|
+
selectedValues: state.selectedValues.filter(val => val !== action.clickedOption),
|
|
90
|
+
allSelected: false,
|
|
91
|
+
selectedGroups: action.group && state.selectedGroups.includes(action.group) ? state.selectedGroups.filter(selectedGroup => selectedGroup !== action.group) : []
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
selectedValues: [...state.selectedValues, action.clickedOption],
|
|
96
|
+
allSelected: state.selectedValues.length + 1 === numberOfOptions,
|
|
97
|
+
selectedGroups: !Array.isArray(options) && action.group && options[action.group].every(option => [...state.selectedValues, action.clickedOption].includes(option) || option.disabled) ? [...state.selectedGroups, action.group] : state.selectedGroups
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
selectedValues: [action.clickedOption],
|
|
102
|
+
allSelected: false,
|
|
103
|
+
selectedGroups: state.selectedGroups
|
|
104
|
+
};
|
|
105
|
+
case 'clearAll':
|
|
106
|
+
return {
|
|
107
|
+
selectedGroups: [],
|
|
108
|
+
selectedValues: [],
|
|
109
|
+
allSelected: false
|
|
110
|
+
};
|
|
111
|
+
case 'update':
|
|
112
|
+
// update the selected values to only keep non-disabled one
|
|
113
|
+
return {
|
|
114
|
+
selectedGroups: state.selectedGroups,
|
|
115
|
+
allSelected: state.allSelected,
|
|
116
|
+
selectedValues: state.selectedValues.filter(selectedValue => {
|
|
117
|
+
if (!Array.isArray(options)) {
|
|
118
|
+
return Object.keys(options).some(group => options[group].some(option => option === selectedValue && !option.disabled));
|
|
119
|
+
}
|
|
120
|
+
return options.some(option => option === selectedValue && !option.disabled);
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
default:
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const [selectedData, setSelectedData] = useReducer(reducer, {
|
|
128
|
+
selectedValues: defaultValue,
|
|
129
|
+
allSelected: false,
|
|
130
|
+
selectedGroups: []
|
|
131
|
+
});
|
|
132
|
+
const providerValue = useMemo(() => ({
|
|
133
|
+
onSearch: setDisplayedOptions,
|
|
134
|
+
isDropdownVisible,
|
|
135
|
+
setIsDropdownVisible,
|
|
136
|
+
searchInput,
|
|
137
|
+
setSearchInput,
|
|
138
|
+
options,
|
|
139
|
+
multiselect,
|
|
140
|
+
selectAll,
|
|
141
|
+
selectAllGroup,
|
|
142
|
+
numberOfOptions,
|
|
143
|
+
displayedOptions,
|
|
144
|
+
selectedData,
|
|
145
|
+
setSelectedData
|
|
146
|
+
}), [displayedOptions, isDropdownVisible, multiselect, numberOfOptions, options, searchInput, selectAll, selectAllGroup, selectedData]);
|
|
147
|
+
return jsx(SelectInputContext.Provider, {
|
|
148
|
+
value: providerValue,
|
|
149
|
+
children: children
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export { SelectInputProvider, useSelectInput };
|