import React from "react"; import { createPortal } from "react-dom"; import { motion, AnimatePresence } from "framer-motion"; import { ArrowLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { isWindows, isLinux, DRAG_REGION_ATTR, DRAG_REGION_STYLE, } from "@/lib/platform"; import { isTextEditableTarget } from "@/utils/domUtils"; import { cn } from "@/lib/utils"; interface FullScreenPanelProps { isOpen: boolean; title: string; onClose: () => void; children: React.ReactNode; footer?: React.ReactNode; /** * 覆盖内容区滚动容器的内边距/间距类。默认 `px-6 py-6 space-y-6`。 * 通过 `cn`(twMerge) 合并,传入如 `pt-3` 只覆盖顶部内边距,其余保持默认。 */ contentClassName?: string; } const DRAG_BAR_HEIGHT = isWindows() || isLinux() ? 0 : 28; // px - match App.tsx const HEADER_HEIGHT = 64; // px - match App.tsx /** * Reusable full-screen panel component * Handles portal rendering, header with back button, and footer * Uses solid theme colors without transparency */ export const FullScreenPanel: React.FC = ({ isOpen, title, onClose, children, footer, contentClassName, }) => { React.useEffect(() => { if (isOpen) { document.body.style.overflow = "hidden"; } return () => { document.body.style.overflow = ""; }; }, [isOpen]); // ESC 键关闭面板 const onCloseRef = React.useRef(onClose); React.useEffect(() => { onCloseRef.current = onClose; }, [onClose]); React.useEffect(() => { if (!isOpen) return; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { // 子组件(例如 Radix 的 Select/Dialog/Dropdown)如果已经消费了 ESC,就不要再关闭整个面板 if (event.defaultPrevented) { return; } if (isTextEditableTarget(event.target)) { return; // 让输入框自己处理 ESC(比如清空、失焦等) } event.stopPropagation(); // 阻止事件继续冒泡到 window,避免触发 App.tsx 的全局监听 onCloseRef.current(); } }; // 使用冒泡阶段监听,让子组件(如 Radix UI)优先处理 ESC window.addEventListener("keydown", handleKeyDown, false); return () => { window.removeEventListener("keydown", handleKeyDown, false); }; }, [isOpen]); return createPortal( {isOpen && ( {/* Drag region - match App.tsx. Linux 上 DRAG_BAR_HEIGHT=0, 直接跳过整个元素;macOS 保留 28px 拖拽占位。 */} {DRAG_BAR_HEIGHT > 0 && (
)} {/* Header - match App.tsx */}

{title}

{/* Content */}
{children}
{/* Footer */} {footer && (
{footer}
)} )} , document.body, ); };