diff --git a/src/components/Form/Settings/Dropdown/Dropdown.jsx b/src/components/Form/Settings/Dropdown/Dropdown.jsx index a5bc6304..c9ce5c63 100644 --- a/src/components/Form/Settings/Dropdown/Dropdown.jsx +++ b/src/components/Form/Settings/Dropdown/Dropdown.jsx @@ -1,7 +1,7 @@ import variables from 'config/variables'; import { memo, useState, useCallback, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; -import { MdExpandMore, MdCheck, MdRefresh } from 'react-icons/md'; +import { MdExpandMore, MdCheck, MdRefresh, MdClose } from 'react-icons/md'; import { toast } from 'react-toastify'; import EventBus from 'utils/eventbus'; @@ -14,10 +14,12 @@ const Dropdown = memo((props) => { const [isClosing, setIsClosing] = useState(false); const [focusedIndex, setFocusedIndex] = useState(-1); const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0, width: 0 }); + const [searchQuery, setSearchQuery] = useState(''); const containerRef = useRef(null); const controlRef = useRef(null); const menuRef = useRef(null); const optionsRef = useRef([]); + const searchInputRef = useRef(null); const closeDropdown = useCallback(() => { setIsClosing(true); @@ -25,6 +27,7 @@ const Dropdown = memo((props) => { setIsOpen(false); setIsClosing(false); setFocusedIndex(-1); + setSearchQuery(''); }, 200); // Match animation duration }, []); @@ -77,6 +80,37 @@ const Dropdown = memo((props) => { setIsOpen(true); }, [calculatePosition]); + useEffect(() => { + if (isOpen && props.searchable && searchInputRef.current) { + // Focus the search input when dropdown opens + setTimeout(() => searchInputRef.current?.focus(), 0); + } + }, [isOpen, props.searchable]); + + const handleSearchChange = useCallback((e) => { + setSearchQuery(e.target.value); + if (!isOpen) { + openDropdown(); + } + }, [isOpen, openDropdown]); + + const handleInputClick = useCallback((e) => { + e.stopPropagation(); + if (!isOpen) { + openDropdown(); + } + }, [isOpen, openDropdown]); + + const handleInputFocus = useCallback(() => { + // When focusing, if not default value, pre-fill with current value for editing + const defaultValue = props.default || props.items[0]?.value; + if (value !== defaultValue && !searchQuery) { + const currentItem = props.items.find((item) => item?.value === value); + const currentText = currentItem?.text || value; + setSearchQuery(currentText); + } + }, [value, props.default, props.items, searchQuery]); + const onChange = useCallback( (newValue) => { if (newValue === variables.getMessage('modals.main.loading')) { @@ -163,11 +197,31 @@ const Dropdown = memo((props) => { toast(variables.getMessage('toasts.reset')); }, [onChange, props.default, props.items]); + const clearSearch = useCallback((e) => { + e.stopPropagation(); + setSearchQuery(''); + if (props.searchable) { + // Reset to default value (first item, usually "Automatic") + const defaultValue = props.default || props.items[0]?.value; + onChange(defaultValue); + } + if (searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [props, onChange]); + const id = 'dropdown' + props.name; const label = props.label || ''; const selectedItem = props.items.find((item) => item?.value === value); const defaultValue = props.default || props.items[0]?.value; + // Filter items based on search query + const filteredItems = props.searchable && searchQuery + ? props.items.filter((item) => + item !== null && item.text.toLowerCase().includes(searchQuery.toLowerCase()), + ) + : props.items; + return (