From 3dd99491a4bf2fad1fa3dfc48740e71eef2378b8 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Sun, 10 May 2026 09:57:40 +0300 Subject: [PATCH] =?UTF-8?q?UI=20-=20=D0=9C=D0=95=D0=96=D0=9F=D0=A0=D0=9E?= =?UTF-8?q?=D0=95=D0=9A=D0=A2=D0=9D=D0=90=D0=AF=20=D0=9A=D0=9E=D0=9C=D0=9C?= =?UTF-8?q?=D0=A3=D0=9D=D0=98=D0=9A=D0=90=D0=A6=D0=98=D0=AF:=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D1=8F=20=D0=BF=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= =?UTF-8?q?=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/ui/src/modals/modal-core.tsx | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) 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; }