@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.
@@ -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 };