import { useCallback, useEffect, useRef, useState, type ReactNode, } from "react"; import { FloatingPortal } from "../inputs/floating-portal"; export function Popover({ align = "left", content, dismissable = true, isOpen, onOpenChange, trigger, }: { align?: "center" | "left" | "right"; content: ReactNode; dismissable?: boolean; isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void; trigger: ReactNode; }) { const panelRef = useRef(null); const floatingRef = useRef(null); const triggerRef = useRef(null); const [internalOpen, setInternalOpen] = useState(false); const resolvedOpen = isOpen ?? internalOpen; const setOpen = useCallback( (nextOpen: boolean) => { if (isOpen === undefined) { setInternalOpen(nextOpen); } onOpenChange?.(nextOpen); }, [isOpen, onOpenChange], ); useEffect(() => { function handlePointerDown(event: PointerEvent) { const target = event.target as Node; if ( dismissable && !panelRef.current?.contains(target) && !floatingRef.current?.contains(target) ) { setOpen(false); } } function handleEscape(event: KeyboardEvent) { if (event.key === "Escape") { setOpen(false); } } window.addEventListener("pointerdown", handlePointerDown); window.addEventListener("keydown", handleEscape); return () => { window.removeEventListener("pointerdown", handlePointerDown); window.removeEventListener("keydown", handleEscape); }; }, [dismissable, setOpen]); return (
setOpen(!resolvedOpen)} ref={triggerRef} > {trigger}
{content}
); }