UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: исправление слоя превью вложений

This commit is contained in:
DCCONSTRUCTIONS 2026-05-10 09:57:40 +03:00
parent 0be8f01283
commit 3dd99491a4
1 changed files with 64 additions and 2 deletions

View File

@ -6,11 +6,27 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { createPortal } from "react-dom";
// constants // constants
import { cn } from "../utils"; import { cn } from "../utils";
import { EModalPosition, EModalWidth } from "./constants"; import { EModalPosition, EModalWidth } from "./constants";
// helpers // 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 = { type Props = {
children: React.ReactNode; children: React.ReactNode;
handleClose?: () => void; handleClose?: () => void;
@ -29,9 +45,41 @@ export function ModalCore(props: Props) {
className = "", className = "",
} = props; } = props;
return ( const [portalRoot, setPortalRoot] = React.useState<HTMLElement | null>(null);
const skipNextCloseRef = React.useRef(false);
const skipNextCloseTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | 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 = (
<Transition.Root show={isOpen} as={Fragment}> <Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-30" onClose={() => handleClose && handleClose()}> <Dialog as="div" className="relative z-[180]" data-prevent-outside-click onClose={handleDialogClose}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -56,6 +104,16 @@ export function ModalCore(props: Props) {
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel <Dialog.Panel
onContextMenuCapture={(event) => {
event.stopPropagation();
guardContextMenuClose();
}}
onPointerDownCapture={(event) => {
if (event.button === 2) {
event.stopPropagation();
guardContextMenuClose();
}
}}
className={cn( className={cn(
"nodedc-glass-modal relative w-full transform rounded-[28px] text-left transition-all", "nodedc-glass-modal relative w-full transform rounded-[28px] text-left transition-all",
width, width,
@ -70,4 +128,8 @@ export function ModalCore(props: Props) {
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); );
if (typeof document !== "undefined" && !portalRoot) return null;
return portalRoot ? createPortal(modal, portalRoot) : modal;
} }