mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DockedMegaMenu: Keep undock button (#78461)
* dock undock smoothly * handle keyboard focus * use ref instead of state * run i18n:extract * undo this change * make dock/undock first button to focus * only focus when going to docked, add comment * minor tweaks
This commit is contained in:
@@ -93,7 +93,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
</main>
|
</main>
|
||||||
{!state.chromeless && (
|
{!state.chromeless && (
|
||||||
<>
|
<>
|
||||||
{config.featureToggles.dockedMegaMenu ? (
|
{config.featureToggles.dockedMegaMenu && state.megaMenu !== 'docked' ? (
|
||||||
<AppChromeMenu />
|
<AppChromeMenu />
|
||||||
) : (
|
) : (
|
||||||
<MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu('closed')} />
|
<MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu('closed')} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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 } from 'react';
|
||||||
import CSSTransition from 'react-transition-group/CSSTransition';
|
import CSSTransition from 'react-transition-group/CSSTransition';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
@@ -20,11 +20,18 @@ export function AppChromeMenu({}: Props) {
|
|||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
|
const prevMegaMenuState = useRef(state.megaMenu);
|
||||||
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
|
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
prevMegaMenuState.current = state.megaMenu;
|
||||||
|
}, [state.megaMenu]);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const backdropRef = useRef(null);
|
const backdropRef = useRef(null);
|
||||||
const animationSpeed = theme.transitions.duration.shortest;
|
// we don't want to show the opening animation when transitioning between docked + open
|
||||||
|
const animationSpeed =
|
||||||
|
prevMegaMenuState.current === 'docked' && state.megaMenu === 'open' ? 0 : theme.transitions.duration.shortest;
|
||||||
const animationStyles = useStyles2(getAnimStyles, animationSpeed);
|
const animationStyles = useStyles2(getAnimStyles, animationSpeed);
|
||||||
|
|
||||||
const isOpen = state.megaMenu === 'open';
|
const isOpen = state.megaMenu === 'open';
|
||||||
@@ -57,7 +64,7 @@ export function AppChromeMenu({}: Props) {
|
|||||||
classNames={animationStyles.overlay}
|
classNames={animationStyles.overlay}
|
||||||
timeout={{ enter: animationSpeed, exit: 0 }}
|
timeout={{ enter: animationSpeed, exit: 0 }}
|
||||||
>
|
>
|
||||||
<FocusScope contain autoFocus>
|
<FocusScope contain autoFocus restoreFocus>
|
||||||
<MegaMenu className={styles.menu} onClose={onClose} ref={ref} {...overlayProps} {...dialogProps} />
|
<MegaMenu className={styles.menu} onClose={onClose} ref={ref} {...overlayProps} {...dialogProps} />
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ export const MegaMenu = React.memo(
|
|||||||
const activeItem = getActiveItem(navItems, location.pathname);
|
const activeItem = getActiveItem(navItems, location.pathname);
|
||||||
|
|
||||||
const handleDockedMenu = () => {
|
const handleDockedMenu = () => {
|
||||||
chrome.setMegaMenu(state.megaMenu === 'docked' ? 'closed' : 'docked');
|
chrome.setMegaMenu(state.megaMenu === 'docked' ? 'open' : 'docked');
|
||||||
|
|
||||||
|
// refocus on dock/undock button when changing state
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById('dock-menu-button')?.focus();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,21 +59,26 @@ export const MegaMenu = React.memo(
|
|||||||
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
||||||
<ul className={styles.itemList}>
|
<ul className={styles.itemList}>
|
||||||
{navItems.map((link, index) => (
|
{navItems.map((link, index) => (
|
||||||
<Stack key={link.text} direction="row" alignItems="center">
|
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="center">
|
||||||
<MegaMenuItem
|
{index === 0 && (
|
||||||
link={link}
|
|
||||||
onClick={state.megaMenu === 'open' ? onClose : undefined}
|
|
||||||
activeItem={activeItem}
|
|
||||||
/>
|
|
||||||
{index === 0 && Boolean(state.megaMenu === 'open') && (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
id="dock-menu-button"
|
||||||
className={styles.dockMenuButton}
|
className={styles.dockMenuButton}
|
||||||
tooltip={t('navigation.megamenu.dock', 'Dock menu')}
|
tooltip={
|
||||||
|
state.megaMenu === 'docked'
|
||||||
|
? t('navigation.megamenu.undock', 'Undock menu')
|
||||||
|
: t('navigation.megamenu.dock', 'Dock menu')
|
||||||
|
}
|
||||||
name="web-section-alt"
|
name="web-section-alt"
|
||||||
onClick={handleDockedMenu}
|
onClick={handleDockedMenu}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<MegaMenuItem
|
||||||
|
link={link}
|
||||||
|
onClick={state.megaMenu === 'open' ? onClose : undefined}
|
||||||
|
activeItem={activeItem}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { Components } from '@grafana/e2e-selectors';
|
import { Components } from '@grafana/e2e-selectors';
|
||||||
import { Icon, IconButton, ToolbarButton, useStyles2 } from '@grafana/ui';
|
import { Icon, IconButton, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
|
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
|
||||||
import { useSelector } from 'app/types';
|
import { useSelector } from 'app/types';
|
||||||
@@ -39,9 +40,22 @@ export function NavToolbar({
|
|||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
|
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
|
||||||
|
const theme = useTheme2();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav);
|
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav);
|
||||||
|
|
||||||
|
const dockMenuBreakpoint = theme.breakpoints.values.xl;
|
||||||
|
const [isTooSmallForDockedMenu, setIsTooSmallForDockedMenu] = useState(
|
||||||
|
!window.matchMedia(`(min-width: ${dockMenuBreakpoint}px)`).matches
|
||||||
|
);
|
||||||
|
|
||||||
|
useMediaQueryChange({
|
||||||
|
breakpoint: dockMenuBreakpoint,
|
||||||
|
onChange: (e) => {
|
||||||
|
setIsTooSmallForDockedMenu(!e.matches);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid={Components.NavToolbar.container} className={styles.pageToolbar}>
|
<div data-testid={Components.NavToolbar.container} className={styles.pageToolbar}>
|
||||||
<div className={styles.menuButton}>
|
<div className={styles.menuButton}>
|
||||||
@@ -49,7 +63,7 @@ export function NavToolbar({
|
|||||||
id={TOGGLE_BUTTON_ID}
|
id={TOGGLE_BUTTON_ID}
|
||||||
name="bars"
|
name="bars"
|
||||||
tooltip={
|
tooltip={
|
||||||
state.megaMenu === 'closed'
|
state.megaMenu === 'closed' || (state.megaMenu === 'docked' && isTooSmallForDockedMenu)
|
||||||
? t('navigation.toolbar.open-menu', 'Open menu')
|
? t('navigation.toolbar.open-menu', 'Open menu')
|
||||||
: t('navigation.toolbar.close-menu', 'Close menu')
|
: t('navigation.toolbar.close-menu', 'Close menu')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -935,7 +935,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "Menü schließen",
|
"close": "Menü schließen",
|
||||||
"dock": "Menü andocken"
|
"dock": "Menü andocken",
|
||||||
|
"undock": ""
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "Menü schließen",
|
"close-menu": "Menü schließen",
|
||||||
|
|||||||
@@ -935,7 +935,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "Close menu",
|
"close": "Close menu",
|
||||||
"dock": "Dock menu"
|
"dock": "Dock menu",
|
||||||
|
"undock": "Undock menu"
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "Close menu",
|
"close-menu": "Close menu",
|
||||||
|
|||||||
@@ -941,7 +941,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "Cerrar menú",
|
"close": "Cerrar menú",
|
||||||
"dock": "Menú base"
|
"dock": "Menú base",
|
||||||
|
"undock": ""
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "Cerrar menú",
|
"close-menu": "Cerrar menú",
|
||||||
|
|||||||
@@ -941,7 +941,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "Fermer le menu",
|
"close": "Fermer le menu",
|
||||||
"dock": "Ancrer le menu"
|
"dock": "Ancrer le menu",
|
||||||
|
"undock": ""
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "Fermer le menu",
|
"close-menu": "Fermer le menu",
|
||||||
|
|||||||
@@ -935,7 +935,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "Cľőşę męʼnū",
|
"close": "Cľőşę męʼnū",
|
||||||
"dock": "Đőčĸ męʼnū"
|
"dock": "Đőčĸ męʼnū",
|
||||||
|
"undock": "Ůʼnđőčĸ męʼnū"
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "Cľőşę męʼnū",
|
"close-menu": "Cľőşę męʼnū",
|
||||||
|
|||||||
@@ -929,7 +929,8 @@
|
|||||||
},
|
},
|
||||||
"megamenu": {
|
"megamenu": {
|
||||||
"close": "关闭菜单",
|
"close": "关闭菜单",
|
||||||
"dock": "停靠菜单"
|
"dock": "停靠菜单",
|
||||||
|
"undock": ""
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"close-menu": "关闭菜单",
|
"close-menu": "关闭菜单",
|
||||||
|
|||||||
Reference in New Issue
Block a user