MegaMenu: Fix broken hamburger toggle (#52770)

* MegaMenu: Fix broken hamburger toggle

* oops

* MegaMenu: move NavBarToggle to FocusScope
This commit is contained in:
Andrey Smirnov 2022-08-03 16:08:39 +05:00 committed by GitHub
parent 7cf2b68e0a
commit fc9577b76d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 33 deletions

View File

@ -20,7 +20,7 @@ export function AppChrome({ children }: Props) {
const state = chrome.useState();
if (state.chromeless || !config.featureToggles.topnav) {
return <main className="main-view">{children} </main>;
return <main className="main-view">{children}</main>;
}
return (
@ -37,7 +37,7 @@ export function AppChrome({ children }: Props) {
/>
</div>
<div className={cx(styles.content, state.searchBarHidden && styles.contentNoSearchBar)}>{children}</div>
{state.megaMenuOpen && <MegaMenu searchBarHidden={state.searchBarHidden} onClose={chrome.toggleMegaMenu} />}
<MegaMenu searchBarHidden={state.searchBarHidden} onClose={() => chrome.setMegaMenu(false)} />
</main>
);
}

View File

@ -62,6 +62,10 @@ export class AppChromeService {
this.update({ megaMenuOpen: !this.state.getValue().megaMenuOpen });
};
setMegaMenu = (megaMenuOpen: boolean) => {
this.update({ megaMenuOpen });
};
toggleSearchBar = () => {
const searchBarHidden = !this.state.getValue().searchBarHidden;
store.set(this.searchBarStorageKey, searchBarHidden);

View File

@ -2,9 +2,11 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { NavModelItem, NavSection } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from 'app/store/configureStore';
import TestProvider from '../../../../test/helpers/TestProvider';
@ -31,15 +33,20 @@ const setup = () => {
},
];
const context = getGrafanaContextMock();
const store = configureStore({ navBarTree });
context.chrome.toggleMegaMenu();
return render(
<Provider store={store}>
<TestProvider>
<Router history={locationService.getHistory()}>
<MegaMenu onClose={() => {}} />
</Router>
</TestProvider>
<GrafanaContext.Provider value={context}>
<TestProvider>
<Router history={locationService.getHistory()}>
<MegaMenu onClose={() => {}} />
</Router>
</TestProvider>
</GrafanaContext.Provider>
</Provider>
);
};

View File

@ -2,12 +2,13 @@ import { css } from '@emotion/css';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
import React, { useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types';
import { NavItem } from '../NavBar/NavBarMenu';
@ -27,29 +28,47 @@ export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: P
const styles = getStyles(theme, searchBarHidden);
const animationSpeed = theme.transitions.duration.shortest;
const animStyles = getAnimStyles(theme, animationSpeed);
const { chrome } = useGrafana();
const state = chrome.useState();
const ref = useRef(null);
const backdropRef = useRef(null);
const { dialogProps } = useDialog({}, ref);
const [isOpen, setIsOpen] = useState(false);
const onMenuClose = () => setIsOpen(false);
const { overlayProps, underlayProps } = useOverlay(
{
isDismissable: true,
isOpen: true,
onClose,
onClose: onMenuClose,
},
ref
);
useEffect(() => {
if (state.megaMenuOpen) {
setIsOpen(true);
}
}, [state.megaMenuOpen]);
return (
<OverlayContainer>
<FocusScope contain autoFocus>
<CSSTransition appear={true} in={true} classNames={animStyles.overlay} timeout={animationSpeed}>
<div data-testid="navbarmenu" ref={ref} {...overlayProps} {...dialogProps} className={styles.container}>
<CSSTransition
in={isOpen}
unmountOnExit={true}
classNames={animStyles.overlay}
timeout={animationSpeed}
onExited={onClose}
>
<div data-testid="navbarmenu" ref={ref} {...overlayProps} {...dialogProps} className={styles.container}>
<FocusScope contain autoFocus>
<div className={styles.mobileHeader}>
<Icon name="bars" size="xl" />
<IconButton
aria-label="Close navigation menu"
name="times"
onClick={onClose}
onClick={onMenuClose}
size="xl"
variant="secondary"
/>
@ -59,24 +78,24 @@ export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: P
isExpanded={true}
onClick={() => {
reportInteraction('grafana_navigation_collapsed');
onClose();
onMenuClose();
}}
/>
<nav className={styles.content}>
<CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}>
{navItems.map((link) => (
<NavItem link={link} onClose={onClose} activeItem={activeItem} key={link.text} />
<NavItem link={link} onClose={onMenuClose} activeItem={activeItem} key={link.text} />
))}
</ul>
</CustomScrollbar>
</nav>
</div>
</CSSTransition>
<CSSTransition appear={true} in={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
<div className={styles.backdrop} {...underlayProps} />
</CSSTransition>
</FocusScope>
</FocusScope>
</div>
</CSSTransition>
<CSSTransition in={isOpen} unmountOnExit={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
<div ref={backdropRef} className={styles.backdrop} {...underlayProps} />
</CSSTransition>
</OverlayContainer>
);
}
@ -109,6 +128,7 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
zIndex: theme.zIndex.navbarFixed - 1,
position: 'fixed',
top: topPosition,
backgroundColor: theme.colors.background.primary,
boxSizing: 'content-box',
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.colors.border.weak}`,
@ -154,7 +174,7 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
const overlayTransition = {
...commonTransition,
transitionProperty: 'background-color, box-shadow, width',
transitionProperty: 'box-shadow, width',
// this is needed to prevent a horizontal scrollbar during the animation on firefox
'.scrollbar-view': {
overflow: 'hidden !important',
@ -167,7 +187,6 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
};
const overlayOpen = {
backgroundColor: theme.colors.background.primary,
boxShadow: theme.shadows.z3,
width: '100%',
[theme.breakpoints.up('md')]: {
@ -178,10 +197,6 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
const overlayClosed = {
boxShadow: 'none',
width: 0,
[theme.breakpoints.up('md')]: {
backgroundColor: theme.colors.background.primary,
width: theme.spacing(7),
},
};
const backdropOpen = {
@ -194,16 +209,16 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
return {
backdrop: {
appear: css(backdropClosed),
appearActive: css(backdropTransition, backdropOpen),
appearDone: css(backdropOpen),
enter: css(backdropClosed),
enterActive: css(backdropTransition, backdropOpen),
enterDone: css(backdropOpen),
exit: css(backdropOpen),
exitActive: css(backdropTransition, backdropClosed),
},
overlay: {
appear: css(overlayClosed),
appearActive: css(overlayTransition, overlayOpen),
appearDone: css(overlayOpen),
enter: css(overlayClosed),
enterActive: css(overlayTransition, overlayOpen),
enterDone: css(overlayOpen),
exit: css(overlayOpen),
exitActive: css(overlayTransition, overlayClosed),
},