96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import useOpenable from "@Front/Hooks/useOpenable";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
import { getLabel } from "../Dropdown";
|
|
import DropdownMenu from "../Dropdown/DropdownMenu";
|
|
import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
|
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
|
import ChipContainer from "./ChipContainer";
|
|
import classes from "./classes.module.scss";
|
|
|
|
type IProps = {
|
|
options: IOption[];
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
label?: string;
|
|
onSelectionChange?: (options: IOption[] | null) => void;
|
|
selectedOptions?: IOption[] | null;
|
|
};
|
|
|
|
export default function AutocompleteMultiSelect(props: IProps) {
|
|
const { onSelectionChange, options, placeholder, disabled, label, selectedOptions: selectedOptionsProps } = props;
|
|
const [selectedOptions, setSelectedOptions] = useState<IOption[] | null>(selectedOptionsProps ?? null);
|
|
const [searchValue, setSearchValue] = useState("");
|
|
const [filteredOptions, setFilteredOptions] = useState<IOption[]>(options);
|
|
const openable = useOpenable({ defaultOpen: false });
|
|
|
|
useEffect(() => {
|
|
if (searchValue) {
|
|
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
|
|
if (filteredOptions.length === 0)
|
|
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
|
|
return setFilteredOptions(filteredOptions);
|
|
}
|
|
return setFilteredOptions(options);
|
|
}, [searchValue, options]);
|
|
|
|
const handleSearchChange = useCallback(
|
|
(value: string) => {
|
|
setSearchValue(value);
|
|
if (value) {
|
|
openable.open();
|
|
} else {
|
|
openable.close();
|
|
}
|
|
},
|
|
[openable],
|
|
);
|
|
|
|
const handleChange = useCallback(
|
|
(options: IOption[] | null) => {
|
|
setSelectedOptions(options);
|
|
onSelectionChange?.(options);
|
|
},
|
|
[onSelectionChange],
|
|
);
|
|
|
|
useEffect(() => {
|
|
setSelectedOptions(selectedOptionsProps ?? null);
|
|
}, [selectedOptionsProps]);
|
|
|
|
const handleSelectOption = useCallback(
|
|
(_newOption: IOption, options: IOption[]) => {
|
|
handleChange(options);
|
|
setSearchValue("");
|
|
openable.close();
|
|
},
|
|
[handleChange, openable],
|
|
);
|
|
|
|
return (
|
|
<DropdownMenu
|
|
options={filteredOptions}
|
|
openable={openable}
|
|
onSelect={handleSelectOption}
|
|
selectedOptions={selectedOptions ? selectedOptions : []}>
|
|
<div className={classes["root"]}>
|
|
{label && (
|
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
|
{label}
|
|
</Typography>
|
|
)}
|
|
</div>
|
|
<ChipContainer
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
onChange={handleSearchChange}
|
|
value={searchValue}
|
|
onClear={() => handleChange(null)}
|
|
onFocus={openable.open}
|
|
selectedOptions={selectedOptions ?? []}
|
|
onSelectedOptionsChange={handleChange}
|
|
/>
|
|
</DropdownMenu>
|
|
);
|
|
}
|