Fix scroll jumping
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fix scroll jumping when opening menu in Backstage UI.
|
||||
@@ -412,3 +412,38 @@ export const VirtualizedMaxHeight: Story = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithScroll: Story = {
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<div style={{ height: '2000px', overflow: 'auto' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
render: () => (
|
||||
<MenuTrigger>
|
||||
<Button aria-label="Menu">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>Edit</MenuItem>
|
||||
<MenuItem>Duplicate</MenuItem>
|
||||
<MenuItem>Rename</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem iconStart={<RiShareBoxLine />}>Share</MenuItem>
|
||||
<MenuItem iconStart={<RiChat1Line />}>Feedback</MenuItem>
|
||||
<MenuSeparator />
|
||||
<SubmenuTrigger>
|
||||
<MenuItem iconStart={<RiSettingsLine />}>Settings</MenuItem>
|
||||
<Menu placement="right top">
|
||||
<MenuItem>Edit</MenuItem>
|
||||
<MenuItem>Duplicate</MenuItem>
|
||||
<MenuItem>Rename</MenuItem>
|
||||
</Menu>
|
||||
</SubmenuTrigger>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
RouterProvider,
|
||||
Virtualizer,
|
||||
ListLayout,
|
||||
OverlayTriggerStateContext,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import type {
|
||||
@@ -54,6 +55,7 @@ import {
|
||||
} from '@remixicon/react';
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
import { useRef, useEffect, useContext } from 'react';
|
||||
|
||||
// The height will be used for virtualized menus. It should match the size set in CSS for each menu item.
|
||||
const rowHeight = 32;
|
||||
@@ -86,6 +88,33 @@ export const Menu = (props: MenuProps<object>) => {
|
||||
const { classNames } = useStyles('Menu');
|
||||
const navigate = useNavigate();
|
||||
let newMaxWidth = maxWidth || (virtualized ? '260px' : 'undefined');
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const state = useContext(OverlayTriggerStateContext);
|
||||
|
||||
// Custom click-outside handler for non-modal popovers
|
||||
useEffect(() => {
|
||||
if (!state?.isOpen) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Node;
|
||||
|
||||
// Check if click is outside the popover
|
||||
if (popoverRef.current && !popoverRef.current.contains(target)) {
|
||||
// Check if click is on a trigger button or submenu
|
||||
const isOnTrigger = (target as Element).closest('[data-trigger]');
|
||||
const isOnSubmenu = (target as Element).closest('[role="menu"]');
|
||||
|
||||
if (!isOnTrigger && !isOnSubmenu) {
|
||||
state.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
const menuContent = (
|
||||
<RAMenu
|
||||
@@ -97,7 +126,13 @@ export const Menu = (props: MenuProps<object>) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<RAPopover className={classNames.popover} placement={placement}>
|
||||
<RAPopover
|
||||
ref={popoverRef}
|
||||
className={classNames.popover}
|
||||
placement={placement}
|
||||
isNonModal={true}
|
||||
isKeyboardDismissDisabled={false}
|
||||
>
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
|
||||
Reference in New Issue
Block a user