import React, { useEffect, useRef, useState, type ReactNode } from "react";
import { createPortal } from "react-dom";
export interface PortalProps {
/**
* @description_en Target DOM element to mount into. Defaults to document.body.
* @description_zh 挂载目标 DOM 元素,默认为 document.body。
* @default document.body
*/
to?: Element | null;
/**
* @description_en Child elements to render into the portal.
* @description_zh 要渲染到 portal 中的子元素。
*/
children?: ReactNode;
/**
* @description_en Whether to disable the portal and render children inline. Default is false.
* @description_zh 是否禁用 portal,直接内联渲染子元素。默认为 false。
* @default false
*/
disabled?: boolean;
}
/**
* @description_zh 声明式 Portal 组件,将子元素渲染到指定 DOM 节点,常用于模态框、浮层等场景。
* @description_en Declarative Portal component that renders children into a specified DOM node, commonly used for modals and overlays.
* @component
* @example
* ```tsx
*
*
*
*
*
*
*
* ```
*/
export function Portal({ to, children, disabled = false }: PortalProps): ReactNode {
const [mounted, setMounted] = useState(false);
// to 可能在首次渲染时为 null(SSR 或 ref 未就绪),延迟到客户端挂载后再渲染
const toRef = useRef(to);
toRef.current = to;
useEffect(() => {
setMounted(true);
}, []);
if (disabled) {
return <>{children}>;
}
if (!mounted) {
return null;
}
const target = toRef.current ?? document.body;
return createPortal(children, target);
}