import { useState, useRef, useEffect, createPortal, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; type PopoverProps = { trigger: React.ReactNode; children: React.ReactNode; className?: string; position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; triggerAs?: 'button' | 'div' | 'span'; showClose?: boolean; onClose?: () => void; }; const Popover = ( { trigger, children, className = '', position = 'bottom-right', triggerAs = 'button', showClose = false, onClose, }: PopoverProps ) => { const [ isOpen, setIsOpen ] = useState( false ); const [ coords, setCoords ] = useState( {} ); const containerRef = useRef( null ); const popoverRef = useRef( null ); const updatePosition = useCallback( () => { if ( ! containerRef.current ) { return; } const rect = containerRef.current.getBoundingClientRect(); const style: React.CSSProperties = { position: 'fixed', zIndex: 9999, }; // Add a small gap const gap = 8; switch ( position ) { case 'bottom-right': style.top = rect.bottom + gap; style.right = window.innerWidth - rect.right; break; case 'bottom-left': style.top = rect.bottom + gap; style.left = rect.left; break; case 'top-right': style.bottom = window.innerHeight - rect.top + gap; style.right = window.innerWidth - rect.right; break; case 'top-left': style.bottom = window.innerHeight - rect.top + gap; style.left = rect.left; break; } setCoords( style ); }, [ position ] ); useEffect( () => { const handleClickOutside = ( event: MouseEvent ) => { const target = event.target as Node; if ( containerRef.current && ! containerRef.current.contains( target ) && popoverRef.current && ! popoverRef.current.contains( target ) ) { setIsOpen( false ); } }; const handleScroll = () => { if ( isOpen ) { updatePosition(); } }; const handleResize = () => { if ( isOpen ) { updatePosition(); } }; if ( isOpen ) { updatePosition(); document.addEventListener( 'mousedown', handleClickOutside ); window.addEventListener( 'scroll', handleScroll, true ); window.addEventListener( 'resize', handleResize ); } return () => { document.removeEventListener( 'mousedown', handleClickOutside ); window.removeEventListener( 'scroll', handleScroll, true ); window.removeEventListener( 'resize', handleResize ); }; }, [ isOpen, position, updatePosition ] ); const toggle = () => setIsOpen( ( prev ) => ! prev ); const close = () => { setIsOpen( false ); if ( onClose ) { onClose(); } }; return (
{ triggerAs === 'button' ? ( ) : (
{ if ( event.key === 'Enter' || event.key === ' ' ) { event.preventDefault(); toggle(); } } } className="cursor-pointer inline-flex" > { trigger }
) } { isOpen && createPortal(
{ showClose && ( ) }
{ children }
, document.body, ) }
); }; export default Popover;