diff --git a/src/app/components/PairSelector.tsx b/src/app/components/PairSelector.tsx index 94c4ddc4..ac66981c 100644 --- a/src/app/components/PairSelector.tsx +++ b/src/app/components/PairSelector.tsx @@ -1,7 +1,8 @@ import { useAppSelector, useAppDispatch } from "../hooks"; import { selectPairAddress } from "../state/pairSelectorSlice"; import { orderInputSlice } from "../state/orderInputSlice"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { FaSearch } from "react-icons/fa"; interface PairInfo { name: string; @@ -14,8 +15,22 @@ function displayName(name?: string) { export function PairSelector() { const pairSelector = useAppSelector((state) => state.pairSelector); const dispatch = useAppDispatch(); + const [query, setQuery] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const [highlightedIndex, setHighlightedIndex] = useState(0); + const [filteredOptions, setFilteredOptions] = useState([]); + + const inputRef = useRef(null); - const options = pairSelector.pairsList; + // uncomment duplicates to test scroll behavior + const options = useMemo( + () => [ + // ...pairSelector.pairsList, + // ...pairSelector.pairsList, + ...pairSelector.pairsList, + ], + [pairSelector.pairsList] + ); const id = "pairOption"; useEffect(() => { @@ -26,110 +41,141 @@ export function PairSelector() { } }, [dispatch]); - const handleChange = (val: PairInfo | null) => { - if (val == null) return; - - dispatch(orderInputSlice.actions.resetNumbersInput()); - - const pairAddress = val["address"]; - dispatch(selectPairAddress(pairAddress)); - }; - - const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); - - const inputRef = useRef(null); - const dropdownRef = useRef(null); - - const selectOption = (option: PairInfo) => { + const selectOption = useCallback(() => { + const option = filteredOptions[highlightedIndex]; setQuery(() => ""); - handleChange(option); + dispatch(orderInputSlice.actions.resetNumbersInput()); + dispatch(selectPairAddress(option["address"])); setIsOpen((isOpen) => !isOpen); - }; - - function toggle(e: React.MouseEvent) { - setIsOpen(e && e.target === inputRef.current); - } + }, [dispatch, highlightedIndex, filteredOptions]); const getDisplayValue = () => { - if (query) return displayName(query); + if (isOpen) return displayName(query); if (pairSelector.name) return displayName(pairSelector.name); return ""; }; - const filter = (options: PairInfo[]) => { - return options.filter( + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "/") { + e.preventDefault(); + setIsOpen(true); + } else if (e.key === "Escape") { + setQuery(""); + setIsOpen(false); + } else if (e.key === "Enter" && isOpen) { + e.preventDefault(); + selectOption(); + } else if (e.key === "ArrowDown" && isOpen) { + e.preventDefault(); + setHighlightedIndex((highlightedIndex) => + highlightedIndex < filteredOptions.length - 1 + ? highlightedIndex + 1 + : 0 + ); + } else if (e.key === "ArrowUp" && isOpen) { + e.preventDefault(); + setHighlightedIndex((highlightedIndex) => + highlightedIndex > 0 + ? highlightedIndex - 1 + : filteredOptions.length - 1 + ); + } + }, + [isOpen, selectOption, filteredOptions.length] + ); + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown); + + // Remove the event listener when the component unmounts + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [handleKeyDown]); + + useEffect(() => { + if (isOpen) { + inputRef.current?.focus(); + inputRef.current?.select(); + } else { + inputRef.current?.blur(); + } + }, [isOpen]); + + useEffect(() => { + const newOptions = options.filter( (option) => option["name"].toLowerCase().indexOf(query.toLowerCase()) > -1 ); - }; + setFilteredOptions(newOptions); + setHighlightedIndex(0); + }, [options, query]); return ( -
-
- -
    - {filter(options).map((option, index) => { - return ( -
  • selectOption(option)} - className=" font-bold !pl-0" - key={`${id}-${index}`} - > -
    - {displayName(option["name"])} - + -
    -
  • - ); - })} -
  • -
    +
    { + setIsOpen((isOpen) => !isOpen); + }} + > + { + setQuery(e.target.value); + }} + className="flex-initial !bg-transparent uppercase" + /> +
    + + + / + +
    +
    +
      + {filteredOptions.map((option, index) => { + return ( +
    • setHighlightedIndex(index)} + onClick={() => selectOption()} className={ - filter(options).length == 0 - ? "flex justify-between" - : "flex justify-between hidden" + "font-bold !px-4 py-0 cursor-pointer" + + (highlightedIndex === index ? " bg-base-300" : "") } + key={`${id}-${index}`} > - No Results -
    -
  • -
-
+
+ {displayName(option["name"])} + + +
+ + ); + })} +
  • + No Results +
  • +
    ); } diff --git a/src/app/components/PriceChart.tsx b/src/app/components/PriceChart.tsx index 4730e0a7..52805cc1 100644 --- a/src/app/components/PriceChart.tsx +++ b/src/app/components/PriceChart.tsx @@ -172,7 +172,7 @@ function PriceChartCanvas(props: PriceChartProps) {