@patternfly/react-templates 6.0.0-alpha.45 → 6.0.0-alpha.46

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/components/package.json +1 -1
  3. package/dist/dynamic/components/Dropdown/package.json +1 -1
  4. package/dist/dynamic/components/Select/package.json +1 -1
  5. package/dist/dynamic-modules.json +4 -0
  6. package/dist/esm/components/Select/CheckboxSelect.d.ts.map +1 -1
  7. package/dist/esm/components/Select/CheckboxSelect.js +5 -0
  8. package/dist/esm/components/Select/CheckboxSelect.js.map +1 -1
  9. package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts +33 -0
  10. package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
  11. package/dist/esm/components/Select/MultiTypeaheadSelect.js +186 -0
  12. package/dist/esm/components/Select/MultiTypeaheadSelect.js.map +1 -0
  13. package/dist/esm/components/Select/SimpleSelect.d.ts +2 -0
  14. package/dist/esm/components/Select/SimpleSelect.d.ts.map +1 -1
  15. package/dist/esm/components/Select/SimpleSelect.js +10 -6
  16. package/dist/esm/components/Select/SimpleSelect.js.map +1 -1
  17. package/dist/esm/components/Select/TypeaheadSelect.d.ts +12 -2
  18. package/dist/esm/components/Select/TypeaheadSelect.d.ts.map +1 -1
  19. package/dist/esm/components/Select/TypeaheadSelect.js +61 -22
  20. package/dist/esm/components/Select/TypeaheadSelect.js.map +1 -1
  21. package/dist/esm/components/Select/index.d.ts +1 -0
  22. package/dist/esm/components/Select/index.d.ts.map +1 -1
  23. package/dist/esm/components/Select/index.js +1 -0
  24. package/dist/esm/components/Select/index.js.map +1 -1
  25. package/dist/js/components/Select/CheckboxSelect.d.ts.map +1 -1
  26. package/dist/js/components/Select/CheckboxSelect.js +5 -0
  27. package/dist/js/components/Select/CheckboxSelect.js.map +1 -1
  28. package/dist/js/components/Select/MultiTypeaheadSelect.d.ts +33 -0
  29. package/dist/js/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
  30. package/dist/js/components/Select/MultiTypeaheadSelect.js +190 -0
  31. package/dist/js/components/Select/MultiTypeaheadSelect.js.map +1 -0
  32. package/dist/js/components/Select/SimpleSelect.d.ts +2 -0
  33. package/dist/js/components/Select/SimpleSelect.d.ts.map +1 -1
  34. package/dist/js/components/Select/SimpleSelect.js +10 -6
  35. package/dist/js/components/Select/SimpleSelect.js.map +1 -1
  36. package/dist/js/components/Select/TypeaheadSelect.d.ts +12 -2
  37. package/dist/js/components/Select/TypeaheadSelect.d.ts.map +1 -1
  38. package/dist/js/components/Select/TypeaheadSelect.js +61 -22
  39. package/dist/js/components/Select/TypeaheadSelect.js.map +1 -1
  40. package/dist/js/components/Select/index.d.ts +1 -0
  41. package/dist/js/components/Select/index.d.ts.map +1 -1
  42. package/dist/js/components/Select/index.js +1 -0
  43. package/dist/js/components/Select/index.js.map +1 -1
  44. package/dist/umd/assets/{output--NzZZ7D3.css → output-DPlyZse9.css} +3498 -3498
  45. package/dist/umd/react-templates.min.js +1 -1
  46. package/package.json +6 -6
  47. package/src/components/Select/CheckboxSelect.tsx +5 -0
  48. package/src/components/Select/MultiTypeaheadSelect.tsx +348 -0
  49. package/src/components/Select/SimpleSelect.tsx +13 -5
  50. package/src/components/Select/TypeaheadSelect.tsx +84 -25
  51. package/src/components/Select/__tests__/MultiTypeaheadSelect.test.tsx +414 -0
  52. package/src/components/Select/__tests__/__snapshots__/MultiTypeaheadSelect.test.tsx.snap +97 -0
  53. package/src/components/Select/examples/CheckboxSelectDemo.tsx +25 -8
  54. package/src/components/Select/examples/MultiTypeaheadSelectDemo.tsx +35 -0
  55. package/src/components/Select/examples/SelectTemplates.md +7 -1
  56. package/src/components/Select/examples/SimpleSelectDemo.tsx +16 -6
  57. package/src/components/Select/examples/TypeaheadSelectDemo.tsx +57 -13
  58. package/src/components/Select/index.ts +1 -0
@@ -0,0 +1,348 @@
1
+ import React from 'react';
2
+ import {
3
+ Select,
4
+ SelectOption,
5
+ SelectList,
6
+ SelectOptionProps,
7
+ MenuToggle,
8
+ MenuToggleElement,
9
+ TextInputGroup,
10
+ TextInputGroupMain,
11
+ TextInputGroupUtilities,
12
+ Button,
13
+ MenuToggleProps,
14
+ SelectProps,
15
+ Label,
16
+ LabelGroup
17
+ } from '@patternfly/react-core';
18
+ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
19
+
20
+ export interface MultiTypeaheadSelectOption extends Omit<SelectOptionProps, 'content'> {
21
+ /** Content of the select option. */
22
+ content: string | number;
23
+ /** Value of the select option. */
24
+ value: string | number;
25
+ }
26
+
27
+ export interface MultiTypeaheadSelectProps extends Omit<SelectProps, 'toggle' | 'onSelect'> {
28
+ /** @hide Forwarded ref */
29
+ innerRef?: React.Ref<any>;
30
+ /** Initial options of the select. */
31
+ initialOptions: MultiTypeaheadSelectOption[];
32
+ /** Callback triggered on selection. */
33
+ onSelectionChange?: (
34
+ _event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement>,
35
+ selections: (string | number)[]
36
+ ) => void;
37
+ /** Callback triggered when the select opens or closes. */
38
+ onToggle?: (nextIsOpen: boolean) => void;
39
+ /** Callback triggered when the text in the input field changes. */
40
+ onInputChange?: (newValue: string) => void;
41
+ /** Placeholder text for the select input. */
42
+ placeholder?: string;
43
+ /** Message to display when no options match the filter. */
44
+ noOptionsFoundMessage?: string | ((filter: string) => string);
45
+ /** Flag indicating the select should be disabled. */
46
+ isDisabled?: boolean;
47
+ /** Width of the toggle. */
48
+ toggleWidth?: string;
49
+ /** Additional props passed to the toggle. */
50
+ toggleProps?: MenuToggleProps;
51
+ }
52
+
53
+ export const MultiTypeaheadSelectBase: React.FunctionComponent<MultiTypeaheadSelectProps> = ({
54
+ innerRef,
55
+ initialOptions,
56
+ onSelectionChange,
57
+ onToggle,
58
+ onInputChange,
59
+ placeholder = 'Select an option',
60
+ noOptionsFoundMessage = (filter) => `No results found for "${filter}"`,
61
+ isDisabled,
62
+ toggleWidth,
63
+ toggleProps,
64
+ ...props
65
+ }: MultiTypeaheadSelectProps) => {
66
+ const [isOpen, setIsOpen] = React.useState(false);
67
+ const [selected, setSelected] = React.useState<(string | number)[]>(
68
+ (initialOptions?.filter((o) => o.selected) ?? []).map((o) => o.value)
69
+ );
70
+ const [inputValue, setInputValue] = React.useState<string>();
71
+ const [selectOptions, setSelectOptions] = React.useState<MultiTypeaheadSelectOption[]>(initialOptions);
72
+ const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
73
+ const [activeItemId, setActiveItemId] = React.useState<string | null>(null);
74
+ const textInputRef = React.useRef<HTMLInputElement>();
75
+
76
+ const NO_RESULTS = 'no results';
77
+
78
+ const openMenu = () => {
79
+ onToggle && onToggle(true);
80
+ setIsOpen(true);
81
+ };
82
+
83
+ React.useEffect(() => {
84
+ let newSelectOptions: MultiTypeaheadSelectOption[] = initialOptions;
85
+
86
+ // Filter menu items based on the text input value when one exists
87
+ if (inputValue) {
88
+ newSelectOptions = initialOptions.filter((option) =>
89
+ String(option.content).toLowerCase().includes(inputValue.toLowerCase())
90
+ );
91
+
92
+ // When no options are found after filtering, display 'No results found'
93
+ if (!newSelectOptions.length) {
94
+ newSelectOptions = [
95
+ {
96
+ isAriaDisabled: true,
97
+ content:
98
+ typeof noOptionsFoundMessage === 'string' ? noOptionsFoundMessage : noOptionsFoundMessage(inputValue),
99
+ value: NO_RESULTS
100
+ }
101
+ ];
102
+ }
103
+
104
+ // Open the menu when the input value changes and the new value is not empty
105
+ openMenu();
106
+ }
107
+
108
+ setSelectOptions(newSelectOptions);
109
+ // eslint-disable-next-line react-hooks/exhaustive-deps
110
+ }, [inputValue, initialOptions]);
111
+
112
+ React.useEffect(
113
+ () => setSelected((initialOptions?.filter((o) => o.selected) ?? []).map((o) => o.value)),
114
+ [initialOptions]
115
+ );
116
+
117
+ const setActiveAndFocusedItem = (itemIndex: number) => {
118
+ setFocusedItemIndex(itemIndex);
119
+ const focusedItem = selectOptions[itemIndex];
120
+ setActiveItemId(focusedItem.value as string);
121
+ };
122
+
123
+ const resetActiveAndFocusedItem = () => {
124
+ setFocusedItemIndex(null);
125
+ setActiveItemId(null);
126
+ };
127
+
128
+ const closeMenu = () => {
129
+ onToggle && onToggle(false);
130
+ setIsOpen(false);
131
+ resetActiveAndFocusedItem();
132
+ setInputValue('');
133
+ };
134
+
135
+ const onInputClick = () => {
136
+ if (!isOpen) {
137
+ openMenu();
138
+ } else if (!inputValue) {
139
+ closeMenu();
140
+ }
141
+ };
142
+
143
+ const selectOption = (
144
+ _event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement> | undefined,
145
+ option: string | number
146
+ ) => {
147
+ const selections = selected.includes(option) ? selected.filter((o) => option !== o) : [...selected, option];
148
+
149
+ onSelectionChange && onSelectionChange(_event, selections);
150
+ setSelected(selections);
151
+ };
152
+
153
+ const clearOption = (
154
+ _event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement> | undefined,
155
+ option: string | number
156
+ ) => {
157
+ const selections = selected.filter((o) => option !== o);
158
+ onSelectionChange && onSelectionChange(_event, selections);
159
+ setSelected(selections);
160
+ };
161
+
162
+ const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
163
+ if (value && value !== NO_RESULTS) {
164
+ selectOption(_event, value);
165
+ }
166
+ };
167
+
168
+ const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
169
+ setInputValue(value);
170
+ onInputChange && onInputChange(value);
171
+
172
+ resetActiveAndFocusedItem();
173
+ };
174
+
175
+ const handleMenuArrowKeys = (key: string) => {
176
+ let indexToFocus = 0;
177
+
178
+ if (!isOpen) {
179
+ openMenu();
180
+ }
181
+
182
+ if (selectOptions.every((option) => option.isDisabled)) {
183
+ return;
184
+ }
185
+
186
+ if (key === 'ArrowUp') {
187
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
188
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
189
+ indexToFocus = selectOptions.length - 1;
190
+ } else {
191
+ indexToFocus = focusedItemIndex - 1;
192
+ }
193
+
194
+ // Skip disabled options
195
+ while (selectOptions[indexToFocus].isDisabled) {
196
+ indexToFocus--;
197
+ if (indexToFocus === -1) {
198
+ indexToFocus = selectOptions.length - 1;
199
+ }
200
+ }
201
+ }
202
+
203
+ if (key === 'ArrowDown') {
204
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
205
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
206
+ indexToFocus = 0;
207
+ } else {
208
+ indexToFocus = focusedItemIndex + 1;
209
+ }
210
+
211
+ // Skip disabled options
212
+ while (selectOptions[indexToFocus].isDisabled) {
213
+ indexToFocus++;
214
+ if (indexToFocus === selectOptions.length) {
215
+ indexToFocus = 0;
216
+ }
217
+ }
218
+ }
219
+
220
+ setActiveAndFocusedItem(indexToFocus);
221
+ };
222
+
223
+ const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
224
+ const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null;
225
+
226
+ switch (event.key) {
227
+ case 'Enter':
228
+ if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) {
229
+ selectOption(event, focusedItem?.value);
230
+ }
231
+
232
+ if (!isOpen) {
233
+ onToggle && onToggle(true);
234
+ setIsOpen(true);
235
+ }
236
+
237
+ break;
238
+ case 'ArrowUp':
239
+ case 'ArrowDown':
240
+ event.preventDefault();
241
+ handleMenuArrowKeys(event.key);
242
+ break;
243
+ }
244
+ };
245
+
246
+ const onToggleClick = () => {
247
+ onToggle && onToggle(!isOpen);
248
+ setIsOpen(!isOpen);
249
+ textInputRef?.current?.focus();
250
+ };
251
+
252
+ const onClearButtonClick = (ev: React.MouseEvent) => {
253
+ setSelected([]);
254
+ onInputChange && onInputChange('');
255
+ resetActiveAndFocusedItem();
256
+ textInputRef?.current?.focus();
257
+ onSelectionChange && onSelectionChange(ev, []);
258
+ };
259
+
260
+ const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
261
+ <MenuToggle
262
+ ref={toggleRef}
263
+ variant="typeahead"
264
+ aria-label="Multi select Typeahead menu toggle"
265
+ onClick={onToggleClick}
266
+ isExpanded={isOpen}
267
+ isDisabled={isDisabled}
268
+ isFullWidth
269
+ style={
270
+ {
271
+ width: toggleWidth
272
+ } as React.CSSProperties
273
+ }
274
+ {...toggleProps}
275
+ >
276
+ <TextInputGroup isPlain>
277
+ <TextInputGroupMain
278
+ value={inputValue}
279
+ onClick={onInputClick}
280
+ onChange={onTextInputChange}
281
+ onKeyDown={onInputKeyDown}
282
+ autoComplete="off"
283
+ innerRef={textInputRef}
284
+ placeholder={placeholder}
285
+ {...(activeItemId && { 'aria-activedescendant': activeItemId })}
286
+ role="combobox"
287
+ isExpanded={isOpen}
288
+ aria-controls="select-typeahead-listbox"
289
+ >
290
+ <LabelGroup aria-label="Current selections">
291
+ {selected.map((selection, index) => (
292
+ <Label
293
+ key={index}
294
+ datatest-id={`${selection}-chip`}
295
+ onClick={(ev) => {
296
+ ev.stopPropagation();
297
+ clearOption(ev, selection);
298
+ }}
299
+ >
300
+ {initialOptions.find((o) => o.value === selection)?.content}
301
+ </Label>
302
+ ))}
303
+ </LabelGroup>
304
+ </TextInputGroupMain>
305
+ <TextInputGroupUtilities {...(selected.length === 0 ? { style: { display: 'none' } } : {})}>
306
+ <Button variant="plain" onClick={onClearButtonClick} aria-label="Clear input value">
307
+ <TimesIcon aria-hidden />
308
+ </Button>
309
+ </TextInputGroupUtilities>
310
+ </TextInputGroup>
311
+ </MenuToggle>
312
+ );
313
+
314
+ return (
315
+ <Select
316
+ isOpen={isOpen}
317
+ selected={selected}
318
+ onSelect={_onSelect}
319
+ onOpenChange={(isOpen) => {
320
+ !isOpen && closeMenu();
321
+ }}
322
+ toggle={toggle}
323
+ shouldFocusFirstItemOnOpen={false}
324
+ ref={innerRef}
325
+ {...props}
326
+ >
327
+ <SelectList>
328
+ {selectOptions.map((option, index) => {
329
+ const { content, value, ...props } = option;
330
+
331
+ return (
332
+ <SelectOption key={value} value={value} isFocused={focusedItemIndex === index} {...props}>
333
+ {content}
334
+ </SelectOption>
335
+ );
336
+ })}
337
+ </SelectList>
338
+ </Select>
339
+ );
340
+ };
341
+
342
+ MultiTypeaheadSelectBase.displayName = 'MultiTypeaheadSelectBase';
343
+
344
+ export const MultiTypeaheadSelect = React.forwardRef((props: MultiTypeaheadSelectProps, ref: React.Ref<any>) => (
345
+ <MultiTypeaheadSelectBase {...props} innerRef={ref} />
346
+ ));
347
+
348
+ MultiTypeaheadSelect.displayName = 'MultiTypeaheadSelect';
@@ -28,6 +28,8 @@ export interface SimpleSelectProps extends Omit<SelectProps, 'toggle'> {
28
28
  isDisabled?: boolean;
29
29
  /** Content of the toggle. Defaults to the selected option. */
30
30
  toggleContent?: React.ReactNode;
31
+ /** Placeholder text for the select input. */
32
+ placeholder?: string;
31
33
  /** Width of the toggle. */
32
34
  toggleWidth?: string;
33
35
  /** Additional props passed to the toggle. */
@@ -43,14 +45,20 @@ const SimpleSelectBase: React.FunctionComponent<SimpleSelectProps> = ({
43
45
  toggleContent,
44
46
  toggleWidth = '200px',
45
47
  toggleProps,
48
+ placeholder = 'Select a value',
46
49
  ...props
47
50
  }: SimpleSelectProps) => {
48
51
  const [isOpen, setIsOpen] = React.useState(false);
49
- const [selected, setSelected] = React.useState<string>('Select a value');
52
+ const [selected, setSelected] = React.useState<SimpleSelectOption | undefined>();
53
+
54
+ React.useEffect(() => {
55
+ const selectedOption = initialOptions?.find((option) => option.selected);
56
+ setSelected(selectedOption);
57
+ }, [initialOptions]);
50
58
 
51
59
  const simpleSelectOptions = initialOptions?.map((option) => {
52
60
  const { content, value, ...props } = option;
53
- const isSelected = selected.includes(`${value}`);
61
+ const isSelected = selected?.value === value;
54
62
  return (
55
63
  <SelectOption value={value} key={value} isSelected={isSelected} {...props}>
56
64
  {content}
@@ -65,8 +73,8 @@ const SimpleSelectBase: React.FunctionComponent<SimpleSelectProps> = ({
65
73
 
66
74
  const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
67
75
  onSelect && onSelect(_event, value);
68
- setSelected(value as string);
69
- onToggle && onToggle(!false);
76
+ setSelected(initialOptions.find((o) => o.value === value));
77
+ onToggle && onToggle(true);
70
78
  setIsOpen(false);
71
79
  };
72
80
 
@@ -83,7 +91,7 @@ const SimpleSelectBase: React.FunctionComponent<SimpleSelectProps> = ({
83
91
  }
84
92
  {...toggleProps}
85
93
  >
86
- {toggleContent ? toggleContent : selected}
94
+ {toggleContent ? toggleContent : selected?.content || placeholder}
87
95
  </MenuToggle>
88
96
  );
89
97
 
@@ -22,22 +22,32 @@ export interface TypeaheadSelectOption extends Omit<SelectOptionProps, 'content'
22
22
  value: string | number;
23
23
  }
24
24
 
25
- export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle'> {
25
+ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle' | 'onSelect'> {
26
26
  /** @hide Forwarded ref */
27
27
  innerRef?: React.Ref<any>;
28
28
  /** Initial options of the select. */
29
29
  initialOptions: TypeaheadSelectOption[];
30
30
  /** Callback triggered on selection. */
31
31
  onSelect?: (
32
- _event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement>,
32
+ _event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement> | undefined,
33
33
  selection: string | number
34
34
  ) => void;
35
35
  /** Callback triggered when the select opens or closes. */
36
36
  onToggle?: (nextIsOpen: boolean) => void;
37
37
  /** Callback triggered when the text in the input field changes. */
38
38
  onInputChange?: (newValue: string) => void;
39
+ /** Callback triggered when the clear button is selected */
40
+ onClearSelection?: () => void;
39
41
  /** Placeholder text for the select input. */
40
42
  placeholder?: string;
43
+ /** Flag to indicate if the typeahead select allows new items */
44
+ isCreatable?: boolean;
45
+ /** Flag to indicate if create option should be at top of typeahead */
46
+ isCreateOptionOnTop?: boolean;
47
+ /** Message to display to create a new option */
48
+ createOptionMessage?: string | ((newValue: string) => string);
49
+ /** Message to display when no options are available. */
50
+ noOptionsAvailableMessage?: string;
41
51
  /** Message to display when no options match the filter. */
42
52
  noOptionsFoundMessage?: string | ((filter: string) => string);
43
53
  /** Flag indicating the select should be disabled. */
@@ -48,22 +58,32 @@ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle'> {
48
58
  toggleProps?: MenuToggleProps;
49
59
  }
50
60
 
61
+ const defaultNoOptionsFoundMessage = (filter: string) => `No results found for "${filter}"`;
62
+ const defaultCreateOptionMessage = (newValue: string) => `Create "${newValue}"`;
63
+
51
64
  export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps> = ({
52
65
  innerRef,
53
66
  initialOptions,
54
67
  onSelect,
55
68
  onToggle,
56
69
  onInputChange,
70
+ onClearSelection,
57
71
  placeholder = 'Select an option',
58
- noOptionsFoundMessage = (filter) => `No results found for "${filter}"`,
72
+ noOptionsAvailableMessage = 'No options are available',
73
+ noOptionsFoundMessage = defaultNoOptionsFoundMessage,
74
+ isCreatable = false,
75
+ isCreateOptionOnTop = false,
76
+ createOptionMessage = defaultCreateOptionMessage,
59
77
  isDisabled,
60
78
  toggleWidth,
61
79
  toggleProps,
62
80
  ...props
63
81
  }: TypeaheadSelectProps) => {
64
82
  const [isOpen, setIsOpen] = React.useState(false);
65
- const [selected, setSelected] = React.useState<string>('');
66
- const [inputValue, setInputValue] = React.useState<string>('');
83
+ const [selected, setSelected] = React.useState<string>(String(initialOptions.find((o) => o.selected)?.content ?? ''));
84
+ const [inputValue, setInputValue] = React.useState<string>(
85
+ String(initialOptions.find((o) => o.selected)?.content ?? '')
86
+ );
67
87
  const [filterValue, setFilterValue] = React.useState<string>('');
68
88
  const [selectOptions, setSelectOptions] = React.useState<TypeaheadSelectOption[]>(initialOptions);
69
89
  const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
@@ -81,6 +101,20 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
81
101
  String(option.content).toLowerCase().includes(filterValue.toLowerCase())
82
102
  );
83
103
 
104
+ if (
105
+ isCreatable &&
106
+ filterValue &&
107
+ !initialOptions.find((o) => String(o.content).toLowerCase() === filterValue.toLowerCase())
108
+ ) {
109
+ const createOption = {
110
+ content: typeof createOptionMessage === 'string' ? createOptionMessage : createOptionMessage(filterValue),
111
+ value: filterValue
112
+ };
113
+ newSelectOptions = isCreateOptionOnTop
114
+ ? [createOption, ...newSelectOptions]
115
+ : [...newSelectOptions, createOption];
116
+ }
117
+
84
118
  // When no options are found after filtering, display 'No results found'
85
119
  if (!newSelectOptions.length) {
86
120
  newSelectOptions = [
@@ -94,18 +128,45 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
94
128
  }
95
129
 
96
130
  // Open the menu when the input value changes and the new value is not empty
97
- if (!isOpen) {
98
- openMenu();
99
- }
131
+ openMenu();
132
+ }
133
+
134
+ // When no options are available, display 'No options available'
135
+ if (!newSelectOptions.length) {
136
+ newSelectOptions = [
137
+ {
138
+ isAriaDisabled: true,
139
+ content: noOptionsAvailableMessage,
140
+ value: NO_RESULTS
141
+ }
142
+ ];
100
143
  }
101
144
 
102
145
  setSelectOptions(newSelectOptions);
103
- }, [filterValue, initialOptions]);
146
+ }, [
147
+ filterValue,
148
+ initialOptions,
149
+ noOptionsFoundMessage,
150
+ isCreatable,
151
+ isCreateOptionOnTop,
152
+ createOptionMessage,
153
+ noOptionsAvailableMessage
154
+ ]);
155
+
156
+ React.useEffect(() => {
157
+ // If the selected option changed and the current input value is the previously selected item, update the displayed value.
158
+ const selectedOption = initialOptions.find((o) => o.selected);
159
+ if (inputValue === selected && selectedOption?.value !== selected) {
160
+ setInputValue(String(selectedOption?.content ?? ''));
161
+ }
162
+ // Only update when options change
163
+ // eslint-disable-next-line react-hooks/exhaustive-deps
164
+ }, [initialOptions]);
104
165
 
105
166
  const setActiveAndFocusedItem = (itemIndex: number) => {
106
167
  setFocusedItemIndex(itemIndex);
107
168
  const focusedItem = selectOptions[itemIndex];
108
- setActiveItemId(focusedItem.value as string);
169
+ setActiveItemId(String(focusedItem.value));
109
170
  };
110
171
 
111
172
  const resetActiveAndFocusedItem = () => {
@@ -114,14 +175,20 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
114
175
  };
115
176
 
116
177
  const openMenu = () => {
117
- onToggle && onToggle(true);
118
- setIsOpen(true);
178
+ if (!isOpen) {
179
+ onToggle && onToggle(true);
180
+ setIsOpen(true);
181
+ }
119
182
  };
120
183
 
121
184
  const closeMenu = () => {
122
185
  onToggle && onToggle(false);
123
186
  setIsOpen(false);
124
187
  resetActiveAndFocusedItem();
188
+ const option = initialOptions.find((o) => o.value === selected);
189
+ if (option) {
190
+ setInputValue(String(option.content));
191
+ }
125
192
  };
126
193
 
127
194
  const onInputClick = () => {
@@ -158,18 +225,12 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
158
225
  setFilterValue(value);
159
226
 
160
227
  resetActiveAndFocusedItem();
161
-
162
- if (value !== selected) {
163
- setSelected('');
164
- }
165
228
  };
166
229
 
167
230
  const handleMenuArrowKeys = (key: string) => {
168
231
  let indexToFocus = 0;
169
232
 
170
- if (!isOpen) {
171
- openMenu();
172
- }
233
+ openMenu();
173
234
 
174
235
  if (selectOptions.every((option) => option.isDisabled)) {
175
236
  return;
@@ -221,10 +282,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
221
282
  selectOption(event, focusedItem);
222
283
  }
223
284
 
224
- if (!isOpen) {
225
- onToggle && onToggle(true);
226
- setIsOpen(true);
227
- }
285
+ openMenu();
228
286
 
229
287
  break;
230
288
  case 'ArrowUp':
@@ -238,7 +296,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
238
296
  const onToggleClick = () => {
239
297
  onToggle && onToggle(!isOpen);
240
298
  setIsOpen(!isOpen);
241
- textInputRef?.current?.focus();
299
+ textInputRef.current?.focus();
242
300
  };
243
301
 
244
302
  const onClearButtonClick = () => {
@@ -247,7 +305,8 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
247
305
  onInputChange && onInputChange('');
248
306
  setFilterValue('');
249
307
  resetActiveAndFocusedItem();
250
- textInputRef?.current?.focus();
308
+ textInputRef.current?.focus();
309
+ onClearSelection && onClearSelection();
251
310
  };
252
311
 
253
312
  const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (