Files
Dwinzo_Demo/app/src/components/ui/inputs/RegularDropDown.tsx

153 lines
4.0 KiB
TypeScript

import React, { useState, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
interface DropdownProps {
header: string;
options: string[];
onSelect: (option: string) => void;
search?: boolean;
onClick?: () => void;
onChange?: () => void;
}
const RegularDropDown: React.FC<DropdownProps> = ({
header,
options,
onSelect,
search = true,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<string | null>(null);
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 when closed
useEffect(() => {
if (!isOpen) {
setSelectedOption(null);
setSearchTerm("");
setFilteredOptions(options);
}
}, [isOpen, options]);
// Reset when header changes
useEffect(() => {
setSelectedOption(null);
setSearchTerm("");
setFilteredOptions(options);
}, [header, options]);
// Close if clicked outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("click", handleClickOutside);
return () => document.removeEventListener("click", handleClickOutside);
}, []);
// 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);
const handleOptionClick = (option: string) => {
setSelectedOption(option);
onSelect(option);
setIsOpen(false);
};
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const term = event.target.value;
setSearchTerm(term);
setFilteredOptions(
options.filter((option) =>
option.toLowerCase().includes(term.toLowerCase())
)
);
};
return (
<div
className="regularDropdown-container"
ref={dropdownRef}
onPointerLeave={() => {
setIsOpen(false);
}}
>
{/* Header */}
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
<div className="key">{selectedOption || header}</div>
<div className="icon"></div>
</div>
{/* 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>
)}
{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>
);
};
export default RegularDropDown;