From 5b3e8690fad1ac84d27b5e2fe6e135307166e75d Mon Sep 17 00:00:00 2001 From: tdbishop Date: Mon, 29 Dec 2025 10:52:37 -0600 Subject: [PATCH] Add awareness of a parent when toggletip is rendered to work inside other modals --- .../src/components/Toggletip/Toggletip.mdx | 20 +++++++ .../components/Toggletip/Toggletip.story.tsx | 54 +++++++++++++++++++ .../src/components/Toggletip/Toggletip.tsx | 7 ++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.mdx b/packages/grafana-ui/src/components/Toggletip/Toggletip.mdx index 53b75f5b5cb..d786d63bd04 100644 --- a/packages/grafana-ui/src/components/Toggletip/Toggletip.mdx +++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.mdx @@ -76,4 +76,24 @@ return ( ); ``` +### Usage inside Drawer + +When using Toggletip inside a Drawer or other focus-trapped container, pass the container element as `portalRoot` to ensure focus management works correctly. This renders the Toggletip content inside the Drawer's DOM tree instead of the default portal container. + +Use a state-based ref pattern to ensure the container is available before rendering the Toggletip: + +```tsx +const [containerEl, setContainerEl] = useState(null); + + +
+ {containerEl && ( + } portalRoot={containerEl}> + + + )} +
+
; +``` + diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.story.tsx b/packages/grafana-ui/src/components/Toggletip/Toggletip.story.tsx index 1c3cbb57919..54a8b855520 100644 --- a/packages/grafana-ui/src/components/Toggletip/Toggletip.story.tsx +++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.story.tsx @@ -1,6 +1,10 @@ import { Meta, StoryFn } from '@storybook/react'; +import { useState } from 'react'; import { Button } from '../Button/Button'; +import { Drawer } from '../Drawer/Drawer'; +import { Field } from '../Forms/Field'; +import { Input } from '../Input/Input'; import { ScrollContainer } from '../ScrollContainer/ScrollContainer'; import mdx from '../Toggletip/Toggletip.mdx'; @@ -133,4 +137,54 @@ LongContent.parameters = { }, }; +export const InsideDrawer: StoryFn = () => { + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + // Use state instead of ref to trigger re-render when container is available + const [containerEl, setContainerEl] = useState(null); + + return ( + <> + + {isDrawerOpen && ( + setIsDrawerOpen(false)}> +
+

+ This demonstrates using Toggletip inside a Drawer. The portalRoot prop is used to render the + Toggletip content inside the Drawer's DOM, allowing focus to work correctly with the Drawer's + focus trap. +

+ {containerEl && ( + + + + + +
+ } + footer="Focus should work correctly within this Toggletip" + placement="bottom-start" + portalRoot={containerEl} + > + + + )} + +
+ )} + + ); +}; + +InsideDrawer.parameters = { + controls: { + hideNoControlsWarning: true, + exclude: ['title', 'content', 'footer', 'children', 'placement', 'theme', 'closeButton', 'portalRoot'], + }, +}; + export default meta; diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx index 002f15ec04b..6bda1602385 100644 --- a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx +++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx @@ -47,6 +47,10 @@ export interface ToggletipProps { show?: boolean; /** Callback function to be called when the toggletip is opened */ onOpen?: () => void; + /** Optional root element for the portal. Use when Toggletip is inside a focus-trapped container like Drawer. + * When provided, the Toggletip will render inside this element and disable its own modal focus trap, + * deferring focus management to the parent container. */ + portalRoot?: HTMLElement; } /** @@ -67,6 +71,7 @@ export const Toggletip = memo( fitContent = false, onOpen, show, + portalRoot, }: ToggletipProps) => { const arrowRef = useRef(null); const grafanaTheme = useTheme2(); @@ -119,7 +124,7 @@ export const Toggletip = memo( ...getReferenceProps(), })} {isOpen && ( - +