import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import classNames from 'classnames'; import './MultiSelect.scss'; import Dropdown from './Dropdown'; import Icon from '../Icon'; import { MultiSelectProps, Option } from './MultiSelectTypes'; const ChipElement = ({ label, onChipCloseClick, }: { label: string; onChipCloseClick: (e: React.MouseEvent) => void; }) => { if (label) { return (
{label}
); } return null; }; const MultiSelect = ({ options, selectedOptions = [], onChange = () => {}, zIndex = 100, label = '', onSearch = () => {}, required = false, disabled = false, errorMessage = 'Fill this field', }: MultiSelectProps) => { const [isOpen, setIsOpen] = useState(false); const [allOptions, setAllOptions] = useState(options); const [searchedKeyword, setSearchedKeyword] = useState(''); const [isSelectFocusedOnce, setIsSelectFocusedOnce] = useState(false); const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; width: number; fromBottom: number; selectHeight: number; }>({ top: 0, left: 0, width: 0, fromBottom: 0, selectHeight: 0, }); const inputRef = useRef(null); const dropdownWrapper = useRef(null); const dropdownRef = useRef(null); const selectWrapper = useRef(null); let isFieldSkipped = isSelectFocusedOnce && selectedOptions.length === 0; const handleClick = () => { if (!isOpen) { setIsOpen(true); } }; const toggleDropdown = () => { inputRef.current?.focus(); setIsOpen((prev) => !prev); if (!isSelectFocusedOnce && isOpen) { setIsSelectFocusedOnce(true); } }; const handleOptionChange = (selectedOption: Option, isChecked: boolean) => { inputRef.current?.focus(); const updatedOptions = allOptions.map((option) => option.value === selectedOption.value ? { ...option, isChecked } : option ); setAllOptions(updatedOptions); const tempCheckedOptions = updatedOptions .filter((option) => option.isChecked) .map(({ isChecked, ...rest }) => rest); if (!isSelectFocusedOnce) { setIsSelectFocusedOnce(true); } onChange && onChange(tempCheckedOptions); }; const handleChipCloseClick = ( option: Option, e: React.MouseEvent ) => { e.stopPropagation(); handleOptionChange(option, false); }; const calculatePosition = () => { if (dropdownWrapper.current && selectWrapper.current) { const rect = dropdownWrapper.current?.getBoundingClientRect(); const rect2 = selectWrapper.current?.getBoundingClientRect(); setDropdownPosition({ top: rect.bottom + window.scrollY, left: rect.left + window.scrollX, width: rect.width, fromBottom: document.documentElement.clientHeight - (rect.bottom - 4), selectHeight: rect2.height, }); } }; useEffect(() => { if (isOpen) { calculatePosition(); } }, [isOpen, allOptions]); const onSelectToggleScroll = (isEnabled: boolean) => { const bodyScrollWidth = window.innerWidth - document.body.clientWidth; document.body.style.paddingRight = isEnabled ? '' : `${bodyScrollWidth}px`; document.body.style.overflow = isEnabled ? '' : 'hidden'; }; useEffect(() => { if (isOpen) { onSelectToggleScroll(!isOpen); } if (dropdownRef?.current) { inputRef?.current?.focus(); } return () => { onSelectToggleScroll(true); }; }, [isOpen]); useEffect(() => { if (options?.length > 0) { const initializeOptions = options.map((option) => ({ ...option, isChecked: selectedOptions.some( (selectedOption) => selectedOption.value === option.value ), })); setAllOptions(initializeOptions); } const handleClickAnywhere = (event: MouseEvent) => { if ( dropdownWrapper.current && inputRef.current && selectWrapper.current && dropdownRef.current && !inputRef.current.contains(event?.target as Node) && !dropdownRef.current.contains(event?.target as Node) && !selectWrapper.current.contains(event?.target as Node) ) { setIsOpen(false); if (!isSelectFocusedOnce) { setIsSelectFocusedOnce(true); } } }; window.addEventListener('resize', calculatePosition); window.addEventListener('click', handleClickAnywhere); return () => { window.removeEventListener('resize', calculatePosition); window.removeEventListener('click', handleClickAnywhere); document.body.style.overflow = 'auto'; }; }, []); return (
{label}
{selectedOptions.map((option) => ( handleChipCloseClick(option, e)} /> ))}
{ if (!isOpen) { setIsOpen(true); } setSearchedKeyword(e.target.value); onSearch?.(e.target.value); }} id="input-ele" className="ff-select-input" style={{ display: isOpen || selectedOptions.length ? 'inherit' : 'none', }} />
{isFieldSkipped && required && errorMessage && (
{errorMessage}
)} {isOpen && createPortal( , document.body )}
); }; export default MultiSelect;