All files / src/components Popup.tsx

51.61% Statements 32/62
57.14% Branches 4/7
40% Functions 2/5
51.61% Lines 32/62

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1001x               1x                               1x 1x 1x 1x 1x 1x 1x 1x 1x   1x   1x                     1x         1x 1x           1x       1x 1x 1x 1x     1x 1x 1x 1x                           1x   1x       1x 1x 1x     1x 1x  
import {createContext, ReactNode, useContext, useEffect, useRef, useState} from "react";
 
// Create a context to manage popup state globally
interface PopupContextType {
    showPopup: (x: number, y: number, content: React.ReactNode) => void;
    hidePopup: () => void;
}
 
const PopupContext = createContext<PopupContextType | null>(null);
 
export interface PopupProviderProps {
    children: ReactNode;
}
 
interface PopupState {
    isVisible: boolean;
    content: ReactNode;
    position: {
        x: number,
        y: number,
    };
}
 
// Provider component to wrap your application
export function PopupProvider(props: PopupProviderProps) {
    const [popupState, setPopupState] = useState<PopupState>({
        isVisible: false,
        content: null,
        position: {
            x: 0,
            y: 0,
        },
    });
 
    const popupRef = useRef<HTMLDivElement>(null);
 
    function showPopup(x: number, y: number, content: ReactNode) {
        setPopupState({
            isVisible: true,
            content,
            position: {
                x,
                y,
            },
        });
    }
 
    function hidePopup() {
        setPopupState(prev => ({...prev, isVisible: false}));
    }
 
    // Close popup when clicking outside
    useEffect(() => {
        const handleClickOutside = (e: MouseEvent) => {
            if (popupRef.current && !popupRef.current.contains(e.target as Node)) {
                hidePopup();
            }
        };
 
        if (popupState.isVisible) {
            document.addEventListener("mousedown", handleClickOutside);
        }
 
        return () => {
            document.removeEventListener("mousedown", handleClickOutside);
        };
    }, [popupState.isVisible]);
 
    // noinspection JSUnusedGlobalSymbols
    return (
        <PopupContext.Provider value={{showPopup, hidePopup}}>
            {props.children}
            {popupState.isVisible && (
                <div
                    ref={popupRef}
                    className="fixed border bg-accent border-gray-400 rounded shadow-md p-4 z-50 card"
                    style={{
                        left: `${popupState.position.x}px`,
                        top: `${popupState.position.y}px`,
                        transform: "translate(-50%, -100%)",
                        marginTop: "-10px"
                    }}
                >
                    {popupState.content}
                </div>
            )}
        </PopupContext.Provider>
    );
}
 
// Custom hook to use the popup functionality
// eslint-disable-next-line react-refresh/only-export-components
export function usePopup() {
    const context = useContext(PopupContext);
    if (!context) {
        throw new Error("usePopup must be used within a PopupProvider");
    }
    return context;
}