diff --git a/.changeset/add-portal-provider.md b/.changeset/add-portal-provider.md new file mode 100644 index 000000000..3e820e901 --- /dev/null +++ b/.changeset/add-portal-provider.md @@ -0,0 +1,5 @@ +--- +'@radix-ui/react-portal': minor +--- + +Added `PortalProvider` to set a default portal container via context, useful for Shadow DOM scenarios diff --git a/packages/react/portal/src/index.ts b/packages/react/portal/src/index.ts index 81c8734f9..649f68070 100644 --- a/packages/react/portal/src/index.ts +++ b/packages/react/portal/src/index.ts @@ -1,7 +1,8 @@ 'use client'; export { Portal, + PortalProvider, // Root, } from './portal'; -export type { PortalProps } from './portal'; +export type { PortalProps, PortalProviderProps } from './portal'; diff --git a/packages/react/portal/src/portal.tsx b/packages/react/portal/src/portal.tsx index bd63df92a..43d646a4b 100644 --- a/packages/react/portal/src/portal.tsx +++ b/packages/react/portal/src/portal.tsx @@ -3,6 +3,28 @@ import ReactDOM from 'react-dom'; import { Primitive } from '@radix-ui/react-primitive'; import { useLayoutEffect } from '@radix-ui/react-use-layout-effect'; +/* ------------------------------------------------------------------------------------------------- + * PortalProvider + * -----------------------------------------------------------------------------------------------*/ + +const PortalContext = React.createContext(null); +PortalContext.displayName = 'PortalContext'; + +interface PortalProviderProps { + children: React.ReactNode; + /** + * The default container element for portaled content. + * @defaultValue document.body + */ + container: Element | DocumentFragment; +} + +const PortalProvider: React.FC = ({ container, children }) => { + return {children}; +}; + +PortalProvider.displayName = 'PortalProvider'; + /* ------------------------------------------------------------------------------------------------- * Portal * -----------------------------------------------------------------------------------------------*/ @@ -20,9 +42,10 @@ interface PortalProps extends PrimitiveDivProps { const Portal = React.forwardRef((props, forwardedRef) => { const { container: containerProp, ...portalProps } = props; + const contextContainer = React.useContext(PortalContext); const [mounted, setMounted] = React.useState(false); useLayoutEffect(() => setMounted(true), []); - const container = containerProp || (mounted && globalThis?.document?.body); + const container = containerProp || contextContainer || (mounted && globalThis?.document?.body); return container ? ReactDOM.createPortal(, container) : null; @@ -36,7 +59,8 @@ const Root = Portal; export { Portal, + PortalProvider, // Root, }; -export type { PortalProps }; +export type { PortalProps, PortalProviderProps };