63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface DropdownProps {
|
|
trigger: React.ReactNode;
|
|
children: React.ReactNode;
|
|
align?: 'left' | 'right';
|
|
}
|
|
|
|
export const Dropdown: React.FC<DropdownProps> = ({ trigger, children, align = 'left' }) => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative" ref={dropdownRef}>
|
|
<div onClick={() => setIsOpen(!isOpen)}>
|
|
{trigger}
|
|
</div>
|
|
{isOpen && (
|
|
<div
|
|
className={cn(
|
|
'absolute top-full mt-1 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50',
|
|
align === 'right' ? 'right-0' : 'left-0'
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface DropdownItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
icon?: React.ReactNode;
|
|
}
|
|
|
|
export const DropdownItem = React.forwardRef<HTMLDivElement, DropdownItemProps>(
|
|
({ className, icon, children, ...props }, ref) => (
|
|
<div
|
|
ref={ref}
|
|
className={cn(
|
|
'flex items-center px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
{icon && <span className="mr-2">{icon}</span>}
|
|
{children}
|
|
</div>
|
|
)
|
|
);
|
|
DropdownItem.displayName = 'DropdownItem'; |