/** * Portal Component - Lynx 版 MUI Portal * 一比一对应 MUI Portal * * Portal 用于将子元素渲染到视觉层级的最顶层 * * 在 Lynx 中实现方式: * - 使用绝对定位 (position: fixed) 将元素放置在最顶层 * - 使用 zIndex 控制层级顺序 * - 使用 accessibility-exclusive-focus 实现焦点锁定 * * 这与 MUI 的 ReactDOM.createPortal 效果相同: * - 元素在视觉上脱离父级 * - 元素渲染在最顶层 * - 支持模态框、抽屉、菜单等覆盖层组件 */ import './Portal.css' export interface PortalProps { /** 子元素 */ children?: any /** 容器 ID (可选,用于多个 Portal 层级管理) */ containerId?: string /** 是否禁用 Portal (禁用时直接渲染子元素) */ disablePortal?: boolean /** 层级 (数值越大越靠前) */ zIndex?: number /** 是否锁定焦点在 Portal 内 */ keepFocus?: boolean /** 自定义类名 */ className?: string /** 内联样式 */ style?: Record } // ============================================= // Portal 类名 // ============================================= export const portalClasses = { root: 'MuiPortal-root', } // ============================================= // Portal 组件实现 // ============================================= export function Portal(props: PortalProps) { const { children, containerId, disablePortal = false, zIndex = 1300, // MUI 默认 modal zIndex keepFocus = false, className, style, ...other } = props // 如果禁用 Portal,直接渲染子元素 if (disablePortal) { return children } // 构建类名 const classes = [ portalClasses.root, className, ].filter(Boolean).join(' ') // 计算样式 - 使用 fixed 定位实现覆盖层效果 const computedStyle: Record = { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex, pointerEvents: 'none', // 默认不拦截事件 ...style, } // 子元素包装器样式 const wrapperStyle: Record = { pointerEvents: 'auto', // 子元素可以接收事件 width: '100%', height: '100%', } return ( {children} ) } export default Portal