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;