@patternfly/react-templates 1.1.0-prerelease.36 → 1.1.0-prerelease.38

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.
@@ -22,14 +22,14 @@ 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. */
@@ -40,6 +40,12 @@ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle'> {
40
40
  onClearSelection?: () => void;
41
41
  /** Placeholder text for the select input. */
42
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);
43
49
  /** Message to display when no options are available. */
44
50
  noOptionsAvailableMessage?: string;
45
51
  /** Message to display when no options match the filter. */
@@ -52,6 +58,9 @@ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle'> {
52
58
  toggleProps?: MenuToggleProps;
53
59
  }
54
60
 
61
+ const defaultNoOptionsFoundMessage = (filter: string) => `No results found for "${filter}"`;
62
+ const defaultCreateOptionMessage = (newValue: string) => `Create "${newValue}"`;
63
+
55
64
  export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps> = ({
56
65
  innerRef,
57
66
  initialOptions,
@@ -61,7 +70,10 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
61
70
  onClearSelection,
62
71
  placeholder = 'Select an option',
63
72
  noOptionsAvailableMessage = 'No options are available',
64
- noOptionsFoundMessage = (filter) => `No results found for "${filter}"`,
73
+ noOptionsFoundMessage = defaultNoOptionsFoundMessage,
74
+ isCreatable = false,
75
+ isCreateOptionOnTop = false,
76
+ createOptionMessage = defaultCreateOptionMessage,
65
77
  isDisabled,
66
78
  toggleWidth,
67
79
  toggleProps,
@@ -89,6 +101,20 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
89
101
  String(option.content).toLowerCase().includes(filterValue.toLowerCase())
90
102
  );
91
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
+
92
118
  // When no options are found after filtering, display 'No results found'
93
119
  if (!newSelectOptions.length) {
94
120
  newSelectOptions = [
@@ -102,9 +128,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
102
128
  }
103
129
 
104
130
  // Open the menu when the input value changes and the new value is not empty
105
- if (!isOpen) {
106
- openMenu();
107
- }
131
+ openMenu();
108
132
  }
109
133
 
110
134
  // When no options are available, display 'No options available'
@@ -119,17 +143,30 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
119
143
  }
120
144
 
121
145
  setSelectOptions(newSelectOptions);
122
- }, [filterValue, initialOptions]);
146
+ }, [
147
+ filterValue,
148
+ initialOptions,
149
+ noOptionsFoundMessage,
150
+ isCreatable,
151
+ isCreateOptionOnTop,
152
+ createOptionMessage,
153
+ noOptionsAvailableMessage
154
+ ]);
123
155
 
124
156
  React.useEffect(() => {
157
+ // If the selected option changed and the current input value is the previously selected item, update the displayed value.
125
158
  const selectedOption = initialOptions.find((o) => o.selected);
126
- setInputValue(String(selectedOption?.content ?? ''));
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
127
164
  }, [initialOptions]);
128
165
 
129
166
  const setActiveAndFocusedItem = (itemIndex: number) => {
130
167
  setFocusedItemIndex(itemIndex);
131
168
  const focusedItem = selectOptions[itemIndex];
132
- setActiveItemId(focusedItem.value as string);
169
+ setActiveItemId(String(focusedItem.value));
133
170
  };
134
171
 
135
172
  const resetActiveAndFocusedItem = () => {
@@ -138,8 +175,10 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
138
175
  };
139
176
 
140
177
  const openMenu = () => {
141
- onToggle && onToggle(true);
142
- setIsOpen(true);
178
+ if (!isOpen) {
179
+ onToggle && onToggle(true);
180
+ setIsOpen(true);
181
+ }
143
182
  };
144
183
 
145
184
  const closeMenu = () => {
@@ -191,9 +230,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
191
230
  const handleMenuArrowKeys = (key: string) => {
192
231
  let indexToFocus = 0;
193
232
 
194
- if (!isOpen) {
195
- openMenu();
196
- }
233
+ openMenu();
197
234
 
198
235
  if (selectOptions.every((option) => option.isDisabled)) {
199
236
  return;
@@ -245,10 +282,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
245
282
  selectOption(event, focusedItem);
246
283
  }
247
284
 
248
- if (!isOpen) {
249
- onToggle && onToggle(true);
250
- setIsOpen(true);
251
- }
285
+ openMenu();
252
286
 
253
287
  break;
254
288
  case 'ArrowUp':
@@ -262,7 +296,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
262
296
  const onToggleClick = () => {
263
297
  onToggle && onToggle(!isOpen);
264
298
  setIsOpen(!isOpen);
265
- textInputRef?.current?.focus();
299
+ textInputRef.current?.focus();
266
300
  };
267
301
 
268
302
  const onClearButtonClick = () => {
@@ -271,7 +305,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
271
305
  onInputChange && onInputChange('');
272
306
  setFilterValue('');
273
307
  resetActiveAndFocusedItem();
274
- textInputRef?.current?.focus();
308
+ textInputRef.current?.focus();
275
309
  onClearSelection && onClearSelection();
276
310
  };
277
311
 
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { TypeaheadSelect, TypeaheadSelectOption } from '@patternfly/react-templates';
3
+ import { Checkbox } from '@patternfly/react-core';
3
4
 
4
5
  const Options = [
5
6
  { content: 'Alabama', value: 'option1' },
@@ -13,10 +14,13 @@ const Options = [
13
14
  /* eslint-disable no-console */
14
15
  export const SelectTypeaheadDemo: React.FunctionComponent = () => {
15
16
  const [selected, setSelected] = React.useState<string | undefined>();
17
+ const [options, setOptions] = React.useState(Options);
18
+ const [isCreatable, setIsCreatable] = React.useState<boolean>(false);
19
+ const [isCreateOptionOnTop, setIsCreateOptionOnTop] = React.useState<boolean>(false);
16
20
 
17
21
  const initialOptions = React.useMemo<TypeaheadSelectOption[]>(
18
- () => Options.map((o) => ({ ...o, selected: o.value === selected })),
19
- [selected]
22
+ () => options.map((o) => ({ ...o, selected: o.value === selected })),
23
+ [options, selected]
20
24
  );
21
25
 
22
26
  React.useEffect(() => {
@@ -24,12 +28,38 @@ export const SelectTypeaheadDemo: React.FunctionComponent = () => {
24
28
  }, [selected]);
25
29
 
26
30
  return (
27
- <TypeaheadSelect
28
- initialOptions={initialOptions}
29
- placeholder="Select a state"
30
- noOptionsFoundMessage={(filter) => `No state was found for "${filter}"`}
31
- onClearSelection={() => setSelected(undefined)}
32
- onSelect={(_ev, selection) => setSelected(String(selection))}
33
- />
31
+ <>
32
+ <TypeaheadSelect
33
+ initialOptions={initialOptions}
34
+ placeholder="Select a state"
35
+ noOptionsFoundMessage={(filter) => `No state was found for "${filter}"`}
36
+ onClearSelection={() => setSelected(undefined)}
37
+ onSelect={(_ev, selection) => {
38
+ if (!options.find((o) => o.value === selection)) {
39
+ setOptions([...options, { content: String(selection), value: String(selection) }]);
40
+ }
41
+ setSelected(String(selection));
42
+ }}
43
+ isCreatable={isCreatable}
44
+ isCreateOptionOnTop={isCreateOptionOnTop}
45
+ />
46
+ <Checkbox
47
+ className="pf-u-mt-sm"
48
+ label="isCreatable"
49
+ isChecked={isCreatable}
50
+ onChange={(_event, checked) => setIsCreatable(checked)}
51
+ aria-label="toggle creatable checkbox"
52
+ id="toggle-creatable-typeahead"
53
+ name="toggle-creatable-typeahead"
54
+ />
55
+ <Checkbox
56
+ label="isCreateOptionOnTop"
57
+ isChecked={isCreateOptionOnTop}
58
+ onChange={(_event, checked) => setIsCreateOptionOnTop(checked)}
59
+ aria-label="toggle createOptionOnTop checkbox"
60
+ id="toggle-create-option-on-top-typeahead"
61
+ name="toggle-create-option-on-top-typeahead"
62
+ />
63
+ </>
34
64
  );
35
65
  };