import React, { HTMLAttributes, createContext, forwardRef, useContext, } from "react"; import ReactDOM from "react-dom"; import { useProvider } from "../provider/Provider"; import { Theme, useThemeInternal } from "../theme/Theme"; import { useClientLayoutEffect } from "../utils-external"; import { useMergeRefs } from "../utils/hooks"; export interface PortalProps extends HTMLAttributes { /** * An optional container where the portaled content should be appended. */ rootElement?: HTMLElement | null; } const PortalContext = createContext(null); export const Portal = forwardRef( ({ rootElement, children, ...restProps }, forwardedRef) => { const providerRootElement = useProvider()?.rootElement; const parentPortalNode = useContext(PortalContext); const [containerElement, setContainerElement] = React.useState< HTMLElement | ShadowRoot | null >(null); const [portalNode, setPortalNode] = React.useState( null, ); const containerRef = React.useRef(null); const mergedRefs = useMergeRefs(forwardedRef, setPortalNode); /** * We update container in effect to avoid SSR mismatches. */ useClientLayoutEffect(() => { /* Wait for the container to be resolved if explicitly `null`. */ if ((rootElement ?? providerRootElement) === null) { if (containerRef.current) { containerRef.current = null; setPortalNode(null); setContainerElement(null); } return; } const resolvedContainer = rootElement ?? parentPortalNode ?? providerRootElement ?? globalThis?.document?.body; if (resolvedContainer === null) { if (containerRef.current) { containerRef.current = null; setPortalNode(null); setContainerElement(null); } return; } if (containerRef.current !== resolvedContainer) { containerRef.current = resolvedContainer; setPortalNode(null); setContainerElement(resolvedContainer); } }, [parentPortalNode, providerRootElement, rootElement]); if (!containerElement) { return null; } return ReactDOM.createPortal( {children} , containerElement, ); }, ); type PortalDivProps = React.HTMLAttributes; const PortalDiv = forwardRef( (props: PortalDivProps, forwardedRef) => { const themeContext = useThemeInternal(); return (
); }, ); export default Portal;