Skip to content

Commit 61426c1

Browse files
committed
feat(@clayui/autocomplete): LPD-54854 Allow adding new items to the autocomplete dropdown
1 parent 12f07ad commit 61426c1

File tree

1 file changed

+39
-9
lines changed

1 file changed

+39
-9
lines changed

packages/clay-autocomplete/src/Autocomplete.tsx

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,24 @@ export interface IProps<T>
114114
* Messages for the Autocomplete.
115115
*/
116116
messages?: {
117+
addCustomValue?: string;
117118
listCount?: string;
118119
listCountPlural?: string;
119120
loading: string;
120121
notFound: string;
122+
setAsHTML?: boolean;
121123
};
122124

123125
/**
124126
* Callback for when the active state changes (controlled).
125127
*/
126128
onActiveChange?: InternalDispatch<boolean>;
127129

130+
/**
131+
* Callback called when an item is added to the autocomplete list.
132+
*/
133+
onAddNewItem?: (val: T) => void;
134+
128135
/**
129136
* Callback called when input value changes (controlled).
130137
*/
@@ -189,10 +196,12 @@ function hasItem<T extends Item>(
189196
const ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g;
190197

191198
const defaultMessages = {
199+
addCustomValue: 'Add {0}',
192200
listCount: '{0} option available.',
193201
listCountPlural: '{0} options available.',
194202
loading: 'Loading...',
195203
notFound: 'No results found',
204+
setAsHTML: false,
196205
};
197206

198207
function AutocompleteInner<T extends Item>(
@@ -214,6 +223,7 @@ function AutocompleteInner<T extends Item>(
214223
menuTrigger = 'input',
215224
messages,
216225
onActiveChange,
226+
onAddNewItem,
217227
onChange,
218228
onItemsChange,
219229
onLoadMore,
@@ -227,7 +237,7 @@ function AutocompleteInner<T extends Item>(
227237
...(messages ?? {}),
228238
};
229239

230-
const [items, , isItemsUncontrolled] = useControlledState({
240+
const [items, setItems, isItemsUncontrolled] = useControlledState({
231241
defaultName: 'defaultItems',
232242
defaultValue: defaultItems,
233243
handleName: 'onItemsChange',
@@ -301,9 +311,8 @@ function AutocompleteInner<T extends Item>(
301311
}, [items]);
302312

303313
useEffect(() => {
304-
// Does not update state on first render, if the custom value is allowed
305-
// or if the value is empty.
306-
if (isFirst || allowsCustomValue || !value) {
314+
// Does not update state on first render, if the value is empty.
315+
if (isFirst || !value) {
307316
return;
308317
}
309318

@@ -352,6 +361,8 @@ function AutocompleteInner<T extends Item>(
352361
});
353362
}, [debouncedLoadingChange, isItemsUncontrolled, items, filterFn]);
354363

364+
const [activeDescendant, setActiveDescendant] = useState<React.Key>('');
365+
355366
const virtualizer = useVirtual({
356367
estimateSize: 37,
357368
items: filteredItems,
@@ -392,6 +403,7 @@ function AutocompleteInner<T extends Item>(
392403
setActive(false);
393404

394405
currentItemSelected.current = itemValue;
406+
395407
setValue(itemValue);
396408

397409
shouldIgnoreOpenMenuOnFocus.current = true;
@@ -405,19 +417,24 @@ function AutocompleteInner<T extends Item>(
405417
items: filteredItems,
406418
notFound: (
407419
<DropDown.Item
408-
aria-disabled="true"
409-
className="disabled"
420+
disabled={allowsCustomValue ? false : true}
410421
roleItem="option"
411422
>
412-
{messages.notFound}
423+
{allowsCustomValue && messages.addCustomValue ? (
424+
<span
425+
dangerouslySetInnerHTML={{
426+
__html: sub(messages.addCustomValue, [value]),
427+
}}
428+
/>
429+
) : (
430+
messages.notFound
431+
)}
413432
</DropDown.Item>
414433
),
415434
suppressTextValueWarning: false,
416435
virtualizer: items ? virtualizer : undefined,
417436
});
418437

419-
const [activeDescendant, setActiveDescendant] = useState<React.Key>('');
420-
421438
useOverlayPosition(
422439
{
423440
alignmentByViewport: true,
@@ -554,6 +571,16 @@ function AutocompleteInner<T extends Item>(
554571
break;
555572
}
556573
case Keys.Enter: {
574+
if (allowsCustomValue && items && onAddNewItem) {
575+
if (value !== '') {
576+
if (!hasItem(items, value, filterKey) && isItemsUncontrolled) {
577+
setItems([...items, value].sort());
578+
}
579+
580+
onAddNewItem(value as T);
581+
}
582+
}
583+
557584
setActive(false);
558585

559586
if (active && activeDescendant) {
@@ -563,6 +590,7 @@ function AutocompleteInner<T extends Item>(
563590
if (!active && event.key === Keys.Esc) {
564591
setValue('');
565592
}
593+
566594
break;
567595
}
568596
case Keys.Home:
@@ -607,6 +635,7 @@ function AutocompleteInner<T extends Item>(
607635
}
608636

609637
navigationProps.onKeyDown(event);
638+
610639
break;
611640
}
612641
default:
@@ -657,6 +686,7 @@ function AutocompleteInner<T extends Item>(
657686
onLoadMore={onLoadMore}
658687
role="listbox"
659688
>
689+
{activeDescendant}
660690
{debouncedLoadingChange ? (
661691
<DropDown.Item
662692
aria-disabled="true"

0 commit comments

Comments
 (0)