'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { createPortal } from 'react-dom' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' interface HelpPopoverProps { /** Popover body: the page's help text (i18n `help_*` keys per namespace). */ children: React.ReactNode className?: string } /** * Page help behind a small "?" (UI-migration convention 7): a 17px circular * button right after the H1 opening a popover anchored at the button. No * instructional copy in the page flow. */ export function HelpPopover({ children, className }: HelpPopoverProps) { const tNav = useTranslations('nav') const [open, setOpen] = useState(false) const triggerRef = useRef(null) const panelRef = useRef(null) const [pos, setPos] = useState({ top: 0, left: 0 }) const updatePosition = useCallback(() => { if (!triggerRef.current || !panelRef.current) return const t = triggerRef.current.getBoundingClientRect() const p = panelRef.current.getBoundingClientRect() const margin = 8 const left = Math.max(margin, Math.min(t.left, window.innerWidth - p.width - margin)) const top = Math.min(t.bottom + 6, window.innerHeight - p.height - margin) setPos({ top, left }) }, []) useEffect(() => { if (!open) return const raf = requestAnimationFrame(() => updatePosition()) return () => cancelAnimationFrame(raf) }, [open, updatePosition]) useEffect(() => { if (!open) return function handleClick(e: MouseEvent) { const target = e.target as HTMLElement if (!target.isConnected) return if ( (!triggerRef.current || !triggerRef.current.contains(target)) && (!panelRef.current || !panelRef.current.contains(target)) ) { setOpen(false) } } function handleKey(e: KeyboardEvent) { if (e.key === 'Escape') setOpen(false) } document.addEventListener('mousedown', handleClick) document.addEventListener('keydown', handleKey) return () => { document.removeEventListener('mousedown', handleClick) document.removeEventListener('keydown', handleKey) } }, [open]) return ( <> {open && createPortal(
{children}
, document.body, )} ) }