import { useEffect, useLayoutEffect, useState, type CSSProperties, type ReactNode, type RefObject } from 'react'; import { createPortal } from 'react-dom'; import { copyThemeVars } from './modal-portal'; /** * A small popover (e.g. the reaction picker) rendered into document.body so it is never clipped by * a scroll container's `overflow: hidden` — the reported "emoji picker goes beneath the container" * bug. Positioned fixed just below the anchor, right-aligned to it, and re-placed on scroll/resize. * Closes on outside pointer-down, scroll of a different element, or Escape. Carries the SDK theme * tokens (copied from the live surface) since a body-portaled node is outside the themed subtree. */ export function PopoverPortal({ anchorRef, onClose, children, }: { anchorRef: RefObject; onClose: () => void; children: ReactNode; }) { const [pos, setPos] = useState<{ top: number; left: number } | null>(null); const [vars] = useState(copyThemeVars); useLayoutEffect(() => { const el = anchorRef.current; if (!el) return; const place = (): void => { const r = el.getBoundingClientRect(); setPos({ top: r.bottom + 4, left: r.right }); }; place(); window.addEventListener('resize', place); window.addEventListener('scroll', place, true); return () => { window.removeEventListener('resize', place); window.removeEventListener('scroll', place, true); }; }, [anchorRef]); useEffect(() => { const onDown = (e: PointerEvent): void => { const target = e.target as Node; if (anchorRef.current?.contains(target)) return; if ((target as Element).closest?.('.miu-popover')) return; onClose(); }; const onKey = (e: KeyboardEvent): void => { if (e.key === 'Escape') onClose(); }; document.addEventListener('pointerdown', onDown, true); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('pointerdown', onDown, true); document.removeEventListener('keydown', onKey); }; }, [anchorRef, onClose]); if (typeof document === 'undefined' || !pos) return null; return createPortal(
{children}
, document.body, ); }