import React, { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import type { ReactElement } from 'react'; import { MoreVertical, Eye, Edit, Trash2, Users, BarChart3 } from 'lucide-react'; import { cn } from '@/lib/utils'; export interface ActionItem { label: string; onClick: () => void | Promise; icon?: React.ReactNode; variant?: 'danger' | 'default'; } interface ActionDropdownProps { onView?: () => void; onEdit?: () => void; onDelete?: () => void; onContacts?: () => void; onScorecards?: () => void; actions?: ActionItem[]; trigger?: React.ReactNode; className?: string; } export const ActionDropdown = ({ onView, onEdit, onDelete, onContacts, onScorecards, actions, trigger, className, }: ActionDropdownProps): ReactElement => { const [isOpen, setIsOpen] = useState(false); const [dropdownStyle, setDropdownStyle] = useState<{ top?: string; bottom?: string; right: string; width: string }>({ right: '0', width: '0' }); const dropdownRef = useRef(null); const buttonRef = useRef(null); const dropdownMenuRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) && dropdownMenuRef.current && !dropdownMenuRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; const handleScroll = (event: Event) => { // Don't close if scrolling inside the dropdown menu itself const target = event.target as HTMLElement; if (dropdownMenuRef.current && (dropdownMenuRef.current === target || dropdownMenuRef.current.contains(target))) { return; } setIsOpen(false); }; if (isOpen && buttonRef.current) { document.addEventListener('mousedown', handleClickOutside); window.addEventListener('scroll', handleScroll, true); // Calculate position when dropdown opens const rect = buttonRef.current.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const dropdownHeight = actions ? actions.length * 32 + 16 : 120; // Approximate height based on actions // Determine if should open upward or downward const shouldOpenUp = spaceBelow < dropdownHeight && spaceAbove > spaceBelow; // Calculate dropdown position const right = window.innerWidth - rect.right; const width = actions ? 140 : 76; // Wider for custom actions if (shouldOpenUp) { // Position above the button const bottom = window.innerHeight - rect.top; setDropdownStyle({ bottom: `${bottom}px`, right: `${right}px`, width: `${width}px`, }); } else { // Position below the button const top = rect.bottom; setDropdownStyle({ top: `${top}px`, right: `${right}px`, width: `${width}px`, }); } } return () => { document.removeEventListener('mousedown', handleClickOutside); window.removeEventListener('scroll', handleScroll, true); }; }, [isOpen, actions]); const handleAction = (action?: () => void | Promise) => { if (action) { void Promise.resolve(action()).catch(console.error); } setIsOpen(false); }; return (
{trigger ? ( React.cloneElement(trigger as React.ReactElement, { ref: buttonRef, onClick: (e: React.MouseEvent) => { e.stopPropagation(); setIsOpen(!isOpen); const triggerProps = (trigger as React.ReactElement).props; if (triggerProps.onClick) triggerProps.onClick(e); }, }) ) : ( )} {isOpen && buttonRef.current && createPortal(
{actions ? ( actions.map((action, index) => ( )) ) : ( <> {onView && ( )} {onEdit && ( )} {onDelete && ( )} {onContacts && ( )} {onScorecards && ( )} )}
, document.body )}
); };