import React, { useState, useRef, useEffect, useCallback } from "react"; import classNames from "classnames"; import Styles from "./dropdown.scss"; import { OutsideClickHandler } from "./OutsideClickHandler"; interface Theme { isOpen?: string; dropdown?: string; button?: string; list?: string; btnOption?: string; icon?: React.ReactNode; } type MappedOption = { [key in T]: string; }; type Option

= MappedOption

& { href?: string; download?: string; }; interface DropdownProps< P extends string = "name", T extends Option

= Option

> { theme?: Theme; options?: T[]; selected?: T; selectOption?: (option: T, index: number) => void; textProperty?: P; matchWidth?: boolean; children?: React.ReactNode; disabled?: boolean; align?: "left" | "right"; } const Dropdown =

= Option

>({ theme = {}, options = [], selected, selectOption, textProperty = "name" as P, disabled = false, children }: DropdownProps) => { const [isOpen, setIsOpen] = useState(false); const buttonRef = useRef(null); const scrollListeners = useRef([]); const hideList = useCallback(() => { setIsOpen(false); }, []); const showList = useCallback(() => { setIsOpen(true); }, []); const select = useCallback( (option: T, index: number) => { selectOption?.(option, index); hideList(); }, [selectOption, hideList] ); const addScrollListeners = useCallback( (element: Element | null, listeningToSoFar: Element[]): Element[] => { if (!element) return listeningToSoFar; if (element.scrollHeight > element.clientHeight) { element.addEventListener("scroll", hideList); listeningToSoFar.push(element); } if (element !== document.body) { return addScrollListeners(element.parentElement, listeningToSoFar); } return listeningToSoFar; }, [hideList] ); const clearListeners = useCallback(() => { scrollListeners.current.forEach((element) => element?.removeEventListener("scroll", hideList) ); scrollListeners.current = []; }, [hideList]); useEffect(() => { if (isOpen) { scrollListeners.current = addScrollListeners(buttonRef.current, []); } else { clearListeners(); } return clearListeners; }, [addScrollListeners, clearListeners, hideList, isOpen]); const selectedText = selected?.[textProperty]; return (

    {options.map((option, i) => { const optionText = option[textProperty]; return (
  • {option.href ? ( {optionText} ) : ( )}
  • ); })}
); }; export default Dropdown;