diff --git a/plane-src/packages/ui/src/modals/modal-core.tsx b/plane-src/packages/ui/src/modals/modal-core.tsx index 5a1e53c..0577805 100644 --- a/plane-src/packages/ui/src/modals/modal-core.tsx +++ b/plane-src/packages/ui/src/modals/modal-core.tsx @@ -6,11 +6,27 @@ import { Dialog, Transition } from "@headlessui/react"; import React, { Fragment } from "react"; +import { createPortal } from "react-dom"; // constants import { cn } from "../utils"; import { EModalPosition, EModalWidth } from "./constants"; // helpers +const MODAL_PORTAL_ID = "nodedc-modal-portal"; + +const ensureModalPortalRoot = () => { + if (typeof document === "undefined") return null; + + const existingPortalRoot = document.getElementById(MODAL_PORTAL_ID); + if (existingPortalRoot) return existingPortalRoot; + + const portalRoot = document.createElement("div"); + portalRoot.id = MODAL_PORTAL_ID; + document.body.appendChild(portalRoot); + + return portalRoot; +}; + type Props = { children: React.ReactNode; handleClose?: () => void; @@ -29,9 +45,41 @@ export function ModalCore(props: Props) { className = "", } = props; - return ( + const [portalRoot, setPortalRoot] = React.useState(null); + const skipNextCloseRef = React.useRef(false); + const skipNextCloseTimeoutRef = React.useRef | null>(null); + + React.useEffect(() => { + setPortalRoot(ensureModalPortalRoot()); + + return () => { + if (skipNextCloseTimeoutRef.current) clearTimeout(skipNextCloseTimeoutRef.current); + }; + }, []); + + const guardContextMenuClose = () => { + skipNextCloseRef.current = true; + + if (skipNextCloseTimeoutRef.current) clearTimeout(skipNextCloseTimeoutRef.current); + + skipNextCloseTimeoutRef.current = setTimeout(() => { + skipNextCloseRef.current = false; + skipNextCloseTimeoutRef.current = null; + }, 300); + }; + + const handleDialogClose = () => { + if (skipNextCloseRef.current) { + skipNextCloseRef.current = false; + return; + } + + handleClose?.(); + }; + + const modal = ( - handleClose && handleClose()}> + { + event.stopPropagation(); + guardContextMenuClose(); + }} + onPointerDownCapture={(event) => { + if (event.button === 2) { + event.stopPropagation(); + guardContextMenuClose(); + } + }} className={cn( "nodedc-glass-modal relative w-full transform rounded-[28px] text-left transition-all", width, @@ -70,4 +128,8 @@ export function ModalCore(props: Props) { ); + + if (typeof document !== "undefined" && !portalRoot) return null; + + return portalRoot ? createPortal(modal, portalRoot) : modal; }