From 6bb4638c3f24b9d737b80730561748874f4a7558 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Tue, 23 Jul 2024 15:45:55 -0700 Subject: [PATCH] fix(toolbar): Fix tooltip & hovercard & menu react-portal rendering inside the toolbar --- .../devtoolbar/components/providers.tsx | 25 +++++++++++-------- static/app/components/dropdownMenu/index.tsx | 5 +++- static/app/components/menuListItem.tsx | 5 +++- .../components/react/useReactPortalTarget.tsx | 18 +++++++++++++ static/app/components/tooltip.tsx | 12 ++++----- 5 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 static/app/components/react/useReactPortalTarget.tsx diff --git a/static/app/components/devtoolbar/components/providers.tsx b/static/app/components/devtoolbar/components/providers.tsx index 3ba29e36ad051..db09a9ace00ad 100644 --- a/static/app/components/devtoolbar/components/providers.tsx +++ b/static/app/components/devtoolbar/components/providers.tsx @@ -3,6 +3,7 @@ import createCache from '@emotion/cache'; import {CacheProvider, ThemeProvider} from '@emotion/react'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; +import {ReactPortalTargetProvider} from 'sentry/components/react/useReactPortalTarget'; import {lightTheme} from 'sentry/utils/theme'; import {ConfigurationContextProvider} from '../hooks/useConfiguration'; @@ -33,16 +34,18 @@ export default function Providers({children, config, container}: Props) { ); return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ); } diff --git a/static/app/components/dropdownMenu/index.tsx b/static/app/components/dropdownMenu/index.tsx index b78cc3e25d0d5..3e1b2385ae6b0 100644 --- a/static/app/components/dropdownMenu/index.tsx +++ b/static/app/components/dropdownMenu/index.tsx @@ -7,6 +7,7 @@ import {Item, Section} from '@react-stately/collections'; import type {DropdownButtonProps} from 'sentry/components/dropdownButton'; import DropdownButton from 'sentry/components/dropdownButton'; +import useReactPortalTarget from 'sentry/components/react/useReactPortalTarget'; import type {FormSize} from 'sentry/utils/theme'; import type {UseOverlayProps} from 'sentry/utils/useOverlay'; import useOverlay from 'sentry/utils/useOverlay'; @@ -153,6 +154,8 @@ function DropdownMenu({ }: DropdownMenuProps) { const isDisabled = disabledProp ?? (!items || items.length === 0); + const portalTarget = useReactPortalTarget(); + const {rootOverlayState} = useContext(DropdownMenuContext); const { isOpen, @@ -248,7 +251,7 @@ function DropdownMenu({ ); - return usePortal ? createPortal(menu, document.body) : menu; + return usePortal ? createPortal(menu, portalTarget) : menu; } return ( diff --git a/static/app/components/menuListItem.tsx b/static/app/components/menuListItem.tsx index 0923573f23525..bfa9230251e46 100644 --- a/static/app/components/menuListItem.tsx +++ b/static/app/components/menuListItem.tsx @@ -7,6 +7,7 @@ import styled from '@emotion/styled'; import InteractionStateLayer from 'sentry/components/interactionStateLayer'; import {Overlay, PositionWrapper} from 'sentry/components/overlay'; +import useReactPortalTarget from 'sentry/components/react/useReactPortalTarget'; import type {TooltipProps} from 'sentry/components/tooltip'; import {Tooltip} from 'sentry/components/tooltip'; import {space} from 'sentry/styles/space'; @@ -247,6 +248,8 @@ function DetailsOverlay({ const popper = usePopper(itemRef.current, overlayElement, POPPER_OPTIONS); + const portalTarget = useReactPortalTarget(); + return createPortal( (document.body); + +interface Props { + children: ReactNode; + target: HTMLElement | ShadowRoot; +} + +export function ReactPortalTargetProvider({children, target}: Props) { + return ( + {children} + ); +} + +export default function useReactPortalTarget() { + return useContext(ReactPortalTarget); +} diff --git a/static/app/components/tooltip.tsx b/static/app/components/tooltip.tsx index 718b529b76fd3..48af74800f419 100644 --- a/static/app/components/tooltip.tsx +++ b/static/app/components/tooltip.tsx @@ -6,11 +6,12 @@ import styled from '@emotion/styled'; import {AnimatePresence} from 'framer-motion'; import {Overlay, PositionWrapper} from 'sentry/components/overlay'; +import useReactPortalTarget from 'sentry/components/react/useReactPortalTarget'; import {space} from 'sentry/styles/space'; import type {UseHoverOverlayProps} from 'sentry/utils/useHoverOverlay'; import {useHoverOverlay} from 'sentry/utils/useHoverOverlay'; -interface TooltipProps extends UseHoverOverlayProps { +export interface TooltipProps extends UseHoverOverlayProps { /** * The content to show in the tooltip popover */ @@ -26,7 +27,7 @@ interface TooltipProps extends UseHoverOverlayProps { overlayStyle?: React.CSSProperties | SerializedStyles; } -function Tooltip({ +export function Tooltip({ children, overlayStyle, title, @@ -44,6 +45,8 @@ function Tooltip({ } }, [reset, disabled]); + const portalTarget = useReactPortalTarget(); + if (disabled || !title) { return {children}; } @@ -65,7 +68,7 @@ function Tooltip({ return ( {wrapTrigger(children)} - {createPortal({tooltipContent}, document.body)} + {createPortal({tooltipContent}, portalTarget)} ); } @@ -79,6 +82,3 @@ const TooltipContent = styled(Overlay)` line-height: 1.2; text-align: center; `; - -export type {TooltipProps}; -export {Tooltip};