Navigation: Add animation when closing nav menu (#47128)

This commit is contained in:
kay delaney 2022-03-31 13:42:38 +01:00 committed by GitHub
parent ee92af8ebe
commit 54b1d88c44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 88 additions and 53 deletions

View File

@ -4,7 +4,7 @@ import { CollapsableSection, CustomScrollbar, Icon, IconName, useStyles2 } from
import { FocusScope } from '@react-aria/focus';
import { useDialog } from '@react-aria/dialog';
import { useOverlay } from '@react-aria/overlays';
import { css, cx, keyframes } from '@emotion/css';
import { css, cx } from '@emotion/css';
import { NavBarMenuItem } from './NavBarMenuItem';
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
import { isMatchOrChildMatch } from '../utils';
@ -29,9 +29,9 @@ export function NavBarMenu({ activeItem, navItems, onClose }: Props) {
);
return (
<FocusScope contain restoreFocus autoFocus>
<div data-testid="navbarmenu" className={styles.container} ref={ref} {...overlayProps} {...dialogProps}>
<nav className={styles.content}>
<div data-testid="navbarmenu" className={styles.container}>
<FocusScope contain restoreFocus autoFocus>
<nav className={styles.content} ref={ref} {...overlayProps} {...dialogProps}>
<CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}>
{navItems.map((link) => (
@ -40,53 +40,40 @@ export function NavBarMenu({ activeItem, navItems, onClose }: Props) {
</ul>
</CustomScrollbar>
</nav>
</div>
</FocusScope>
</FocusScope>
</div>
);
}
NavBarMenu.displayName = 'NavBarMenu';
const getStyles = (theme: GrafanaTheme2) => {
const fadeIn = keyframes`
from {
background-color: ${theme.colors.background.primary};
width: ${theme.spacing(7)};
}
to {
background-color: ${theme.colors.background.canvas};
width: 300px;
}`;
return {
container: css({
animation: `150ms ease-in 0s 1 normal forwards ${fadeIn}`,
bottom: 0,
display: 'flex',
flexDirection: 'column',
left: 0,
whiteSpace: 'nowrap',
paddingTop: theme.spacing(1),
marginRight: theme.spacing(1.5),
right: 0,
zIndex: theme.zIndex.sidemenu,
top: 0,
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.colors.border.weak}`,
right: 'unset',
},
}),
content: css({
display: 'flex',
flexDirection: 'column',
overflow: 'auto',
}),
itemList: css({
display: 'grid',
gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
}),
};
};
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
bottom: 0,
display: 'flex',
flexDirection: 'column',
left: 0,
whiteSpace: 'nowrap',
paddingTop: theme.spacing(1),
marginRight: theme.spacing(1.5),
right: 0,
zIndex: theme.zIndex.sidemenu,
top: 0,
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.colors.border.weak}`,
right: 'unset',
},
}),
content: css({
display: 'flex',
flexDirection: 'column',
overflow: 'auto',
}),
itemList: css({
display: 'grid',
gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
}),
});
function NavItem({
link,

View File

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';
import CSSTransition from 'react-transition-group/CSSTransition';
import { css, cx } from '@emotion/css';
import { cloneDeep } from 'lodash';
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
import { Icon, IconButton, IconName, useTheme2 } from '@grafana/ui';
import { Icon, IconButton, IconName, useStyles2, useTheme2 } from '@grafana/ui';
import { config, locationService } from '@grafana/runtime';
import { getKioskMode } from 'app/core/navigation/kiosk';
import { KioskMode, StoreState } from 'app/types';
@ -37,6 +38,7 @@ export const NavBarNext = React.memo(() => {
const navBarTree = useSelector((state: StoreState) => state.navBarTree);
const theme = useTheme2();
const styles = getStyles(theme);
const animStyles = useStyles2(getAnimStyles);
const location = useLocation();
const kiosk = getKioskMode();
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
@ -115,16 +117,16 @@ export const NavBarNext = React.memo(() => {
</nav>
{showSwitcherModal && <OrgSwitcher onDismiss={toggleSwitcherModal} />}
<div className={styles.menuWrapper}>
{menuOpen && (
<CSSTransition in={menuOpen} classNames={animStyles} timeout={150} unmountOnExit>
<NavBarMenu
activeItem={activeItem}
navItems={[homeItem, searchItem, ...coreItems, ...pluginItems, ...configItems]}
onClose={() => setMenuOpen(false)}
/>
)}
</CSSTransition>
<IconButton
name={menuOpen ? 'angle-left' : 'angle-right'}
className={cx(styles.menuToggle, { [styles.menuOpen]: menuOpen })}
className={styles.menuToggle}
size="xl"
onClick={() => setMenuOpen(!menuOpen)}
/>
@ -233,7 +235,53 @@ const getStyles = (theme: GrafanaTheme2) => ({
display: 'none',
},
}),
menuOpen: css({
transform: 'translateX(0%)',
}),
});
const getAnimStyles = (theme: GrafanaTheme2) => {
const transitionProps = {
transitionProperty: 'width, background-color',
transitionDuration: '150ms',
transitionTimingFunction: 'ease-in-out',
};
const openStyles = {
backgroundColor: theme.colors.background.canvas,
width: '300px',
};
const closedStyles = {
backgroundColor: theme.colors.background.primary,
width: theme.spacing(7),
};
const buttonShift = {
'& + button': {
transform: 'translateX(0%)',
},
};
return {
enter: css({
...closedStyles,
...buttonShift,
}),
enterActive: css({
...transitionProps,
...openStyles,
...buttonShift,
}),
enterDone: css({
...openStyles,
...buttonShift,
}),
exit: css({
...openStyles,
...buttonShift,
}),
exitActive: css({
...transitionProps,
...closedStyles,
...buttonShift,
}),
};
};