mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Prevent chevron overlaying navbar menus (#47988)
* Attach nav item menus to a portal that's a sibling of the chevron to prevent incorrect stacking * Make sure hover menus size correctly * refactor into separate component * Fix focus behaviour * fix test again...
This commit is contained in:
parent
28665a869b
commit
11bc5e1a06
@ -78,14 +78,11 @@ function getStyles(theme: GrafanaTheme2, reverseDirection?: boolean) {
|
|||||||
menu: css`
|
menu: css`
|
||||||
background-color: ${theme.colors.background.primary};
|
background-color: ${theme.colors.background.primary};
|
||||||
border: 1px solid ${theme.components.panel.borderColor};
|
border: 1px solid ${theme.components.panel.borderColor};
|
||||||
bottom: ${reverseDirection ? 0 : 'auto'};
|
|
||||||
box-shadow: ${theme.shadows.z3};
|
box-shadow: ${theme.shadows.z3};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
left: 100%;
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
top: ${reverseDirection ? 'auto' : 0};
|
|
||||||
transition: ${theme.transitions.create('opacity')};
|
transition: ${theme.transitions.create('opacity')};
|
||||||
z-index: ${theme.zIndex.sidemenu};
|
z-index: ${theme.zIndex.sidemenu};
|
||||||
`,
|
`,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react';
|
import React, { ReactElement, useEffect, useState } from 'react';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { getPortalContainer, Icon, IconName, Link, useTheme2 } from '@grafana/ui';
|
import { Icon, IconName, Link, useTheme2 } from '@grafana/ui';
|
||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { MenuTriggerProps } from '@react-types/menu';
|
import { MenuTriggerProps } from '@react-types/menu';
|
||||||
import { useMenuTriggerState } from '@react-stately/menu';
|
import { useMenuTriggerState } from '@react-stately/menu';
|
||||||
@ -14,6 +14,7 @@ import { FocusScope } from '@react-aria/focus';
|
|||||||
import { NavBarItemMenuContext, useNavBarContext } from '../context';
|
import { NavBarItemMenuContext, useNavBarContext } from '../context';
|
||||||
import { NavFeatureHighlight } from '../NavFeatureHighlight';
|
import { NavFeatureHighlight } from '../NavFeatureHighlight';
|
||||||
import { reportExperimentView } from '@grafana/runtime';
|
import { reportExperimentView } from '@grafana/runtime';
|
||||||
|
import { getNavMenuPortalContainer } from './NavBarMenuPortalContainer';
|
||||||
|
|
||||||
export interface NavBarItemMenuTriggerProps extends MenuTriggerProps {
|
export interface NavBarItemMenuTriggerProps extends MenuTriggerProps {
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
@ -185,7 +186,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE
|
|||||||
<div className={cx(styles.element, 'dropdown')} {...focusWithinProps}>
|
<div className={cx(styles.element, 'dropdown')} {...focusWithinProps}>
|
||||||
{element}
|
{element}
|
||||||
{state.isOpen && (
|
{state.isOpen && (
|
||||||
<OverlayContainer portalContainer={getPortalContainer()}>
|
<OverlayContainer portalContainer={getNavMenuPortalContainer()}>
|
||||||
<NavBarItemMenuContext.Provider
|
<NavBarItemMenuContext.Provider
|
||||||
value={{
|
value={{
|
||||||
menuProps,
|
menuProps,
|
||||||
|
@ -99,6 +99,7 @@ const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive'], hasIcon: b
|
|||||||
overflowWrap: 'anywhere',
|
overflowWrap: 'anywhere',
|
||||||
padding: !hasIcon ? `${theme.spacing(0.5, 2)}` : '5px 12px 5px 10px',
|
padding: !hasIcon ? `${theme.spacing(0.5, 2)}` : '5px 12px 5px 10px',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
|
width: '100%',
|
||||||
'&:hover, &:focus-visible': {
|
'&:hover, &:focus-visible': {
|
||||||
backgroundColor: theme.colors.action.hover,
|
backgroundColor: theme.colors.action.hover,
|
||||||
color: theme.colors.text.primary,
|
color: theme.colors.text.primary,
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container';
|
||||||
|
|
||||||
|
export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body;
|
||||||
|
|
||||||
|
export const NavBarMenuPortalContainer = () => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
return <div className={styles.menuPortalContainer} id={NAV_MENU_PORTAL_CONTAINER_ID} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
NavBarMenuPortalContainer.displayName = 'NavBarMenuPortalContainer';
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
menuPortalContainer: css({
|
||||||
|
left: 0,
|
||||||
|
position: 'fixed',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
zIndex: theme.zIndex.sidemenu,
|
||||||
|
}),
|
||||||
|
});
|
@ -16,6 +16,7 @@ import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
|
|||||||
import { FocusScope } from '@react-aria/focus';
|
import { FocusScope } from '@react-aria/focus';
|
||||||
import { NavBarContext } from '../context';
|
import { NavBarContext } from '../context';
|
||||||
import { NavBarToggle } from './NavBarToggle';
|
import { NavBarToggle } from './NavBarToggle';
|
||||||
|
import { NavBarMenuPortalContainer } from './NavBarMenuPortalContainer';
|
||||||
|
|
||||||
const onOpenSearch = () => {
|
const onOpenSearch = () => {
|
||||||
locationService.partial({ search: 'open' });
|
locationService.partial({ search: 'open' });
|
||||||
@ -85,6 +86,8 @@ export const NavBarNext = React.memo(() => {
|
|||||||
onClick={() => setMenuOpen(!menuOpen)}
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<NavBarMenuPortalContainer />
|
||||||
|
|
||||||
<ul className={styles.itemList}>
|
<ul className={styles.itemList}>
|
||||||
<NavBarItemWithoutMenu
|
<NavBarItemWithoutMenu
|
||||||
isActive={isMatchOrChildMatch(homeItem, activeItem)}
|
isActive={isMatchOrChildMatch(homeItem, activeItem)}
|
||||||
|
@ -33,7 +33,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
border: `1px solid ${theme.colors.border.weak}`,
|
border: `1px solid ${theme.colors.border.weak}`,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
marginRight: 0,
|
marginRight: 0,
|
||||||
zIndex: theme.zIndex.sidemenu,
|
zIndex: theme.zIndex.sidemenu + 1,
|
||||||
|
|
||||||
[theme.breakpoints.down('md')]: {
|
[theme.breakpoints.down('md')]: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
|
@ -115,10 +115,9 @@ describe('VariablesUnknownTable', () => {
|
|||||||
await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i }));
|
await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i }));
|
||||||
|
|
||||||
// make sure we report the interaction for slow expansion
|
// make sure we report the interaction for slow expansion
|
||||||
await waitFor(() => expect(reportInteractionSpy).toHaveBeenCalledTimes(2));
|
await waitFor(() =>
|
||||||
expect(reportInteractionSpy.mock.calls[0][0]).toEqual('Unknown variables section expanded');
|
expect(reportInteractionSpy).toHaveBeenCalledWith('Slow unknown variables expansion', { elapsed: 1000 })
|
||||||
expect(reportInteractionSpy.mock.calls[1][0]).toEqual('Slow unknown variables expansion');
|
);
|
||||||
expect(reportInteractionSpy.mock.calls[1][1]).toEqual({ elapsed: 1000 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user