From 167c8eee31ada31621f43ed48261b72731e29d33 Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Wed, 27 May 2026 23:22:44 +0800 Subject: [PATCH] fix: render plugin preview modal via portal to escape stacking context The plugin preview details modal was rendering under sticky workspace headers (chat-header and ws-tabs-shell) when opened from the chat composer's @ plugin picker. Root cause: The modal was rendered from inside the chat composer subtree, which has its own stacking context. Fixed-position modal descendants cannot reliably escape above sibling sticky headers. Solution: Use React Portal to render the modal directly to document.body, ensuring it renders above all other page content regardless of parent stacking context. Fixes #3064 --- apps/web/src/components/PreviewModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/PreviewModal.tsx b/apps/web/src/components/PreviewModal.tsx index 08ececc0d..904f8a535 100644 --- a/apps/web/src/components/PreviewModal.tsx +++ b/apps/web/src/components/PreviewModal.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; +import { createPortal } from 'react-dom'; import { useT } from '../i18n'; import { copyToClipboard } from '../lib/copy-to-clipboard'; import { exportAsHtml, exportAsPdf, exportAsZip, openSandboxedPreviewInNewTab } from '../runtime/exports'; @@ -456,7 +457,7 @@ export function PreviewModal({ const showTemplateShareMenu = !isCustomView || Boolean(shareTarget?.url); const canOpenTemplateShareMenu = canExportFiles || Boolean(previewShareUrl); - return ( + const modalContent = (