import { memo, useCallback, useEffect, useState } from 'react';
import clsx from 'clsx';
import type { AutocompleteRenderGetTagProps } from '@mui/material/Autocomplete';
import type { FilterOptionsState } from '@mui/material/useAutocomplete';
import Tooltip from '@mui/material/Tooltip';
import { ASSETS_URL } from '../../../consts/common';
import { Chips } from '../../chips';
import type { ChipsProps } from '../../chips';
import { CustomIcon } from '../../custom-icon';
import { truncateWithEllipsis } from '../../../utils';
import { AutocompleteWithListbox } from './components/autocomplete-with-listbox';
import type { AutocompleteProps, AutocompleteOptionProps } from './types';
import createClasses from './styles';
/**
* The autocomplete is a normal text input enhanced by a panel of suggested options.
* It's meant to be an improved version of the "react-select" and "downshift" packages.
* @link https://mui.com/material-ui/react-autocomplete/
*/
const Autocomplete = (props: AutocompleteProps) => {
const {
defaultLimitTags = 2,
showLimitTagsTooltip,
size = 'medium',
handleClear,
handleCreate,
handleDelete,
listItemsMapFN,
menuContentProps,
...otherProps
} = props;
const classes = createClasses({ multiple: otherProps.multiple, size });
const defaultValue =
(typeof otherProps.defaultValue === 'string' && otherProps.defaultValue) || '';
const hasStringInputValue = !otherProps.multiple && typeof otherProps.value === 'string';
const [inputText, setInputText] = useState(defaultValue);
useEffect(() => {
if (hasStringInputValue) setInputText(otherProps.value as string);
}, [hasStringInputValue, setInputText, otherProps.value]);
const getLimitTagsTooltip: AutocompleteProps['getLimitTagsText'] = more => {
const tooltipTitle = (otherProps.value as AutocompleteOptionProps[])
?.slice(defaultLimitTags)
.map(option => option?.name)
.join(', ');
return (
{`+${more}`}
);
};
const onBlur = useCallback>(
e => {
if (inputText && otherProps.freeSolo) {
handleCreate?.(e, [inputText] as unknown as AutocompleteOptionProps[]);
setInputText('');
}
},
[handleCreate, inputText, otherProps.freeSolo]
);
const onChange = useCallback>(
(e, v, reason) => {
switch (reason) {
case 'clear':
if (hasStringInputValue) {
setInputText('');
}
handleClear?.(e);
break;
case 'createOption':
handleCreate?.(e, v as AutocompleteOptionProps[]);
break;
case 'removeOption':
handleDelete?.(e, v as AutocompleteOptionProps[]);
break;
}
},
[handleClear, handleCreate, handleDelete, hasStringInputValue]
);
const getOptSearchWords = (option: AutocompleteOptionProps) => {
const { name, searchWords: _searchWords = [], additionalProps } = option;
const searchWords = [..._searchWords, name];
if (additionalProps)
searchWords.push(
...Object.entries(additionalProps)
.filter(ent => typeof ent[1] === 'string')
.map(ent => ent[1] as string)
);
return searchWords;
};
const filterOptions = (
options: AutocompleteOptionProps[],
params: FilterOptionsState
) => {
const inputValue = (hasStringInputValue ? inputText : params.inputValue).toLowerCase();
const opts = options.filter(option => {
const optSearchWords = getOptSearchWords(option);
const found = optSearchWords.some(word =>
word?.toString().toLowerCase().includes(inputValue)
);
return found;
});
return opts;
};
const renderTags = (
tagValue: AutocompleteOptionProps[],
getTagProps: AutocompleteRenderGetTagProps
) => {
return tagValue.map((option: AutocompleteOptionProps, index) => {
const tagProps: ChipsProps = getTagProps({ index });
return (
);
});
};
const clearIcon = (
);
return (
(typeof option !== 'string' && option.name?.toString()) || ''}
inputText={inputText}
isOptionEqualToValue={(o, v) => o.name === v.name}
onBlur={onBlur}
onChange={onChange}
renderOption={(_, option) => option}
renderTags={renderTags}
size={size}
{...otherProps}
listItemsMapFN={listItemsMapFN}
menuContentProps={menuContentProps}
setInputText={setInputText}
/>
);
};
const m = memo(Autocomplete);
export { m as Autocomplete };