import React, { useState, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "../../hooks/useTranslation"; export interface SelectOption { value: string | number; label: string; } interface SelectProps { value: string | number; onChange: (value: string) => void; options: SelectOption[]; className?: string; buttonClassName?: string; isLoading?: boolean; disabled?: boolean; loadingText?: string; showChevron?: boolean; portalOwnerId?: string; } export const Select: React.FC = ({ value, onChange, options, className = "", buttonClassName = "", isLoading = false, disabled = false, loadingText = "", showChevron = true, portalOwnerId, }) => { const [isOpen, setIsOpen] = useState(false); const [dropdownStyle, setDropdownStyle] = useState({}); const buttonRef = useRef(null); const dropdownRef = useRef(null); const { t } = useTranslation() loadingText = loadingText || t.loadingText // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( buttonRef.current && !buttonRef.current.contains(event.target as Node) && dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isOpen]); // Calculate placement (Auto-placement + Fixed position for Portals) useEffect(() => { if (isOpen && buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const dropdownHeight = 240; // Estimated max height let isUpwards = false; if (spaceBelow < dropdownHeight && spaceAbove > spaceBelow) { isUpwards = true; } setDropdownStyle({ position: "fixed", top: isUpwards ? `${rect.top - 4}px` : `${rect.bottom + 4}px`, left: `${rect.left}px`, width: `${rect.width}px`, transform: isUpwards ? "translateY(-100%)" : "none", zIndex: 99999, // Ensure it's above all modals }); } }, [isOpen]); // Close on window resize or scroll to avoid floating detachment useEffect(() => { const handleScrollOrResize = () => setIsOpen(false); if (isOpen) { window.addEventListener("resize", handleScrollOrResize); window.addEventListener("scroll", handleScrollOrResize, true); } return () => { window.removeEventListener("resize", handleScrollOrResize); window.removeEventListener("scroll", handleScrollOrResize, true); }; }, [isOpen]); const selectedOption = options.find((o) => o.value === value) || options[0]; const isDisabled = disabled || isLoading; return (
{isOpen && !isDisabled && createPortal(
{options.map((option) => (
{ onChange(String(option.value)); setIsOpen(false); }} className={`px-3 py-2 text-sm cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors ${ value === option.value ? "bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 font-medium" : "text-slate-700 dark:text-slate-300" }`} > {option.label}
))}
, document.body )}
); };