diff options
Diffstat (limited to 'client/src/components/CustomSelect')
-rw-r--r-- | client/src/components/CustomSelect/CustomSelect.jsx | 111 | ||||
-rw-r--r-- | client/src/components/CustomSelect/CustomSelect.module.css | 49 |
2 files changed, 141 insertions, 19 deletions
diff --git a/client/src/components/CustomSelect/CustomSelect.jsx b/client/src/components/CustomSelect/CustomSelect.jsx index 3155700..4a906bd 100644 --- a/client/src/components/CustomSelect/CustomSelect.jsx +++ b/client/src/components/CustomSelect/CustomSelect.jsx @@ -1,35 +1,110 @@ import React, { useEffect, useState, useRef } from "react"; +import { SUPPORTED_LANGUAGES } from "../../utils/constants"; + import styles from "./CustomSelect.module.css"; -const CustomSelect = ({ options, onSelect }) => { +const CustomSelect = ({ onSelect, textAreaRef, isModalOpen }) => { const [isOpen, setIsOpen] = useState(false); + const [options, setOptions] = useState(SUPPORTED_LANGUAGES); const [selectedOption, setSelectedOption] = useState( options.length > 0 ? options[0] : null, ); + const [focusedOptionIndex, setFocusedOptionIndex] = useState(0); const selectRef = useRef(null); + const searchRef = useRef(null); + const optionsRef = useRef([]); const toggleDropdown = () => { setIsOpen(!isOpen); }; - const handleOptionClick = (option) => { + const handleOptionClick = (option, index) => { setSelectedOption(option); onSelect(option.value); + setFocusedOptionIndex(index); setIsOpen(false); + setOptions(SUPPORTED_LANGUAGES); + if (textAreaRef.current) { + textAreaRef.current.focus(); + } }; const handleClickOutside = (event) => { if (selectRef.current && !selectRef.current.contains(event.target)) { setIsOpen(false); + setOptions(SUPPORTED_LANGUAGES); + + if (textAreaRef.current) { + textAreaRef.current.focus(); + } + } + }; + + const handleChange = (e) => { + const searchVal = e.target.value; + const filteredOptions = + searchVal.length > 0 + ? SUPPORTED_LANGUAGES.filter((option) => + option.label.toLowerCase().includes(searchVal.toLowerCase()), + ) + : SUPPORTED_LANGUAGES; + + setOptions(filteredOptions); + setFocusedOptionIndex(0); + }; + + const handleKeyDown = (event) => { + if (isOpen) { + switch (event.key) { + case "ArrowDown": + setFocusedOptionIndex((prevIndex) => { + const newIndex = + prevIndex < options.length - 1 ? prevIndex + 1 : prevIndex; + optionsRef.current[newIndex].scrollIntoView({ block: "nearest" }); + return newIndex; + }); + break; + case "ArrowUp": + setFocusedOptionIndex((prevIndex) => { + const newIndex = prevIndex > 0 ? prevIndex - 1 : prevIndex; + optionsRef.current[newIndex].scrollIntoView({ block: "nearest" }); + return newIndex; + }); + break; + case "Enter": + event.preventDefault(); + handleOptionClick(options[focusedOptionIndex], focusedOptionIndex); + break; + case "Escape": + setIsOpen(false); + setOptions(SUPPORTED_LANGUAGES); + if (textAreaRef.current) { + textAreaRef.current.focus(); + } + break; + default: + break; + } + } else if (!isModalOpen && event.ctrlKey && event.key === "l") { + event.preventDefault(); + setIsOpen(true); } }; useEffect(() => { document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleKeyDown); }; - }, []); + }, [isOpen, options, focusedOptionIndex]); + + useEffect(() => { + if (isOpen && searchRef.current) { + searchRef.current.focus(); + } + }, [isOpen]); return ( <div className={styles.select} ref={selectRef}> @@ -45,16 +120,26 @@ const CustomSelect = ({ options, onSelect }) => { )} </div> {isOpen && ( - <div className={styles.options}> - {options.map((option) => ( - <div - key={option.value} - className={styles.option} - onClick={() => handleOptionClick(option)} - > - {option.label} - </div> - ))} + <div className={styles.options__container}> + <input + onChange={handleChange} + className={styles.options__search} + ref={searchRef} + placeholder="Search..." + /> + <div className={styles.options}> + {options.map((option, index) => ( + <div + key={option.value} + className={`${styles.option} ${index === focusedOptionIndex ? styles.focused : ""}`} + onClick={() => handleOptionClick(option, index)} + tabIndex={0} + ref={(el) => (optionsRef.current[index] = el)} + > + {option.label} + </div> + ))} + </div> </div> )} </div> diff --git a/client/src/components/CustomSelect/CustomSelect.module.css b/client/src/components/CustomSelect/CustomSelect.module.css index 4b5f1a4..f62a77f 100644 --- a/client/src/components/CustomSelect/CustomSelect.module.css +++ b/client/src/components/CustomSelect/CustomSelect.module.css @@ -10,39 +10,76 @@ .selected__option { cursor: pointer; + font-weight: 500; padding: 10px; border: none; display: flex; justify-content: space-between; } -.options { +.options__container { position: absolute; + display: flex; + flex-direction: column; + padding: 0.5rem 0; top: 140%; left: 0; - height: 400px; - overflow-y: auto; width: 100%; + height: 400px; border: none; border-radius: 10px; background-color: var(--color-yellow); z-index: 2; + align-items: center; +} + +.options__search { + margin-top: 0.5rem; + padding: 6px; + width: 85%; + border: none; + border-radius: 6px; + background-color: #d79921; + color: var(--color-dark); + &:focus { + outline: none; + } + &::placeholder { + color: #504945; + opacity: 1; + } + + &::-ms-input-placeholder { + color: #504945; + } +} + +.options { + width: 100%; + height: 400px; + overflow-y: auto; + margin-top: 10px; } .option { padding: 10px; color: var(--color-dark); - border-bottom: var(--color-dark) 1px solid; + border-bottom: 1px solid var(--color-dark); cursor: pointer; - transition: all 0.3s ease; + transition: transform 0.3s ease; &:hover { - scale: 0.9; + transform: scale(0.9); } &:last-child { border-bottom: none; } } +.option.focused { + background-color: #d79921; + outline: none; +} + @media screen and (max-width: 480px) { .select { width: 8rem; |