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:
Ashley Harrison 2023-11-22 15:56:36 +00:00 committed by GitHub
parent b5d1c8874b
commit 4247696402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 59 additions and 22 deletions

View File

@ -93,7 +93,7 @@ export function AppChrome({ children }: Props) {
</main>
{!state.chromeless && (
<>
{config.featureToggles.dockedMegaMenu ? (
{config.featureToggles.dockedMegaMenu && state.megaMenu !== 'docked' ? (
<AppChromeMenu />
) : (
<MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu('closed')} />

View File

@ -2,7 +2,7 @@ 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 } from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
import { GrafanaTheme2 } from '@grafana/data';
@ -20,11 +20,18 @@ export function AppChromeMenu({}: Props) {
const theme = useTheme2();
const { chrome } = useGrafana();
const state = chrome.useState();
const prevMegaMenuState = useRef(state.megaMenu);
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
useEffect(() => {
prevMegaMenuState.current = state.megaMenu;
}, [state.megaMenu]);
const ref = 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 isOpen = state.megaMenu === 'open';
@ -57,7 +64,7 @@ export function AppChromeMenu({}: Props) {
classNames={animationStyles.overlay}
timeout={{ enter: animationSpeed, exit: 0 }}
>
<FocusScope contain autoFocus>
<FocusScope contain autoFocus restoreFocus>
<MegaMenu className={styles.menu} onClose={onClose} ref={ref} {...overlayProps} {...dialogProps} />
</FocusScope>
</CSSTransition>

View File

@ -35,7 +35,12 @@ export const MegaMenu = React.memo(
const activeItem = getActiveItem(navItems, location.pathname);
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 (
@ -54,21 +59,26 @@ export const MegaMenu = React.memo(
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
<ul className={styles.itemList}>
{navItems.map((link, index) => (
<Stack key={link.text} direction="row" alignItems="center">
<MegaMenuItem
link={link}
onClick={state.megaMenu === 'open' ? onClose : undefined}
activeItem={activeItem}
/>
{index === 0 && Boolean(state.megaMenu === 'open') && (
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="center">
{index === 0 && (
<IconButton
id="dock-menu-button"
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"
onClick={handleDockedMenu}
variant="secondary"
/>
)}
<MegaMenuItem
link={link}
onClick={state.megaMenu === 'open' ? onClose : undefined}
activeItem={activeItem}
/>
</Stack>
))}
</ul>

View File

@ -1,10 +1,11 @@
import { css } from '@emotion/css';
import React from 'react';
import React, { useState } from 'react';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
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 { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
import { t } from 'app/core/internationalization';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { useSelector } from 'app/types';
@ -39,9 +40,22 @@ export function NavToolbar({
const { chrome } = useGrafana();
const state = chrome.useState();
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
const theme = useTheme2();
const styles = useStyles2(getStyles);
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 (
<div data-testid={Components.NavToolbar.container} className={styles.pageToolbar}>
<div className={styles.menuButton}>
@ -49,7 +63,7 @@ export function NavToolbar({
id={TOGGLE_BUTTON_ID}
name="bars"
tooltip={
state.megaMenu === 'closed'
state.megaMenu === 'closed' || (state.megaMenu === 'docked' && isTooSmallForDockedMenu)
? t('navigation.toolbar.open-menu', 'Open menu')
: t('navigation.toolbar.close-menu', 'Close menu')
}

View File

@ -935,7 +935,8 @@
},
"megamenu": {
"close": "Menü schließen",
"dock": "Menü andocken"
"dock": "Menü andocken",
"undock": ""
},
"toolbar": {
"close-menu": "Menü schließen",

View File

@ -935,7 +935,8 @@
},
"megamenu": {
"close": "Close menu",
"dock": "Dock menu"
"dock": "Dock menu",
"undock": "Undock menu"
},
"toolbar": {
"close-menu": "Close menu",

View File

@ -941,7 +941,8 @@
},
"megamenu": {
"close": "Cerrar menú",
"dock": "Menú base"
"dock": "Menú base",
"undock": ""
},
"toolbar": {
"close-menu": "Cerrar menú",

View File

@ -941,7 +941,8 @@
},
"megamenu": {
"close": "Fermer le menu",
"dock": "Ancrer le menu"
"dock": "Ancrer le menu",
"undock": ""
},
"toolbar": {
"close-menu": "Fermer le menu",

View File

@ -935,7 +935,8 @@
},
"megamenu": {
"close": "Cľőşę męʼnū",
"dock": "Đőčĸ męʼnū"
"dock": "Đőčĸ męʼnū",
"undock": "Ůʼnđőčĸ męʼnū"
},
"toolbar": {
"close-menu": "Cľőşę męʼnū",

View File

@ -929,7 +929,8 @@
},
"megamenu": {
"close": "关闭菜单",
"dock": "停靠菜单"
"dock": "停靠菜单",
"undock": ""
},
"toolbar": {
"close-menu": "关闭菜单",