refactor: optimized contextmenu, style update, ui bug fixes
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
@@ -14,32 +15,35 @@ const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [position, setPosition] = useState<{ top: number; left: number; width: number }>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
// Reset when closed
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
setSearchTerm("");
|
||||
setFilteredOptions(options);
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
// Reset when header changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
setSearchTerm("");
|
||||
setFilteredOptions(options);
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
// Close if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -49,77 +53,88 @@ const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
return () => document.removeEventListener("click", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
// Recalculate position when opening
|
||||
useEffect(() => {
|
||||
if (isOpen && dropdownRef.current) {
|
||||
const rect = dropdownRef.current.getBoundingClientRect();
|
||||
setPosition({
|
||||
top: rect.bottom + window.scrollY,
|
||||
left: rect.left + window.scrollX,
|
||||
width: rect.width,
|
||||
});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
setFilteredOptions(
|
||||
options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
)
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
{/* Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
{/* Options rendered in portal */}
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<div
|
||||
className="dropdown-options"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: position.top,
|
||||
left: position.left,
|
||||
width: position.width,
|
||||
zIndex: 9999,
|
||||
}}
|
||||
>
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
title={option}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user