diff --git a/public/app/core/components/NavBar/Next/NavBarMenu.tsx b/public/app/core/components/NavBar/Next/NavBarMenu.tsx
index 3c7bfe2b442..bad0b3fe435 100644
--- a/public/app/core/components/NavBar/Next/NavBarMenu.tsx
+++ b/public/app/core/components/NavBar/Next/NavBarMenu.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 (
-
+
-
+ setMenuAnimationInProgress(true)}
+ onExited={() => setMenuAnimationInProgress(false)}
+ appear={isOpen}
+ in={isOpen}
+ classNames={animStyles.overlay}
+ timeout={ANIMATION_DURATION}
+ >
+
+
+
+
+
-
+
+
+
+
);
}
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,
diff --git a/public/app/core/components/NavBar/Next/NavBarNext.tsx b/public/app/core/components/NavBar/Next/NavBarNext.tsx
index 73ea07636ef..63cf25fcb27 100644
--- a/public/app/core/components/NavBar/Next/NavBarNext.tsx
+++ b/public/app/core/components/NavBar/Next/NavBarNext.tsx
@@ -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(null);
if (kiosk !== KioskMode.Off) {
@@ -135,16 +134,17 @@ export const NavBarNext = React.memo(() => {
{showSwitcherModal && }
-
-
+ {(menuOpen || menuAnimationInProgress) && (
+
setMenuOpen(false)}
/>
-
-
+
+ )}
);
});
@@ -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,
- }),
- };
-};