58 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
} |