mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
MegaMenu: Fix broken hamburger toggle (#52770)
* MegaMenu: Fix broken hamburger toggle * oops * MegaMenu: move NavBarToggle to FocusScope
This commit is contained in:
parent
7cf2b68e0a
commit
fc9577b76d
@ -20,7 +20,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
|
|
||||||
if (state.chromeless || !config.featureToggles.topnav) {
|
if (state.chromeless || !config.featureToggles.topnav) {
|
||||||
return <main className="main-view">{children} </main>;
|
return <main className="main-view">{children}</main>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -37,7 +37,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.content, state.searchBarHidden && styles.contentNoSearchBar)}>{children}</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>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,10 @@ export class AppChromeService {
|
|||||||
this.update({ megaMenuOpen: !this.state.getValue().megaMenuOpen });
|
this.update({ megaMenuOpen: !this.state.getValue().megaMenuOpen });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setMegaMenu = (megaMenuOpen: boolean) => {
|
||||||
|
this.update({ megaMenuOpen });
|
||||||
|
};
|
||||||
|
|
||||||
toggleSearchBar = () => {
|
toggleSearchBar = () => {
|
||||||
const searchBarHidden = !this.state.getValue().searchBarHidden;
|
const searchBarHidden = !this.state.getValue().searchBarHidden;
|
||||||
store.set(this.searchBarStorageKey, searchBarHidden);
|
store.set(this.searchBarStorageKey, searchBarHidden);
|
||||||
|
@ -2,9 +2,11 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
import { NavModelItem, NavSection } from '@grafana/data';
|
import { NavModelItem, NavSection } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
|
||||||
import TestProvider from '../../../../test/helpers/TestProvider';
|
import TestProvider from '../../../../test/helpers/TestProvider';
|
||||||
@ -31,15 +33,20 @@ const setup = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
const store = configureStore({ navBarTree });
|
const store = configureStore({ navBarTree });
|
||||||
|
|
||||||
|
context.chrome.toggleMegaMenu();
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<TestProvider>
|
<GrafanaContext.Provider value={context}>
|
||||||
<Router history={locationService.getHistory()}>
|
<TestProvider>
|
||||||
<MegaMenu onClose={() => {}} />
|
<Router history={locationService.getHistory()}>
|
||||||
</Router>
|
<MegaMenu onClose={() => {}} />
|
||||||
</TestProvider>
|
</Router>
|
||||||
|
</TestProvider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,13 @@ import { css } from '@emotion/css';
|
|||||||
import { useDialog } from '@react-aria/dialog';
|
import { useDialog } from '@react-aria/dialog';
|
||||||
import { FocusScope } from '@react-aria/focus';
|
import { FocusScope } from '@react-aria/focus';
|
||||||
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
|
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 CSSTransition from 'react-transition-group/CSSTransition';
|
||||||
|
|
||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui';
|
import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui';
|
||||||
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
|
||||||
import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types';
|
import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types';
|
||||||
import { NavItem } from '../NavBar/NavBarMenu';
|
import { NavItem } from '../NavBar/NavBarMenu';
|
||||||
@ -27,29 +28,47 @@ export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: P
|
|||||||
const styles = getStyles(theme, searchBarHidden);
|
const styles = getStyles(theme, searchBarHidden);
|
||||||
const animationSpeed = theme.transitions.duration.shortest;
|
const animationSpeed = theme.transitions.duration.shortest;
|
||||||
const animStyles = getAnimStyles(theme, animationSpeed);
|
const animStyles = getAnimStyles(theme, animationSpeed);
|
||||||
|
const { chrome } = useGrafana();
|
||||||
|
const state = chrome.useState();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
const backdropRef = useRef(null);
|
||||||
const { dialogProps } = useDialog({}, ref);
|
const { dialogProps } = useDialog({}, ref);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const onMenuClose = () => setIsOpen(false);
|
||||||
|
|
||||||
const { overlayProps, underlayProps } = useOverlay(
|
const { overlayProps, underlayProps } = useOverlay(
|
||||||
{
|
{
|
||||||
isDismissable: true,
|
isDismissable: true,
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
onClose,
|
onClose: onMenuClose,
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state.megaMenuOpen) {
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
}, [state.megaMenuOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayContainer>
|
<OverlayContainer>
|
||||||
<FocusScope contain autoFocus>
|
<CSSTransition
|
||||||
<CSSTransition appear={true} in={true} classNames={animStyles.overlay} timeout={animationSpeed}>
|
in={isOpen}
|
||||||
<div data-testid="navbarmenu" ref={ref} {...overlayProps} {...dialogProps} className={styles.container}>
|
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}>
|
<div className={styles.mobileHeader}>
|
||||||
<Icon name="bars" size="xl" />
|
<Icon name="bars" size="xl" />
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Close navigation menu"
|
aria-label="Close navigation menu"
|
||||||
name="times"
|
name="times"
|
||||||
onClick={onClose}
|
onClick={onMenuClose}
|
||||||
size="xl"
|
size="xl"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
/>
|
/>
|
||||||
@ -59,24 +78,24 @@ export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: P
|
|||||||
isExpanded={true}
|
isExpanded={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
reportInteraction('grafana_navigation_collapsed');
|
reportInteraction('grafana_navigation_collapsed');
|
||||||
onClose();
|
onMenuClose();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<nav className={styles.content}>
|
<nav className={styles.content}>
|
||||||
<CustomScrollbar hideHorizontalTrack>
|
<CustomScrollbar hideHorizontalTrack>
|
||||||
<ul className={styles.itemList}>
|
<ul className={styles.itemList}>
|
||||||
{navItems.map((link) => (
|
{navItems.map((link) => (
|
||||||
<NavItem link={link} onClose={onClose} activeItem={activeItem} key={link.text} />
|
<NavItem link={link} onClose={onMenuClose} activeItem={activeItem} key={link.text} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</FocusScope>
|
||||||
</CSSTransition>
|
</div>
|
||||||
<CSSTransition appear={true} in={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
|
</CSSTransition>
|
||||||
<div className={styles.backdrop} {...underlayProps} />
|
<CSSTransition in={isOpen} unmountOnExit={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
|
||||||
</CSSTransition>
|
<div ref={backdropRef} className={styles.backdrop} {...underlayProps} />
|
||||||
</FocusScope>
|
</CSSTransition>
|
||||||
</OverlayContainer>
|
</OverlayContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -109,6 +128,7 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
|
|||||||
zIndex: theme.zIndex.navbarFixed - 1,
|
zIndex: theme.zIndex.navbarFixed - 1,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: topPosition,
|
top: topPosition,
|
||||||
|
backgroundColor: theme.colors.background.primary,
|
||||||
boxSizing: 'content-box',
|
boxSizing: 'content-box',
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
borderRight: `1px solid ${theme.colors.border.weak}`,
|
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||||
@ -154,7 +174,7 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
|
|||||||
|
|
||||||
const overlayTransition = {
|
const overlayTransition = {
|
||||||
...commonTransition,
|
...commonTransition,
|
||||||
transitionProperty: 'background-color, box-shadow, width',
|
transitionProperty: 'box-shadow, width',
|
||||||
// this is needed to prevent a horizontal scrollbar during the animation on firefox
|
// this is needed to prevent a horizontal scrollbar during the animation on firefox
|
||||||
'.scrollbar-view': {
|
'.scrollbar-view': {
|
||||||
overflow: 'hidden !important',
|
overflow: 'hidden !important',
|
||||||
@ -167,7 +187,6 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const overlayOpen = {
|
const overlayOpen = {
|
||||||
backgroundColor: theme.colors.background.primary,
|
|
||||||
boxShadow: theme.shadows.z3,
|
boxShadow: theme.shadows.z3,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
@ -178,10 +197,6 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
|
|||||||
const overlayClosed = {
|
const overlayClosed = {
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
width: 0,
|
width: 0,
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
backgroundColor: theme.colors.background.primary,
|
|
||||||
width: theme.spacing(7),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const backdropOpen = {
|
const backdropOpen = {
|
||||||
@ -194,16 +209,16 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
backdrop: {
|
backdrop: {
|
||||||
appear: css(backdropClosed),
|
enter: css(backdropClosed),
|
||||||
appearActive: css(backdropTransition, backdropOpen),
|
enterActive: css(backdropTransition, backdropOpen),
|
||||||
appearDone: css(backdropOpen),
|
enterDone: css(backdropOpen),
|
||||||
exit: css(backdropOpen),
|
exit: css(backdropOpen),
|
||||||
exitActive: css(backdropTransition, backdropClosed),
|
exitActive: css(backdropTransition, backdropClosed),
|
||||||
},
|
},
|
||||||
overlay: {
|
overlay: {
|
||||||
appear: css(overlayClosed),
|
enter: css(overlayClosed),
|
||||||
appearActive: css(overlayTransition, overlayOpen),
|
enterActive: css(overlayTransition, overlayOpen),
|
||||||
appearDone: css(overlayOpen),
|
enterDone: css(overlayOpen),
|
||||||
exit: css(overlayOpen),
|
exit: css(overlayOpen),
|
||||||
exitActive: css(overlayTransition, overlayClosed),
|
exitActive: css(overlayTransition, overlayClosed),
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user