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

58 lines
2.1 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import type { ReactNode } from 'react';
interface PopoverProps {
trigger: ReactNode;
content: ReactNode;
position?: 'top' | 'bottom' | 'left' | 'right';
}
export default function Popover({ trigger, content, position = 'bottom' }: PopoverProps) {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [isOpen]);
const positionClasses = {
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
};
const arrowClasses = {
top: 'bottom-[-4px] left-1/2 -translate-x-1/2 border-t-gray-200 border-x-transparent border-b-transparent',
bottom: 'top-[-4px] left-1/2 -translate-x-1/2 border-b-gray-200 border-x-transparent border-t-transparent',
left: 'right-[-4px] top-1/2 -translate-y-1/2 border-l-gray-200 border-y-transparent border-r-transparent',
right: 'left-[-4px] top-1/2 -translate-y-1/2 border-r-gray-200 border-y-transparent border-l-transparent',
};
return (
<div ref={containerRef} className="relative inline-block">
<div onClick={() => setIsOpen(!isOpen)} className="cursor-pointer">
{trigger}
</div>
{isOpen && (
<div className={`absolute z-50 w-max max-w-xs bg-white border border-gray-200 rounded-lg shadow-lg p-3 ${positionClasses[position]}`}>
{/* Arrow */}
<div className={`absolute w-2 h-2 border-4 ${arrowClasses[position]}`} />
<div className="relative z-10">
{content}
</div>
</div>
)}
</div>
);
}