EmberClone/apps/web/src/components/DropdownMenu.tsx

71 lines
2.1 KiB
TypeScript

import React, { useState, useEffect, useRef, ReactNode } from 'react';
interface DropdownItem {
label: string;
icon?: ReactNode;
onClick: () => void;
danger?: boolean;
}
interface DropdownMenuProps {
trigger: ReactNode;
items: DropdownItem[];
align?: 'left' | 'right';
}
export default function DropdownMenu({ trigger, items, align = 'left' }: DropdownMenuProps) {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen) return;
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [isOpen]);
const alignmentClass = align === 'right' ? 'right-0' : 'left-0';
return (
<div ref={containerRef} className="relative inline-block text-left">
<div
onClick={() => setIsOpen(!isOpen)}
className="cursor-pointer"
>
{trigger}
</div>
{isOpen && (
<div
className={`absolute z-50 mt-2 w-48 origin-top-left bg-white border border-gray-200 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${alignmentClass}`}
>
<div className="py-1">
{items.map((item, index) => (
<button
key={index}
onClick={() => {
item.onClick();
setIsOpen(false);
}}
className={`flex items-center w-full px-4 py-2 text-sm transition-colors duration-150 ${
item.danger
? 'text-red-600 hover:bg-red-50'
: 'text-gray-700 hover:bg-gray-100'
}`}
>
{item.icon && <span className="mr-2">{item.icon}</span>}
{item.label}
</button>
))}
</div>
</div>
)}
</div>
);
}