Navigation: implement underlay (#47311)

* Navigation: implement underlay

* abstract out animation duration and add box-shadow

* rename underlay to backdrop, better syntax for composing css
pull/47328/head
Ashley Harrison 3 years ago committed by GitHub
parent 2fe6eca7a0
commit 8ede6161d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 123
      public/app/core/components/NavBar/Next/NavBarMenu.tsx
  2. 52
      public/app/core/components/NavBar/Next/NavBarNext.tsx

@ -1,9 +1,10 @@
import React, { useRef } from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { CollapsableSection, CustomScrollbar, Icon, IconName, useStyles2 } from '@grafana/ui';
import { CollapsableSection, CustomScrollbar, Icon, IconName, useStyles2, useTheme2 } from '@grafana/ui';
import { FocusScope } from '@react-aria/focus';
import { useDialog } from '@react-aria/dialog';
import { useOverlay } from '@react-aria/overlays';
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
import { css, cx } from '@emotion/css';
import { NavBarMenuItem } from './NavBarMenuItem';
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
@ -15,14 +16,18 @@ export interface Props {
activeItem?: NavModelItem;
isOpen: boolean;
navItems: NavModelItem[];
setMenuAnimationInProgress: (isInProgress: boolean) => void;
onClose: () => void;
}
export function NavBarMenu({ activeItem, isOpen, navItems, onClose }: Props) {
const styles = useStyles2(getStyles);
export function NavBarMenu({ activeItem, isOpen, navItems, onClose, setMenuAnimationInProgress }: Props) {
const theme = useTheme2();
const styles = getStyles(theme);
const ANIMATION_DURATION = theme.transitions.duration.standard;
const animStyles = getAnimStyles(theme, ANIMATION_DURATION);
const ref = useRef(null);
const { dialogProps } = useDialog({}, ref);
const { overlayProps } = useOverlay(
const { overlayProps, underlayProps } = useOverlay(
{
isDismissable: true,
isOpen,
@ -32,26 +37,50 @@ export function NavBarMenu({ activeItem, isOpen, navItems, onClose }: Props) {
);
return (
<div data-testid="navbarmenu" className={styles.container}>
<OverlayContainer>
<FocusScope contain restoreFocus autoFocus>
<nav className={styles.content} ref={ref} {...overlayProps} {...dialogProps}>
<NavBarToggle className={styles.menuCollapseIcon} isExpanded={isOpen} onClick={onClose} />
<CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}>
{navItems.map((link) => (
<NavItem link={link} onClose={onClose} activeItem={activeItem} key={link.text} />
))}
</ul>
</CustomScrollbar>
</nav>
<CSSTransition
onEnter={() => setMenuAnimationInProgress(true)}
onExited={() => setMenuAnimationInProgress(false)}
appear={isOpen}
in={isOpen}
classNames={animStyles.overlay}
timeout={ANIMATION_DURATION}
>
<div data-testid="navbarmenu" ref={ref} {...overlayProps} {...dialogProps} className={styles.container}>
<NavBarToggle className={styles.menuCollapseIcon} isExpanded={isOpen} onClick={onClose} />
<nav className={styles.content}>
<CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}>
{navItems.map((link) => (
<NavItem link={link} onClose={onClose} activeItem={activeItem} key={link.text} />
))}
</ul>
</CustomScrollbar>
</nav>
</div>
</CSSTransition>
</FocusScope>
</div>
<CSSTransition appear={isOpen} in={isOpen} classNames={animStyles.backdrop} timeout={ANIMATION_DURATION}>
<div className={styles.backdrop} {...underlayProps} />
</CSSTransition>
</OverlayContainer>
);
}
NavBarMenu.displayName = 'NavBarMenu';
const getStyles = (theme: GrafanaTheme2) => ({
backdrop: css({
backdropFilter: 'blur(1px)',
backgroundColor: theme.components.overlay.background,
bottom: 0,
left: 0,
position: 'fixed',
right: 0,
top: 0,
zIndex: theme.zIndex.modalBackdrop,
}),
container: css({
bottom: 0,
display: 'flex',
@ -60,9 +89,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
whiteSpace: 'nowrap',
paddingTop: theme.spacing(1),
marginRight: theme.spacing(1.5),
overflow: 'hidden',
right: 0,
zIndex: theme.zIndex.sidemenu,
zIndex: theme.zIndex.modal,
position: 'fixed',
top: 0,
boxSizing: 'content-box',
[theme.breakpoints.up('md')]: {
@ -83,9 +112,65 @@ const getStyles = (theme: GrafanaTheme2) => ({
position: 'absolute',
top: '43px',
right: '0px',
transform: `translateX(50%)`,
}),
});
const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => {
const commonTransition = {
transitionProperty: 'width, background-color, opacity',
transitionDuration: `${animationDuration}ms`,
transitionTimingFunction: theme.transitions.easing.easeInOut,
};
const overlayTransition = {
...commonTransition,
transitionProperty: 'width, background-color, box-shadow',
};
const backdropTransition = {
...commonTransition,
transitionProperty: 'opacity',
};
const overlayOpen = {
backgroundColor: theme.colors.background.canvas,
boxShadow: theme.shadows.z3,
width: '300px',
};
const overlayClosed = {
backgroundColor: theme.colors.background.primary,
boxShadow: 'none',
width: theme.spacing(7),
};
const backdropOpen = {
opacity: 1,
};
const backdropClosed = {
opacity: 0,
};
return {
backdrop: {
appear: css(backdropClosed),
appearActive: css(backdropTransition, backdropOpen),
appearDone: css(backdropOpen),
exit: css(backdropOpen),
exitActive: css(backdropTransition, backdropClosed),
},
overlay: {
appear: css(overlayClosed),
appearActive: css(overlayTransition, overlayOpen),
appearDone: css(overlayOpen),
exit: css(overlayOpen),
exitActive: css(overlayTransition, overlayClosed),
},
};
};
function NavItem({
link,
activeItem,

@ -1,10 +1,9 @@
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, IconName, useStyles2, useTheme2 } from '@grafana/ui';
import { Icon, IconName, useTheme2 } from '@grafana/ui';
import { config, locationService } from '@grafana/runtime';
import { getKioskMode } from 'app/core/navigation/kiosk';
import { KioskMode, StoreState } from 'app/types';
@ -41,7 +40,6 @@ 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);
@ -60,6 +58,7 @@ export const NavBarNext = React.memo(() => {
);
const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
const [menuOpen, setMenuOpen] = useState(false);
const [menuAnimationInProgress, setMenuAnimationInProgress] = useState(false);
const [menuIdOpen, setMenuIdOpen] = useState<string | null>(null);
if (kiosk !== KioskMode.Off) {
@ -135,16 +134,17 @@ export const NavBarNext = React.memo(() => {
</NavBarContext.Provider>
</nav>
{showSwitcherModal && <OrgSwitcher onDismiss={toggleSwitcherModal} />}
<div className={styles.menuWrapper}>
<CSSTransition in={menuOpen} classNames={animStyles} timeout={150} unmountOnExit>
{(menuOpen || menuAnimationInProgress) && (
<div className={styles.menuWrapper}>
<NavBarMenu
activeItem={activeItem}
isOpen={menuOpen}
setMenuAnimationInProgress={setMenuAnimationInProgress}
navItems={[homeItem, searchItem, ...coreItems, ...pluginItems, ...configItems]}
onClose={() => setMenuOpen(false)}
/>
</CSSTransition>
</div>
</div>
)}
</div>
);
});
@ -242,41 +242,3 @@ const getStyles = (theme: GrafanaTheme2) => ({
transform: `translateX(50%)`,
}),
});
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),
};
return {
enter: css({
...closedStyles,
}),
enterActive: css({
...transitionProps,
...openStyles,
}),
enterDone: css({
...openStyles,
}),
exit: css({
...openStyles,
}),
exitActive: css({
...transitionProps,
...closedStyles,
}),
};
};

Loading…
Cancel
Save