Navigation: Put expand toggle at beginning of tab order (#47268)

* Put toggle at beginning of tab order

* create NavBarToggle

* move margin into the common component

* lint fixes
This commit is contained in:
Ashley Harrison 2022-04-04 16:51:24 +01:00 committed by GitHub
parent 322a14fe6e
commit 1c34cc8b91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 31 deletions

View File

@ -8,21 +8,23 @@ import { css, cx } from '@emotion/css';
import { NavBarMenuItem } from './NavBarMenuItem'; import { NavBarMenuItem } from './NavBarMenuItem';
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
import { isMatchOrChildMatch } from '../utils'; import { isMatchOrChildMatch } from '../utils';
import { NavBarToggle } from './NavBarToggle';
export interface Props { export interface Props {
activeItem?: NavModelItem; activeItem?: NavModelItem;
isOpen: boolean;
navItems: NavModelItem[]; navItems: NavModelItem[];
onClose: () => void; onClose: () => void;
} }
export function NavBarMenu({ activeItem, navItems, onClose }: Props) { export function NavBarMenu({ activeItem, isOpen, navItems, onClose }: Props) {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const ref = useRef(null); const ref = useRef(null);
const { dialogProps } = useDialog({}, ref); const { dialogProps } = useDialog({}, ref);
const { overlayProps } = useOverlay( const { overlayProps } = useOverlay(
{ {
isDismissable: true, isDismissable: true,
isOpen: true, isOpen,
onClose, onClose,
}, },
ref ref
@ -32,6 +34,7 @@ export function NavBarMenu({ activeItem, navItems, onClose }: Props) {
<div data-testid="navbarmenu" className={styles.container}> <div data-testid="navbarmenu" className={styles.container}>
<FocusScope contain restoreFocus autoFocus> <FocusScope contain restoreFocus autoFocus>
<nav className={styles.content} ref={ref} {...overlayProps} {...dialogProps}> <nav className={styles.content} ref={ref} {...overlayProps} {...dialogProps}>
<NavBarToggle className={styles.menuCollapseIcon} isExpanded={isOpen} onClick={onClose} />
<CustomScrollbar hideHorizontalTrack> <CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}> <ul className={styles.itemList}>
{navItems.map((link) => ( {navItems.map((link) => (
@ -75,6 +78,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
display: 'grid', display: 'grid',
gridAutoRows: `minmax(${theme.spacing(6)}, auto)`, gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
}), }),
menuCollapseIcon: css({
position: 'absolute',
top: '43px',
right: '0px',
}),
}); });
function NavItem({ function NavItem({

View File

@ -4,7 +4,7 @@ import CSSTransition from 'react-transition-group/CSSTransition';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data'; import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
import { Icon, IconButton, IconName, useStyles2, useTheme2 } from '@grafana/ui'; import { Icon, IconName, useStyles2, useTheme2 } from '@grafana/ui';
import { config, locationService } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { getKioskMode } from 'app/core/navigation/kiosk'; import { getKioskMode } from 'app/core/navigation/kiosk';
import { KioskMode, StoreState } from 'app/types'; import { KioskMode, StoreState } from 'app/types';
@ -16,6 +16,7 @@ import { useSelector } from 'react-redux';
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
import { FocusScope } from '@react-aria/focus'; import { FocusScope } from '@react-aria/focus';
import { NavBarContext } from '../context'; import { NavBarContext } from '../context';
import { NavBarToggle } from './NavBarToggle';
const onOpenSearch = () => { const onOpenSearch = () => {
locationService.partial({ search: 'open' }); locationService.partial({ search: 'open' });
@ -79,6 +80,12 @@ export const NavBarNext = React.memo(() => {
<Icon name="bars" size="xl" /> <Icon name="bars" size="xl" />
</div> </div>
<NavBarToggle
className={styles.menuExpandIcon}
isExpanded={menuOpen}
onClick={() => setMenuOpen(!menuOpen)}
/>
<ul className={styles.itemList}> <ul className={styles.itemList}>
<NavBarItemWithoutMenu <NavBarItemWithoutMenu
isActive={isMatchOrChildMatch(homeItem, activeItem)} isActive={isMatchOrChildMatch(homeItem, activeItem)}
@ -132,16 +139,11 @@ export const NavBarNext = React.memo(() => {
<CSSTransition in={menuOpen} classNames={animStyles} timeout={150} unmountOnExit> <CSSTransition in={menuOpen} classNames={animStyles} timeout={150} unmountOnExit>
<NavBarMenu <NavBarMenu
activeItem={activeItem} activeItem={activeItem}
isOpen={menuOpen}
navItems={[homeItem, searchItem, ...coreItems, ...pluginItems, ...configItems]} navItems={[homeItem, searchItem, ...coreItems, ...pluginItems, ...configItems]}
onClose={() => setMenuOpen(false)} onClose={() => setMenuOpen(false)}
/> />
</CSSTransition> </CSSTransition>
<IconButton
name={menuOpen ? 'angle-left' : 'angle-right'}
className={styles.menuToggle}
size="xl"
onClick={() => setMenuOpen(!menuOpen)}
/>
</div> </div>
</div> </div>
); );
@ -233,20 +235,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
height: '100%', height: '100%',
zIndex: theme.zIndex.sidemenu, zIndex: theme.zIndex.sidemenu,
}), }),
menuToggle: css({ menuExpandIcon: css({
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.weak}`,
position: 'absolute', position: 'absolute',
marginRight: 0,
top: '43px', top: '43px',
right: '0px', right: '0px',
zIndex: theme.zIndex.sidemenu, transform: `translateX(50%)`,
transform: `translateX(calc(${theme.spacing(7)} + 50%))`,
borderRadius: '50%',
[theme.breakpoints.down('md')]: {
display: 'none',
},
}), }),
}); });
@ -267,34 +260,23 @@ const getAnimStyles = (theme: GrafanaTheme2) => {
width: theme.spacing(7), width: theme.spacing(7),
}; };
const buttonShift = {
'& + button': {
transform: 'translateX(0%)',
},
};
return { return {
enter: css({ enter: css({
...closedStyles, ...closedStyles,
...buttonShift,
}), }),
enterActive: css({ enterActive: css({
...transitionProps, ...transitionProps,
...openStyles, ...openStyles,
...buttonShift,
}), }),
enterDone: css({ enterDone: css({
...openStyles, ...openStyles,
...buttonShift,
}), }),
exit: css({ exit: css({
...openStyles, ...openStyles,
...buttonShift,
}), }),
exitActive: css({ exitActive: css({
...transitionProps, ...transitionProps,
...closedStyles, ...closedStyles,
...buttonShift,
}), }),
}; };
}; };

View File

@ -0,0 +1,41 @@
import React from 'react';
import { IconButton, useTheme2 } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
import classnames from 'classnames';
export interface Props {
className?: string;
isExpanded: boolean;
onClick: () => void;
}
export const NavBarToggle = ({ className, isExpanded, onClick }: Props) => {
const theme = useTheme2();
const styles = getStyles(theme);
return (
<IconButton
name={isExpanded ? 'angle-left' : 'angle-right'}
className={classnames(className, styles.icon)}
size="xl"
onClick={onClick}
/>
);
};
NavBarToggle.displayName = 'NavBarToggle';
const getStyles = (theme: GrafanaTheme2) => ({
icon: css({
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.weak}`,
borderRadius: '50%',
marginRight: 0,
zIndex: theme.zIndex.sidemenu,
[theme.breakpoints.down('md')]: {
display: 'none',
},
}),
});