mirror of https://github.com/grafana/grafana.git
				
				
				
			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();
 | 
			
		||||
 | 
			
		||||
  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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}>
 | 
			
		||||
      <GrafanaContext.Provider value={context}>
 | 
			
		||||
        <TestProvider>
 | 
			
		||||
          <Router history={locationService.getHistory()}>
 | 
			
		||||
            <MegaMenu onClose={() => {}} />
 | 
			
		||||
          </Router>
 | 
			
		||||
        </TestProvider>
 | 
			
		||||
      </GrafanaContext.Provider>
 | 
			
		||||
    </Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}>
 | 
			
		||||
      <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>
 | 
			
		||||
          </FocusScope>
 | 
			
		||||
        </div>
 | 
			
		||||
      </CSSTransition>
 | 
			
		||||
        <CSSTransition appear={true} in={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
 | 
			
		||||
          <div className={styles.backdrop} {...underlayProps} />
 | 
			
		||||
      <CSSTransition in={isOpen} unmountOnExit={true} classNames={animStyles.backdrop} timeout={animationSpeed}>
 | 
			
		||||
        <div ref={backdropRef} className={styles.backdrop} {...underlayProps} />
 | 
			
		||||
      </CSSTransition>
 | 
			
		||||
      </FocusScope>
 | 
			
		||||
    </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),
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue