import React, { type ReactNode } from 'react'; import { DropdownItem } from '../../DropdownItem'; import { Combobox } from '../../Combobox'; import type { AutocompleteProps } from '../Autocomplete'; import type { Option } from '../option'; import { useAutocompleteCombobox } from '../hooks'; import styles from './AutocompleteWithAddOption.module.css'; export const addOptionItemKey = 'add-option-item'; export type Props = AutocompleteProps & { /** * Function to render the add option item. It should always return a DropdownItem component. */ renderAddOption(rawValue: string): ReactNode; /** * Handler that is called when an option has been added. */ onAddOption(newOptionLabel: string): T | Promise; }; export const AutocompleteWithAddOption = ({ name, value, options, placeholder, isLoading, showClearSelectionButton = false, renderPrefix, renderOption, renderOptionGroup, renderAddOption, renderNoOptions, onAddOption, onSearch, onSelect, ...comboboxProps }: Props) => { let internalOptions = options.length === 0 && value ? [value, { key: addOptionItemKey, label: '' } as T] : [...options, { key: addOptionItemKey, label: '' } as T]; const { isOpen, inputValue, selectedItem, isLoading: internalIsLoading, getMenuProps, getItemProps, getInputProps, getToggleButtonProps, selectItem, reset, } = useAutocompleteCombobox({ options: internalOptions, value, isLoading, onSelect, onSearch, onSelectedItemChange: async ({ selectedItem: newSelectedItem }) => { if (newSelectedItem?.key === addOptionItemKey) { const newOption = await onAddOption(inputValue); onSelect(newOption); selectItem(newOption); } else { onSelect(newSelectedItem ?? undefined); } }, }); // Remove 'Add option' item if the input search is empty if (inputValue.trim().length === 0) { internalOptions = internalOptions.filter( (option) => option.key !== addOptionItemKey, ); } return ( renderPrefix?.(selectedItem) ? (
{renderPrefix(selectedItem)}
) : null } renderOption={(option) => { const isSelected = value?.key === option.key; if (option.key === addOptionItemKey) { const hasInputValue = !!inputValue; const doesOptionAlreadyExist = internalOptions.some( (option) => option.label.toLowerCase() === inputValue.toLowerCase().trim(), ); const shouldShowAddOptionItem = hasInputValue && !doesOptionAlreadyExist; return shouldShowAddOptionItem && renderAddOption(inputValue); } return renderOption ? ( renderOption(option, { isSelected }) ) : ( ); }} renderOptionGroup={renderOptionGroup} renderNoOptions={() => renderNoOptions ? renderNoOptions(inputValue) : null } propsGetters={{ getInputProps: () => getInputProps({ name, placeholder, }), getToggleButtonProps, getItemProps: ({ index, item }) => getItemProps({ index, item }), getMenuProps, }} onClearSelection={ selectedItem && showClearSelectionButton ? reset : undefined } /> ); };