tcmmichaelb139's picture
fixed some issues + change color style.
f3f5382
import { useState, useRef, useEffect } from "react";
const Dropdown = ({
label,
selectedValue,
onSelect,
options = [],
placeholder = "Select an option...",
disabled = false,
loading = false,
icon = null,
className = "",
dropdownClassName = "",
optionClassName = "",
showSearch = false,
searchPlaceholder = "Search...",
emptyMessage = "No options available",
loadingMessage = "Loading...",
}) => {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const dropdownRef = useRef(null);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const filteredOptions = options.filter((option) =>
option.label
? option.label.toLowerCase().includes(searchTerm.toLowerCase())
: option.toLowerCase().includes(searchTerm.toLowerCase())
);
const displayValue = selectedValue?.label || selectedValue || placeholder;
const isSelected = selectedValue && selectedValue !== placeholder;
return (
<div className={`relative ${className}`} ref={dropdownRef}>
{label && (
<label className="block text-sm font-medium text-foreground mb-2">
{label}
</label>
)}
<button
onClick={() => !disabled && setIsOpen(!isOpen)}
disabled={disabled}
className={`w-full p-4 rounded-xl text-left transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed bg-background border-2 border-secondary-300 hover:bg-primary-50 hover:shadow-lg ${className}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
{icon && <div className="text-lg">{icon}</div>}
<span
className={`${
isSelected ? "text-foreground font-medium" : "text-foreground"
} ${loading ? "text-foreground" : ""}`}
>
{loading ? loadingMessage : displayValue}
</span>
</div>
<div className="flex items-center space-x-2">
{loading && (
<div className="animate-spin w-4 h-4 border-2 border-primary-500 border-t-transparent rounded-full"></div>
)}
<svg
className={`w-5 h-5 text-primary-600 transition-transform duration-200 ${
isOpen ? "rotate-180" : ""
} ${disabled ? "text-foreground" : ""}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</div>
</button>
{isOpen && !disabled && (
<div
className={`absolute z-50 w-full mt-2 rounded-xl max-h-60 overflow-hidden bg-background border-2 border-primary-200 shadow-lg ${dropdownClassName}`}
>
{showSearch && (
<div className="p-3 border-b border-secondary-100">
<div className="relative">
<svg
className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-foreground"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<input
type="text"
placeholder={searchPlaceholder}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-3 py-2 border border-secondary-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent hover:border-primary-300 transition-colors text-sm"
onClick={(e) => e.stopPropagation()}
/>
</div>
</div>
)}
<div className="max-h-48 overflow-y-auto p-2">
{loading ? (
<div className="p-4 text-center text-foreground">
<div className="animate-spin w-6 h-6 border-2 border-primary-500 border-t-transparent rounded-full mx-auto mb-2"></div>
{loadingMessage}
</div>
) : filteredOptions.length === 0 ? (
<div className="p-4 text-center text-foreground">
{emptyMessage}
</div>
) : (
filteredOptions.map((option) => {
const optionValue = option.value || option;
const optionLabel = option.label || option;
const optionIcon = option.icon;
const optionDescription = option.description;
const isOptionSelected =
selectedValue === optionValue ||
(selectedValue?.value && selectedValue.value === optionValue);
return (
<button
key={optionValue}
onClick={() => {
onSelect(option);
setIsOpen(false);
setSearchTerm("");
}}
className={`w-full p-3 text-left rounded-lg transition-all duration-200 hover:bg-blue-100 hover:text-blue-900 ${
isOptionSelected
? "bg-gradient-to-r from-primary-200 to-accent-200 text-primary-800 font-medium"
: "text-foreground hover:bg-blue-50"
} ${optionClassName}`}
>
<div className="flex items-center space-x-3">
{optionIcon && (
<div className="text-lg">{optionIcon}</div>
)}
<div className="flex-1 min-w-0">
<div className="truncate font-medium">
{optionLabel}
</div>
{optionDescription && (
<div className="text-xs text-foreground truncate mt-1">
{optionDescription}
</div>
)}
</div>
</div>
</button>
);
})
)}
</div>
</div>
)}
</div>
);
};
export default Dropdown;