diff --git a/src/components/portal/portal.provider.tsx b/src/components/portal/portal.provider.tsx new file mode 100644 index 00000000000..35c8bb1ea78 --- /dev/null +++ b/src/components/portal/portal.provider.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { PropsWithChildren, useContext } from 'react'; +import { EuiPortalInsertion } from './portal.types'; + +const PortalContext = React.createContext( + undefined +); + +export function usePortalInsertion() { + return useContext(PortalContext); +} + +export type PortalProviderProps = PropsWithChildren<{ + insert: EuiPortalInsertion; +}>; + +export function PortalProvider(props: PortalProviderProps) { + return ( + + {props.children} + + ); +} diff --git a/src/components/portal/portal.tsx b/src/components/portal/portal.tsx index 996c6470915..39581bdb5f2 100644 --- a/src/components/portal/portal.tsx +++ b/src/components/portal/portal.tsx @@ -11,26 +11,12 @@ * into portals. */ -import { Component, ReactNode } from 'react'; +import React, { Component, ReactNode } from 'react'; import { createPortal } from 'react-dom'; import { EuiNestedThemeContext } from '../../services'; -import { keysOf } from '../common'; - -interface InsertPositionsMap { - after: InsertPosition; - before: InsertPosition; -} - -export const insertPositions: InsertPositionsMap = { - after: 'afterend', - before: 'beforebegin', -}; - -type EuiPortalInsertPosition = keyof typeof insertPositions; - -export const INSERT_POSITIONS: EuiPortalInsertPosition[] = - keysOf(insertPositions); +import { usePortalInsertion } from './portal.provider'; +import { insertPositions } from './portal.types'; export interface EuiPortalProps { /** @@ -41,7 +27,16 @@ export interface EuiPortalProps { portalRef?: (ref: HTMLDivElement | null) => void; } -export class EuiPortal extends Component { +export function EuiPortal(props: EuiPortalProps) { + const ctxInsertion = usePortalInsertion(); + return ( + + {props.children} + + ); +} + +export class EuiPortalClassComponent extends Component { static contextType = EuiNestedThemeContext; portalNode: HTMLDivElement | null = null; @@ -78,7 +73,7 @@ export class EuiPortal extends Component { } // Set the inherited color of the portal based on the wrapping EuiThemeProvider - setThemeColor() { + private setThemeColor() { if (this.portalNode && this.context) { const { hasDifferentColorFromGlobalTheme, colorClassName } = this.context; @@ -88,7 +83,7 @@ export class EuiPortal extends Component { } } - updatePortalRef(ref: HTMLDivElement | null) { + private updatePortalRef(ref: HTMLDivElement | null) { if (this.props.portalRef) { this.props.portalRef(ref); } diff --git a/src/components/portal/portal.types.ts b/src/components/portal/portal.types.ts new file mode 100644 index 00000000000..735e4875e46 --- /dev/null +++ b/src/components/portal/portal.types.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { keysOf } from '../common'; + +interface InsertPositionsMap { + after: InsertPosition; + before: InsertPosition; +} + +export const insertPositions: InsertPositionsMap = { + after: 'afterend', + before: 'beforebegin', +}; + +export type EuiPortalInsertPosition = keyof typeof insertPositions; + +export const INSERT_POSITIONS: EuiPortalInsertPosition[] = + keysOf(insertPositions); + +export interface EuiPortalInsertion { + sibling: HTMLElement; + position: EuiPortalInsertPosition; +} diff --git a/src/components/provider/provider.tsx b/src/components/provider/provider.tsx index 50b4c29f17b..be648fd0b41 100644 --- a/src/components/provider/provider.tsx +++ b/src/components/provider/provider.tsx @@ -22,6 +22,8 @@ import { } from '../../services'; import { EuiThemeAmsterdam } from '../../themes'; import { EuiCacheProvider } from './cache'; +import { EuiPortalInsertion } from '../portal/portal.types'; +import { PortalProvider } from '../portal/portal.provider'; const isEmotionCacheObject = ( obj: EmotionCache | Object @@ -61,6 +63,16 @@ export interface EuiProviderProps global?: EmotionCache; utility?: EmotionCache; }; + /** + * Provide global settings for EuiPortal props. + */ + portal?: { + /** + * Provide a global setting for EuiPortal's default insertion position. + * If not specified or set to `null`, `EuiPortal` will insert itself into the `document.body` by default. + */ + insert: EuiPortalInsertion | null; + }; } export const EuiProvider = ({ @@ -71,6 +83,7 @@ export const EuiProvider = ({ colorMode, modify, children, + portal, }: PropsWithChildren>) => { let defaultCache; let globalCache; @@ -113,7 +126,13 @@ export const EuiProvider = ({ /> )} - {children} + + {portal?.insert ? ( + {children} + ) : ( + children + )} + ); diff --git a/upcoming_changelogs/6889.md b/upcoming_changelogs/6889.md new file mode 100644 index 00000000000..dd5820ce7c5 --- /dev/null +++ b/upcoming_changelogs/6889.md @@ -0,0 +1 @@ +- Added `portal` prop to `EuiProvider`.