Performance: Create separate div for portal root to limit reflow -> recalc style (#47633)

* Performance: Create separate div for portal root to limit reflow -> recalc style

* refactoring
This commit is contained in:
Torkel Ödegaard 2022-04-13 07:39:22 +02:00 committed by GitHub
parent a4381ebc91
commit 6042beab43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 20 additions and 5 deletions

View File

@ -9,9 +9,11 @@ interface Props {
} }
export function Portal(props: PropsWithChildren<Props>) { export function Portal(props: PropsWithChildren<Props>) {
const { children, className, root: portalRoot = document.body, forwardedRef } = props; const { children, className, root, forwardedRef } = props;
const theme = useTheme2(); const theme = useTheme2();
const node = useRef<HTMLDivElement | null>(null); const node = useRef<HTMLDivElement | null>(null);
const portalRoot = root ?? getPortalContainer();
if (!node.current) { if (!node.current) {
node.current = document.createElement('div'); node.current = document.createElement('div');
if (className) { if (className) {
@ -25,6 +27,7 @@ export function Portal(props: PropsWithChildren<Props>) {
if (node.current) { if (node.current) {
portalRoot.appendChild(node.current); portalRoot.appendChild(node.current);
} }
return () => { return () => {
if (node.current) { if (node.current) {
portalRoot.removeChild(node.current); portalRoot.removeChild(node.current);
@ -35,7 +38,18 @@ export function Portal(props: PropsWithChildren<Props>) {
return ReactDOM.createPortal(<div ref={forwardedRef}>{children}</div>, node.current); return ReactDOM.createPortal(<div ref={forwardedRef}>{children}</div>, node.current);
} }
/** @internal */
export function getPortalContainer() {
return window.document.getElementById('grafana-portal-container') ?? document.body;
}
/** @internal */
export function PortalContainer() {
return <div id="grafana-portal-container" />;
}
export const RefForwardingPortal = React.forwardRef<HTMLDivElement, Props>((props, ref) => { export const RefForwardingPortal = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
return <Portal {...props} forwardedRef={ref} />; return <Portal {...props} forwardedRef={ref} />;
}); });
RefForwardingPortal.displayName = 'RefForwardingPortal'; RefForwardingPortal.displayName = 'RefForwardingPortal';

View File

@ -6,7 +6,7 @@ export { Tooltip } from './Tooltip/Tooltip';
export { PopoverContent } from './Tooltip/types'; export { PopoverContent } from './Tooltip/types';
export { PopoverController } from './Tooltip/PopoverController'; export { PopoverController } from './Tooltip/PopoverController';
export { Popover } from './Tooltip/Popover'; export { Popover } from './Tooltip/Popover';
export { Portal } from './Portal/Portal'; export { Portal, getPortalContainer, PortalContainer } from './Portal/Portal';
export { CustomScrollbar, ScrollbarPosition } from './CustomScrollbar/CustomScrollbar'; export { CustomScrollbar, ScrollbarPosition } from './CustomScrollbar/CustomScrollbar';
export { TabbedContainer, TabConfig } from './TabbedContainer/TabbedContainer'; export { TabbedContainer, TabConfig } from './TabbedContainer/TabbedContainer';

View File

@ -3,7 +3,7 @@ import { Router, Route, Redirect, Switch } from 'react-router-dom';
import { config, locationService, navigationLogger } from '@grafana/runtime'; import { config, locationService, navigationLogger } from '@grafana/runtime';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, ModalsProvider } from '@grafana/ui'; import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, ModalsProvider, PortalContainer } from '@grafana/ui';
import { GrafanaApp } from './app'; import { GrafanaApp } from './app';
import { getAppRoutes } from 'app/routes/routes'; import { getAppRoutes } from 'app/routes/routes';
import { ConfigContext, ThemeProvider } from './core/utils/ConfigProvider'; import { ConfigContext, ThemeProvider } from './core/utils/ConfigProvider';
@ -113,6 +113,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
</div> </div>
<LiveConnectionWarning /> <LiveConnectionWarning />
<ModalRoot /> <ModalRoot />
<PortalContainer />
</ModalsProvider> </ModalsProvider>
</ThemeProvider> </ThemeProvider>
</ConfigContext.Provider> </ConfigContext.Provider>

View File

@ -1,6 +1,6 @@
import React, { ReactElement, useEffect, useState } from 'react'; import React, { ReactElement, useEffect, useState } from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; import { getPortalContainer, Icon, IconName, Link, useTheme2 } from '@grafana/ui';
import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { MenuTriggerProps } from '@react-types/menu'; import { MenuTriggerProps } from '@react-types/menu';
import { useMenuTriggerState } from '@react-stately/menu'; import { useMenuTriggerState } from '@react-stately/menu';
@ -182,7 +182,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE
<div className={cx(styles.element, 'dropdown')} {...focusWithinProps} {...hoverProps}> <div className={cx(styles.element, 'dropdown')} {...focusWithinProps} {...hoverProps}>
{element} {element}
{state.isOpen && ( {state.isOpen && (
<OverlayContainer> <OverlayContainer portalContainer={getPortalContainer()}>
<NavBarItemMenuContext.Provider <NavBarItemMenuContext.Provider
value={{ value={{
menuProps, menuProps,