Modal: Use default Portal root element to render Modals (#109271)

* Table: Fix z-index conflict between DataLinksActionTooltip and ActionConfirmModal

* add an action to the kitchen sink Info column

* don't need the class for z-index

* fix this issue across a variety of viz types

* kill tooltip container in TableNG

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
Paul Marbach 2025-08-15 16:38:57 -04:00 committed by GitHub
parent 0eb79b3f53
commit 6d97f05349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 37 additions and 16 deletions

View File

@ -100,6 +100,22 @@
"url": "https://google.com/search?q=${__value:percentencode}"
}
]
},
{
"id": "actions",
"value": [
{
"fetch": {
"body": "{}",
"headers": [["Content-Type", "application/json"]],
"method": "GET",
"queryParams": [],
"url": "https://grafana.com"
},
"title": "My action",
"type": "fetch"
}
]
}
]
},
@ -1673,5 +1689,5 @@
"timezone": "",
"title": "Panel Tests - Table - Kitchen Sink",
"uid": "dcb9f5e9-8066-4397-889e-864b99555dbb",
"version": 8
"version": 9
}

View File

@ -11,6 +11,8 @@ import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
interface Props {
className?: string;
root?: HTMLElement;
// the zIndex of the node; defaults to theme.zIndex.portal
zIndex?: number;
forwardedRef?: React.ForwardedRef<HTMLDivElement>;
}
@ -26,7 +28,7 @@ export function Portal(props: PropsWithChildren<Props>) {
node.current.className = className;
}
node.current.style.position = 'relative';
node.current.style.zIndex = `${theme.zIndex.portal}`;
node.current.style.zIndex = `${props.zIndex ?? theme.zIndex.portal}`;
}
useLayoutEffect(() => {

View File

@ -5,7 +5,7 @@ import { useMemo, ReactNode } from 'react';
import { ActionModel, GrafanaTheme2, LinkModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes/ThemeContext';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { Portal } from '../Portal/Portal';
import { VizTooltipFooter } from '../VizTooltip/VizTooltipFooter';
import { VizTooltipWrapper } from '../VizTooltip/VizTooltipWrapper';
@ -25,6 +25,7 @@ interface Props {
* @internal
*/
export const DataLinksActionsTooltip = ({ links, actions, value, coords, onTooltipClose }: Props) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
// the order of middleware is important!
@ -83,7 +84,7 @@ export const DataLinksActionsTooltip = ({ links, actions, value, coords, onToolt
<>
{/* TODO: we can remove `value` from this component when tableNextGen is fully rolled out */}
{value}
<Portal>
<Portal zIndex={theme.zIndex.tooltip}>
<div
ref={refCallback}
{...getReferenceProps()}
@ -119,7 +120,6 @@ export const renderSingleLink = (link: LinkModel, children: ReactNode, className
const getStyles = (theme: GrafanaTheme2) => {
return {
tooltipWrapper: css({
zIndex: theme.zIndex.portal,
whiteSpace: 'pre',
borderRadius: theme.shape.radius.default,
background: theme.colors.background.primary,

View File

@ -749,7 +749,7 @@ const getStyles = (theme: GrafanaTheme2, maxWidth?: number) => ({
tooltipWrapper: css({
top: 0,
left: 0,
zIndex: theme.zIndex.portal,
zIndex: theme.zIndex.tooltip,
whiteSpace: 'pre',
borderRadius: theme.shape.radius.default,
position: 'fixed',

View File

@ -1,3 +1,4 @@
import { UNSAFE_PortalProvider } from '@react-aria/overlays';
import { Action, KBarProvider } from 'kbar';
import { Component, ComponentType, Fragment, ReactNode } from 'react';
import CacheProvider from 'react-inlinesvg/provider';
@ -5,7 +6,7 @@ import { Provider } from 'react-redux';
import { Route, Routes } from 'react-router-dom-v5-compat';
import { config, navigationLogger, reportInteraction } from '@grafana/runtime';
import { ErrorBoundaryAlert, PortalContainer, TimeRangeProvider } from '@grafana/ui';
import { ErrorBoundaryAlert, getPortalContainer, PortalContainer, TimeRangeProvider } from '@grafana/ui';
import { getAppRoutes } from 'app/routes/routes';
import { store } from 'app/store/store';
@ -122,12 +123,14 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
<ScopesContextProvider>
<ExtensionRegistriesProvider registries={pluginExtensionRegistries}>
<MaybeExtensionSidebarProvider>
<GlobalStylesWrapper />
<div className="grafana-app">
<RouterWrapper {...routerWrapperProps} />
<LiveConnectionWarning />
<PortalContainer />
</div>
<UNSAFE_PortalProvider getContainer={getPortalContainer}>
<GlobalStylesWrapper />
<div className="grafana-app">
<RouterWrapper {...routerWrapperProps} />
<LiveConnectionWarning />
<PortalContainer />
</div>
</UNSAFE_PortalProvider>
</MaybeExtensionSidebarProvider>
</ExtensionRegistriesProvider>
</ScopesContextProvider>

View File

@ -14,7 +14,7 @@ import {
ValueLinkConfig,
ActionModel,
} from '@grafana/data';
import { Portal, useStyles2, VizTooltipContainer } from '@grafana/ui';
import { Portal, useStyles2, useTheme2, VizTooltipContainer } from '@grafana/ui';
import {
VizTooltipContent,
VizTooltipFooter,
@ -33,6 +33,7 @@ interface Props {
}
export const CanvasTooltip = ({ scene }: Props) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
const onClose = () => {
@ -139,7 +140,7 @@ export const CanvasTooltip = ({ scene }: Props) => {
return (
<>
{scene.tooltipPayload?.element && scene.tooltipPayload.anchorPoint && (
<Portal>
<Portal zIndex={theme.zIndex.tooltip}>
<VizTooltipContainer
className={cx(styles.tooltipWrapper, scene.tooltipPayload.isOpen && styles.pinned)}
position={{ x: scene.tooltipPayload.anchorPoint.x, y: scene.tooltipPayload.anchorPoint.y }}
@ -167,7 +168,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
tooltipWrapper: css({
top: 0,
left: 0,
zIndex: theme.zIndex.portal,
whiteSpace: 'pre',
borderRadius: theme.shape.radius.default,
position: 'fixed',