diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx
index f1abdfc2a68..5c2f458f9c1 100644
--- a/public/app/core/components/AppChrome/AppChrome.tsx
+++ b/public/app/core/components/AppChrome/AppChrome.tsx
@@ -9,7 +9,7 @@ import { useGrafana } from 'app/core/context/GrafanaContext';
import { CommandPalette } from 'app/features/commandPalette/CommandPalette';
import { KioskMode } from 'app/types';
-import { DockedMegaMenu } from './DockedMegaMenu/DockedMegaMenu';
+import { AppChromeMenu } from './AppChromeMenu';
import { MegaMenu } from './MegaMenu/MegaMenu';
import { NavToolbar } from './NavToolbar/NavToolbar';
import { SectionNav } from './SectionNav/SectionNav';
@@ -19,11 +19,10 @@ import { TOP_BAR_LEVEL_HEIGHT } from './types';
export interface Props extends PropsWithChildren<{}> {}
export function AppChrome({ children }: Props) {
- const styles = useStyles2(getStyles);
const { chrome } = useGrafana();
const state = chrome.useState();
-
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
+ const styles = useStyles2(getStyles);
const contentClass = cx({
[styles.content]: true,
@@ -34,7 +33,6 @@ export function AppChrome({ children }: Props) {
// Chromeless routes are without topNav, mega menu, search & command palette
// We check chromeless twice here instead of having a separate path so {children}
// doesn't get re-mounted when chromeless goes from true to false.
-
return (
>
)}
-
+
{state.layout === PageLayoutType.Standard && state.sectionNav && !config.featureToggles.dockedMegaMenu && (
)}
-
{children}
+
+ {children}
+
{!state.chromeless && (
<>
{config.featureToggles.dockedMegaMenu ? (
- chrome.setMegaMenu(false)} />
+
) : (
chrome.setMegaMenu(false)} />
)}
diff --git a/public/app/core/components/AppChrome/DockedMegaMenu/NavBarMenu.tsx b/public/app/core/components/AppChrome/AppChromeMenu.tsx
similarity index 50%
rename from public/app/core/components/AppChrome/DockedMegaMenu/NavBarMenu.tsx
rename to public/app/core/components/AppChrome/AppChromeMenu.tsx
index 6879e4f1cda..e8c0404f366 100644
--- a/public/app/core/components/AppChrome/DockedMegaMenu/NavBarMenu.tsx
+++ b/public/app/core/components/AppChrome/AppChromeMenu.tsx
@@ -2,105 +2,79 @@ 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, { useEffect, useRef, useState } from 'react';
+import React, { useRef } from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
-import { GrafanaTheme2, NavModelItem } from '@grafana/data';
-import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui';
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
+import { KioskMode } from 'app/types';
-import { TOP_BAR_LEVEL_HEIGHT } from '../types';
+import { DockedMegaMenu, MENU_WIDTH } from './DockedMegaMenu/DockedMegaMenu';
+import { TOGGLE_BUTTON_ID } from './NavToolbar/NavToolbar';
+import { TOP_BAR_LEVEL_HEIGHT } from './types';
-import { NavBarMenuItemWrapper } from './NavBarMenuItemWrapper';
+interface Props {}
-const MENU_WIDTH = '350px';
-
-export interface Props {
- activeItem?: NavModelItem;
- navItems: NavModelItem[];
- searchBarHidden?: boolean;
- onClose: () => void;
-}
-
-export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: Props) {
+export function AppChromeMenu({}: Props) {
const theme = useTheme2();
- const styles = getStyles(theme, searchBarHidden);
- const animationSpeed = theme.transitions.duration.shortest;
- const animStyles = getAnimStyles(theme, animationSpeed);
const { chrome } = useGrafana();
const state = chrome.useState();
+ const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
+
const ref = useRef(null);
const backdropRef = useRef(null);
- const { dialogProps } = useDialog({}, ref);
- const [isOpen, setIsOpen] = useState(false);
+ const animationSpeed = theme.transitions.duration.shortest;
+ const animationStyles = useStyles2(getAnimStyles, animationSpeed);
- const onMenuClose = () => setIsOpen(false);
+ const isOpen = state.megaMenuOpen;
+ const onClose = () => chrome.setMegaMenu(false);
const { overlayProps, underlayProps } = useOverlay(
{
isDismissable: true,
isOpen: true,
- onClose: onMenuClose,
+ onClose,
+ shouldCloseOnInteractOutside: (element) => {
+ // don't close when clicking on the menu toggle, let the toggle button handle that
+ // this prevents some nasty flickering when the menu is open and the toggle button is clicked
+ const isMenuToggle = document.getElementById(TOGGLE_BUTTON_ID)?.contains(element);
+ return !isMenuToggle;
+ },
},
ref
);
-
- useEffect(() => {
- if (state.megaMenuOpen) {
- setIsOpen(true);
- }
- }, [state.megaMenuOpen]);
+ const { dialogProps } = useDialog({}, ref);
+ const styles = useStyles2(getStyles, searchBarHidden);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
}
-NavBarMenu.displayName = 'NavBarMenu';
-
const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
const topPosition = (searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2) + 1;
@@ -119,12 +93,11 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
top: topPosition,
},
}),
- container: css({
+ menu: css({
display: 'flex',
bottom: 0,
flexDirection: 'column',
left: 0,
- marginRight: theme.spacing(1.5),
right: 0,
// Needs to below navbar should we change the navbarFixed? add add a new level?
zIndex: theme.zIndex.modal,
@@ -135,32 +108,17 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
flex: '1 1 0',
[theme.breakpoints.up('md')]: {
- borderRight: `1px solid ${theme.colors.border.weak}`,
right: 'unset',
+ borderRight: `1px solid ${theme.colors.border.weak}`,
top: topPosition,
},
}),
- content: css({
- display: 'flex',
- flexDirection: 'column',
- flexGrow: 1,
- minHeight: 0,
- }),
- mobileHeader: css({
- display: 'flex',
- justifyContent: 'space-between',
- padding: theme.spacing(1, 1, 1, 2),
- borderBottom: `1px solid ${theme.colors.border.weak}`,
-
- [theme.breakpoints.up('md')]: {
- display: 'none',
- },
- }),
- itemList: css({
+ wrapper: css({
+ position: 'fixed',
display: 'grid',
- gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
- gridTemplateColumns: `minmax(${MENU_WIDTH}, auto)`,
- minWidth: MENU_WIDTH,
+ gridAutoFlow: 'column',
+ height: '100%',
+ zIndex: theme.zIndex.sidemenu,
}),
};
};
diff --git a/public/app/core/components/AppChrome/DockedMegaMenu/DockedMegaMenu.tsx b/public/app/core/components/AppChrome/DockedMegaMenu/DockedMegaMenu.tsx
index 71fbdf52e3f..30e6e30aa2e 100644
--- a/public/app/core/components/AppChrome/DockedMegaMenu/DockedMegaMenu.tsx
+++ b/public/app/core/components/AppChrome/DockedMegaMenu/DockedMegaMenu.tsx
@@ -1,50 +1,87 @@
import { css } from '@emotion/css';
+import { DOMAttributes } from '@react-types/shared';
import { cloneDeep } from 'lodash';
-import React from 'react';
+import React, { forwardRef } from 'react';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data';
-import { useTheme2 } from '@grafana/ui';
+import { CustomScrollbar, Icon, IconButton, useStyles2 } from '@grafana/ui';
import { useSelector } from 'app/types';
-import { NavBarMenu } from './NavBarMenu';
+import { NavBarMenuItemWrapper } from './NavBarMenuItemWrapper';
import { enrichWithInteractionTracking, getActiveItem } from './utils';
-export interface Props {
+export const MENU_WIDTH = '350px';
+
+export interface Props extends DOMAttributes {
onClose: () => void;
- searchBarHidden?: boolean;
}
-export const DockedMegaMenu = React.memo(({ onClose, searchBarHidden }) => {
- const navBarTree = useSelector((state) => state.navBarTree);
- const theme = useTheme2();
- const styles = getStyles(theme);
- const location = useLocation();
+export const DockedMegaMenu = React.memo(
+ forwardRef(({ onClose, ...restProps }, ref) => {
+ const navBarTree = useSelector((state) => state.navBarTree);
+ const styles = useStyles2(getStyles);
+ const location = useLocation();
- const navTree = cloneDeep(navBarTree);
+ const navTree = cloneDeep(navBarTree);
- // Remove profile + help from tree
- const navItems = navTree
- .filter((item) => item.id !== 'profile' && item.id !== 'help')
- .map((item) => enrichWithInteractionTracking(item, true));
+ // Remove profile + help from tree
+ const navItems = navTree
+ .filter((item) => item.id !== 'profile' && item.id !== 'help')
+ .map((item) => enrichWithInteractionTracking(item, true));
- const activeItem = getActiveItem(navItems, location.pathname);
+ const activeItem = getActiveItem(navItems, location.pathname);
- return (
-
-
-
- );
-});
+ return (
+
+
+
+
+
+
+
+ );
+ })
+);
DockedMegaMenu.displayName = 'DockedMegaMenu';
const getStyles = (theme: GrafanaTheme2) => ({
- menuWrapper: css({
- position: 'fixed',
+ content: css({
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 1,
+ minHeight: 0,
+ }),
+ mobileHeader: css({
+ display: 'flex',
+ justifyContent: 'space-between',
+ padding: theme.spacing(1, 1, 1, 2),
+ borderBottom: `1px solid ${theme.colors.border.weak}`,
+
+ [theme.breakpoints.up('md')]: {
+ display: 'none',
+ },
+ }),
+ itemList: css({
display: 'grid',
- gridAutoFlow: 'column',
- height: '100%',
- zIndex: theme.zIndex.sidemenu,
+ gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
+ gridTemplateColumns: `minmax(${MENU_WIDTH}, auto)`,
+ minWidth: MENU_WIDTH,
}),
});
diff --git a/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx
index c422fa4f4bf..ec8738a9af4 100644
--- a/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx
+++ b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx
@@ -14,6 +14,8 @@ import { TOP_BAR_LEVEL_HEIGHT } from '../types';
import { NavToolbarSeparator } from './NavToolbarSeparator';
+export const TOGGLE_BUTTON_ID = 'mega-menu-toggle';
+
export interface Props {
onToggleSearchBar(): void;
onToggleMegaMenu(): void;
@@ -41,6 +43,7 @@ export function NavToolbar({