import React, { createContext, useContext, useState, useRef, useLayoutEffect, useCallback } from 'react'; import { useFade } from '../../core/hooks/useAnimation'; interface PopperContextType { isOpen: boolean; setIsOpen: React.Dispatch>; triggerRef: React.MutableRefObject; popperRef: React.RefObject; setPosition: () => void; } const PopperContext = createContext(null); export const usePopperContext = () => { const context = useContext(PopperContext); if (!context) throw new Error('Popper components must be used within a Popper provider.'); return context; }; export const Popper: React.FC<{ children: React.ReactNode; isOpen?: boolean; setIsOpen?: React.Dispatch>; }> = ({ children, ...props }) => { const [internalIsOpen, setInternalIsOpen] = useState(false); const triggerRef = useRef(null); const popperRef = useRef(null); const isOpen = props.isOpen ?? internalIsOpen; const setIsOpen = props.setIsOpen ?? setInternalIsOpen; const setPosition = useCallback(() => { if (triggerRef.current && popperRef.current) { const triggerRect = triggerRef.current.getBoundingClientRect(); popperRef.current.style.position = 'fixed'; popperRef.current.style.top = `${triggerRect.bottom + 4}px`; popperRef.current.style.left = `${triggerRect.left}px`; } }, []); useLayoutEffect(() => { if (isOpen) { setPosition(); window.addEventListener('scroll', setPosition, true); window.addEventListener('resize', setPosition); } return () => { window.removeEventListener('scroll', setPosition, true); window.removeEventListener('resize', setPosition); }; }, [isOpen, setPosition]); const contextValue = { isOpen, setIsOpen, triggerRef, popperRef, setPosition }; return {children}; }; export const PopperTrigger: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { setIsOpen, triggerRef, setPosition } = usePopperContext(); const child = React.Children.only(children) as React.ReactElement; const ref = (node: HTMLElement | null) => { triggerRef.current = node; const childRef = (child as any).ref; if (typeof childRef === 'function') { childRef(node); } else if (childRef) { (childRef as React.MutableRefObject).current = node; } }; return React.cloneElement(child, { ref, onClick: (e: React.MouseEvent) => { setIsOpen(prev => !prev); setPosition(); child.props.onClick?.(e); }, }); }; export const PopperContent: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => { const { isOpen, popperRef } = usePopperContext(); const { isRendered, style: fadeStyle } = useFade(isOpen, 200); if (!isRendered) return null; return (
{children}
); };