Compare commits

...

1 Commits

Author SHA1 Message Date
tdbishop
5b3e8690fa Add awareness of a parent when toggletip is rendered to work inside other modals 2025-12-29 10:52:37 -06:00
3 changed files with 80 additions and 1 deletions

View File

@@ -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<HTMLDivElement | null>(null);
<Drawer title="Settings" onClose={onClose}>
<div ref={setContainerEl}>
{containerEl && (
<Toggletip content={<Input placeholder="Type here..." />} portalRoot={containerEl}>
<Button>Open Toggletip</Button>
</Toggletip>
)}
</div>
</Drawer>;
```
<ArgTypes of={Toggletip} />

View File

@@ -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<typeof Toggletip> = () => {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
// Use state instead of ref to trigger re-render when container is available
const [containerEl, setContainerEl] = useState<HTMLDivElement | null>(null);
return (
<>
<Button onClick={() => setIsDrawerOpen(true)}>Open Drawer</Button>
{isDrawerOpen && (
<Drawer title="Drawer with Toggletip" onClose={() => setIsDrawerOpen(false)}>
<div ref={setContainerEl}>
<p style={{ marginBottom: '16px' }}>
This demonstrates using Toggletip inside a Drawer. The <code>portalRoot</code> prop is used to render the
Toggletip content inside the Drawer&apos;s DOM, allowing focus to work correctly with the Drawer&apos;s
focus trap.
</p>
{containerEl && (
<Toggletip
title="Interactive Form"
content={
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<Field label="Name">
<Input placeholder="Enter your name" />
</Field>
<Button variant="primary" size="sm">
Submit
</Button>
</div>
}
footer="Focus should work correctly within this Toggletip"
placement="bottom-start"
portalRoot={containerEl}
>
<Button>Click to show Toggletip</Button>
</Toggletip>
)}
</div>
</Drawer>
)}
</>
);
};
InsideDrawer.parameters = {
controls: {
hideNoControlsWarning: true,
exclude: ['title', 'content', 'footer', 'children', 'placement', 'theme', 'closeButton', 'portalRoot'],
},
};
export default meta;

View File

@@ -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 && (
<Portal>
<Portal root={portalRoot}>
<FloatingFocusManager context={context} modal={true}>
<div
data-testid="toggletip-content"