@ultraviolet/ui 1.49.0 → 1.51.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 CHANGED
@@ -3534,7 +3534,7 @@ type OptionType = {
3534
3534
  };
3535
3535
  type DataType = Record<string, OptionType[]> | OptionType[];
3536
3536
 
3537
- type SelectInputV2Props = {
3537
+ type SelectInputV2Props<IsMulti extends undefined | boolean = false> = {
3538
3538
  /**
3539
3539
  * Input name
3540
3540
  */
@@ -3631,31 +3631,21 @@ type SelectInputV2Props = {
3631
3631
  */
3632
3632
  selectAllGroup?: boolean;
3633
3633
  autofocus?: boolean;
3634
- 'data-testid'?: string;
3635
- onChange?: (value: string[]) => void;
3636
- } & Pick<HTMLAttributes<HTMLDivElement>, 'id' | 'onBlur' | 'onFocus' | 'aria-label' | 'className'> & ({
3637
- /**
3638
- * Whether it is possible to select multiple options
3639
- */
3640
- multiselect: true;
3641
- /**
3642
- * Default value, must be one of the options
3643
- */
3644
- value?: string[];
3645
- } | {
3646
3634
  /**
3647
3635
  * Whether it is possible to select multiple options
3648
3636
  */
3649
- multiselect?: false | undefined;
3637
+ multiselect?: IsMulti;
3650
3638
  /**
3651
3639
  * Default value, must be one of the options
3652
3640
  */
3653
- value?: string;
3654
- });
3641
+ value?: IsMulti extends true ? string[] : string;
3642
+ onChange?: IsMulti extends true ? (value: string[]) => void : (value: string) => void;
3643
+ 'data-testid'?: string;
3644
+ } & Pick<HTMLAttributes<HTMLDivElement>, 'id' | 'onBlur' | 'onFocus' | 'aria-label' | 'className'>;
3655
3645
  /**
3656
3646
  * SelectInputV2 component is used to select one or many elements from a selection.
3657
3647
  */
3658
- declare const SelectInputV2: ({ name, id, onBlur, onFocus, onChange, "aria-label": ariaLabel, value, label, helper, options, size, emptyState, descriptionDirection, success, error, "data-testid": dataTestId, className, footer, placeholderSearch, placeholder, searchable, disabled, readOnly, clearable, multiselect, required, labelDescription, autofocus, loadMore, optionalInfoPlacement, isLoading, selectAll, selectAllGroup, }: SelectInputV2Props) => _emotion_react_jsx_runtime.JSX.Element;
3648
+ declare const SelectInputV2: <IsMulti extends boolean | undefined>({ name, id, onBlur, onFocus, onChange, "aria-label": ariaLabel, value, label, helper, options, size, emptyState, descriptionDirection, success, error, "data-testid": dataTestId, className, footer, placeholderSearch, placeholder, searchable, disabled, readOnly, clearable, multiselect, required, labelDescription, autofocus, loadMore, optionalInfoPlacement, isLoading, selectAll, selectAllGroup, }: SelectInputV2Props<IsMulti>) => _emotion_react_jsx_runtime.JSX.Element;
3659
3649
 
3660
3650
  declare const getUUID: (prefix?: string) => string;
3661
3651
 
@@ -207,7 +207,6 @@ const CreateDropdown = ({
207
207
  isEmpty,
208
208
  emptyState,
209
209
  descriptionDirection,
210
- onChange,
211
210
  loadMore,
212
211
  optionalInfoPlacement,
213
212
  defaultSearchValue,
@@ -215,6 +214,7 @@ const CreateDropdown = ({
215
214
  }) => {
216
215
  const {
217
216
  setIsDropdownVisible,
217
+ onChange,
218
218
  options,
219
219
  multiselect,
220
220
  selectAll,
@@ -253,44 +253,48 @@ const CreateDropdown = ({
253
253
  onChange?.([...selectedData.selectedValues, clickedOption.value]);
254
254
  }
255
255
  } else {
256
- onChange?.([clickedOption.value]);
256
+ onChange?.(clickedOption.value);
257
257
  }
258
258
  setIsDropdownVisible(multiselect); // hide the dropdown on click when single select only
259
259
  };
260
260
  const selectAllOptions = () => {
261
- setSelectedData({
262
- type: 'selectAll'
263
- });
264
- if (selectedData.allSelected && onChange) {
265
- onChange?.([]);
266
- } else {
267
- const allValues = [];
268
- if (!Array.isArray(options)) {
269
- Object.keys(options).map(group => options[group].map(option => {
270
- if (!option.disabled) {
271
- allValues.push(option);
272
- }
273
- return null;
274
- }));
261
+ if (multiselect) {
262
+ setSelectedData({
263
+ type: 'selectAll'
264
+ });
265
+ if (selectedData.allSelected && onChange) {
266
+ onChange([]);
275
267
  } else {
276
- options.map(option => allValues.push(option));
268
+ const allValues = [];
269
+ if (!Array.isArray(options)) {
270
+ Object.keys(options).map(group => options[group].map(option => {
271
+ if (!option.disabled) {
272
+ allValues.push(option);
273
+ }
274
+ return null;
275
+ }));
276
+ } else {
277
+ options.map(option => allValues.push(option));
278
+ }
279
+ onChange?.(allValues.map(value => value.value));
277
280
  }
278
- onChange?.(allValues.map(value => value.value));
279
281
  }
280
282
  };
281
283
  const handleSelectGroup = group => {
282
- setSelectedData({
283
- type: 'selectGroup',
284
- selectedGroup: group
285
- });
286
- if (!Array.isArray(options)) {
287
- if (selectedData.selectedGroups.includes(group)) {
288
- const newSelectedValues = [...selectedData.selectedValues].filter(selectedValue => !options[group].find(option => option.value === selectedValue));
289
- onChange?.(newSelectedValues);
290
- } else {
291
- const newSelectedValues = [...selectedData.selectedValues];
292
- options[group].map(option => newSelectedValues.includes(option.value) || option.disabled ? null : newSelectedValues.push(option.value));
293
- onChange?.(newSelectedValues);
284
+ if (multiselect) {
285
+ setSelectedData({
286
+ type: 'selectGroup',
287
+ selectedGroup: group
288
+ });
289
+ if (!Array.isArray(options)) {
290
+ if (selectedData.selectedGroups.includes(group)) {
291
+ const newSelectedValues = [...selectedData.selectedValues].filter(selectedValue => !options[group].find(option => option.value === selectedValue));
292
+ onChange?.(newSelectedValues);
293
+ } else {
294
+ const newSelectedValues = [...selectedData.selectedValues];
295
+ options[group].map(option => newSelectedValues.includes(option.value) || option.disabled ? null : newSelectedValues.push(option.value));
296
+ onChange?.(newSelectedValues);
297
+ }
294
298
  }
295
299
  }
296
300
  };
@@ -504,7 +508,6 @@ const Dropdown = ({
504
508
  searchable,
505
509
  placeholder,
506
510
  footer,
507
- onChange,
508
511
  refSelect,
509
512
  loadMore,
510
513
  optionalInfoPlacement,
@@ -562,13 +565,11 @@ const Dropdown = ({
562
565
  children: [searchable && !isLoading ? jsx(SearchBarDropdown, {
563
566
  placeholder: placeholder,
564
567
  displayedOptions: displayedOptions,
565
- setSearchBarActive: setSearchBarActive,
566
- onChange: onChange
568
+ setSearchBarActive: setSearchBarActive
567
569
  }) : null, jsx(CreateDropdown, {
568
570
  isEmpty: isEmpty,
569
571
  emptyState: emptyState,
570
572
  descriptionDirection: descriptionDirection,
571
- onChange: onChange,
572
573
  loadMore: loadMore,
573
574
  optionalInfoPlacement: optionalInfoPlacement,
574
575
  defaultSearchValue: defaultSearchValue,
@@ -1,5 +1,6 @@
1
1
  import _styled from '@emotion/styled/base';
2
2
  import { Icon } from '@ultraviolet/icons';
3
+ import { useRef, useEffect } from 'react';
3
4
  import { TextInputV2 } from '../TextInputV2/index.js';
4
5
  import { useSelectInput } from './SelectInputProvider.js';
5
6
  import { jsx } from '@emotion/react/jsx-runtime';
@@ -41,10 +42,11 @@ const findClosestOption = (options, searchInput) => {
41
42
  const SearchBarDropdown = ({
42
43
  placeholder,
43
44
  displayedOptions,
44
- setSearchBarActive,
45
- onChange
45
+ setSearchBarActive
46
46
  }) => {
47
+ const searchInputRef = useRef(null);
47
48
  const {
49
+ onChange,
48
50
  onSearch,
49
51
  setSearchInput,
50
52
  searchInput,
@@ -92,11 +94,18 @@ const SearchBarDropdown = ({
92
94
  type: 'selectOption',
93
95
  clickedOption: closestOption
94
96
  });
95
- onChange?.(selectedData.selectedValues);
97
+ onChange?.(selectedData.selectedValues[0] ?? '');
96
98
  }
97
99
  }
98
100
  }
99
101
  };
102
+ useEffect(() => {
103
+ // TODO: Remove me and use autoFocus when popup is fixed
104
+ // Explanation : Actually the component render at -999px -999px then it will be placed according to child position and it broke the autoFocus (scroll -999px to top)
105
+ setTimeout(() => {
106
+ searchInputRef.current?.focus();
107
+ }, 50);
108
+ }, []);
100
109
  return jsx(StyledInput, {
101
110
  value: searchInput,
102
111
  onChange: event => handleChange(event),
@@ -110,9 +119,9 @@ const SearchBarDropdown = ({
110
119
  sentiment: "neutral"
111
120
  }),
112
121
  onKeyDown: event => handleKeyDown(event.key, searchInput),
113
- autoFocus: true,
114
122
  size: "medium",
115
- "aria-label": "search-bar"
123
+ "aria-label": "search-bar",
124
+ ref: searchInputRef
116
125
  });
117
126
  };
118
127
 
@@ -72,52 +72,56 @@ const StyledPlaceholder = /*#__PURE__*/_styled(Text, {
72
72
  }) => theme.colors.neutral.textWeakDisabled, ";}");
73
73
  const isValidSelectedValue = (selectedValue, options) => !Array.isArray(options) ? Object.keys(options).some(group => options[group].some(option => option.value === selectedValue && !option.disabled)) : options.some(option => option.value === selectedValue && !option.disabled);
74
74
  const DisplayValues = ({
75
- multiselect,
76
75
  refTag,
77
76
  nonOverflowedValues,
78
77
  disabled,
79
78
  readOnly,
80
- onChange,
81
79
  overflowed,
82
80
  overflowAmount,
83
- setSelectedData,
84
- selectedData,
85
- size,
86
- options
87
- }) => multiselect ? jsxs(Stack, {
88
- direction: "row",
89
- gap: "1",
90
- wrap: "nowrap",
91
- ref: refTag,
92
- alignItems: "center",
93
- children: [nonOverflowedValues.map(option => jsx(CustomTag, {
94
- "data-testid": "selected-options-tags",
95
- sentiment: "neutral",
96
- disabled: disabled,
97
- onClose: !readOnly ? event => {
98
- event.stopPropagation();
99
- setSelectedData({
100
- type: 'selectOption',
101
- clickedOption: option
102
- });
103
- const newSelectedValues = selectedData.selectedValues?.filter(val => val !== option.value);
104
- onChange?.(newSelectedValues);
105
- } : undefined,
106
- children: option?.label
107
- }, option?.value)), overflowed ? jsxs(Tag, {
108
- sentiment: "neutral",
109
- disabled: disabled,
110
- "data-testid": "plus-tag",
111
- "aria-label": "Plus tag",
112
- children: [jsx(Icon, {
113
- name: "plus"
114
- }), overflowAmount]
115
- }, "+") : null]
116
- }) : jsx(Text, {
117
- as: "p",
118
- variant: size === 'large' ? 'body' : 'bodySmall',
119
- children: selectedData.selectedValues[0] ? findOptionInOptions(options, selectedData.selectedValues[0])?.label : null
120
- });
81
+ size
82
+ }) => {
83
+ const {
84
+ multiselect,
85
+ selectedData,
86
+ setSelectedData,
87
+ options,
88
+ onChange
89
+ } = useSelectInput();
90
+ return multiselect ? jsxs(Stack, {
91
+ direction: "row",
92
+ gap: "1",
93
+ wrap: "nowrap",
94
+ ref: refTag,
95
+ alignItems: "center",
96
+ children: [nonOverflowedValues.map(option => jsx(CustomTag, {
97
+ "data-testid": "selected-options-tags",
98
+ sentiment: "neutral",
99
+ disabled: disabled,
100
+ onClose: !readOnly ? event => {
101
+ event.stopPropagation();
102
+ setSelectedData({
103
+ type: 'selectOption',
104
+ clickedOption: option
105
+ });
106
+ const newSelectedValues = selectedData.selectedValues?.filter(val => val !== option.value);
107
+ onChange?.(newSelectedValues);
108
+ } : undefined,
109
+ children: option?.label
110
+ }, option?.value)), overflowed ? jsxs(Tag, {
111
+ sentiment: "neutral",
112
+ disabled: disabled,
113
+ "data-testid": "plus-tag",
114
+ "aria-label": "Plus tag",
115
+ children: [jsx(Icon, {
116
+ name: "plus"
117
+ }), overflowAmount]
118
+ }, "+") : null]
119
+ }) : jsx(Text, {
120
+ as: "p",
121
+ variant: size === 'large' ? 'body' : 'bodySmall',
122
+ children: selectedData.selectedValues[0] ? findOptionInOptions(options, selectedData.selectedValues[0])?.label : null
123
+ });
124
+ };
121
125
  const SelectBar = ({
122
126
  size,
123
127
  clearable,
@@ -126,17 +130,17 @@ const SelectBar = ({
126
130
  placeholder,
127
131
  success,
128
132
  error,
129
- onChange,
130
133
  autoFocus,
131
134
  innerRef
132
135
  }) => {
133
136
  const {
134
137
  isDropdownVisible,
138
+ onChange,
135
139
  setIsDropdownVisible,
136
140
  options,
137
- multiselect,
138
141
  selectedData,
139
- setSelectedData
142
+ setSelectedData,
143
+ multiselect
140
144
  } = useSelectInput();
141
145
  const openable = !(readOnly || disabled);
142
146
  const refTag = useRef(null);
@@ -223,18 +227,13 @@ const SelectBar = ({
223
227
  "aria-controls": "select-dropdown",
224
228
  tabIndex: 0,
225
229
  children: [selectedData.selectedValues.length > 0 ? jsx(DisplayValues, {
226
- multiselect: multiselect,
227
230
  refTag: refTag,
228
231
  nonOverflowedValues: nonOverflowedValues,
229
232
  disabled: disabled,
230
233
  readOnly: readOnly,
231
- selectedData: selectedData,
232
- setSelectedData: setSelectedData,
233
- onChange: onChange,
234
234
  overflowed: overflowed,
235
235
  overflowAmount: overflowAmount,
236
- size: size,
237
- options: options
236
+ size: size
238
237
  }) : jsx(StyledPlaceholder, {
239
238
  as: "p",
240
239
  variant: size === 'large' ? 'body' : 'bodySmall',
@@ -261,7 +260,11 @@ const SelectBar = ({
261
260
  setSelectedData({
262
261
  type: 'clearAll'
263
262
  });
264
- onChange?.([]);
263
+ if (multiselect) {
264
+ onChange?.([]);
265
+ } else {
266
+ onChange?.('');
267
+ }
265
268
  },
266
269
  sentiment: "neutral",
267
270
  "data-testid": "clear-all"
@@ -20,7 +20,8 @@ const SelectInputContext = /*#__PURE__*/createContext({
20
20
  selectedGroups: [],
21
21
  selectedValues: []
22
22
  },
23
- setSelectedData: () => {}
23
+ setSelectedData: () => {},
24
+ onChange: () => {}
24
25
  });
25
26
  const useSelectInput = () => useContext(SelectInputContext);
26
27
  const SelectInputProvider = ({
@@ -30,7 +31,8 @@ const SelectInputProvider = ({
30
31
  value,
31
32
  selectAllGroup,
32
33
  numberOfOptions,
33
- children
34
+ children,
35
+ onChange
34
36
  }) => {
35
37
  const currentValue = useMemo(() => {
36
38
  if (value) {
@@ -155,8 +157,9 @@ const SelectInputProvider = ({
155
157
  numberOfOptions,
156
158
  displayedOptions,
157
159
  selectedData,
158
- setSelectedData
159
- }), [displayedOptions, isDropdownVisible, multiselect, numberOfOptions, options, searchInput, selectAll, selectAllGroup, selectedData]);
160
+ setSelectedData,
161
+ onChange
162
+ }), [displayedOptions, isDropdownVisible, multiselect, numberOfOptions, options, searchInput, selectAll, selectAllGroup, selectedData, onChange]);
160
163
  return jsx(SelectInputContext.Provider, {
161
164
  value: providerValue,
162
165
  children: children
@@ -72,6 +72,7 @@ const SelectInputV2 = ({
72
72
  value: value,
73
73
  selectAllGroup: selectAllGroup,
74
74
  numberOfOptions: numberOfOptions,
75
+ onChange: onChange,
75
76
  children: jsxs(SelectInputContainer, {
76
77
  id: id,
77
78
  onBlur: onBlur,
@@ -85,7 +86,6 @@ const SelectInputV2 = ({
85
86
  searchable: searchable,
86
87
  placeholder: placeholderSearch,
87
88
  footer: footer,
88
- onChange: onChange,
89
89
  refSelect: ref,
90
90
  loadMore: loadMore,
91
91
  optionalInfoPlacement: optionalInfoPlacement,
@@ -113,7 +113,6 @@ const SelectInputV2 = ({
113
113
  placeholder: placeholder,
114
114
  success: success,
115
115
  error: error,
116
- onChange: onChange,
117
116
  autoFocus: autofocus,
118
117
  innerRef: ref
119
118
  })]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultraviolet/ui",
3
- "version": "1.49.0",
3
+ "version": "1.51.0",
4
4
  "description": "Ultraviolet UI",
5
5
  "homepage": "https://github.com/scaleway/ultraviolet#readme",
6
6
  "repository": {