diff --git a/package.json b/package.json
index c14ba5ebdd5..5cd820d1c6a 100644
--- a/package.json
+++ b/package.json
@@ -243,6 +243,8 @@
"@opentelemetry/exporter-collector": "0.23.0",
"@opentelemetry/semantic-conventions": "1.0.0",
"@popperjs/core": "2.5.4",
+ "@react-aria/focus": "3.5.0",
+ "@react-aria/overlays": "3.7.2",
"@reduxjs/toolkit": "1.6.1",
"@sentry/browser": "5.25.0",
"@sentry/types": "5.24.2",
diff --git a/public/app/core/components/NavBar/DropdownChild.tsx b/public/app/core/components/NavBar/DropdownChild.tsx
deleted file mode 100644
index abd9c95d6f2..00000000000
--- a/public/app/core/components/NavBar/DropdownChild.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import { css } from '@emotion/css';
-import { GrafanaTheme2 } from '@grafana/data';
-import { Icon, IconName, Link, useTheme2 } from '@grafana/ui';
-
-export interface Props {
- isDivider?: boolean;
- icon?: IconName;
- onClick?: () => void;
- target?: HTMLAnchorElement['target'];
- text: string;
- url?: string;
-}
-
-const DropdownChild = ({ isDivider = false, icon, onClick, target, text, url }: Props) => {
- const theme = useTheme2();
- const styles = getStyles(theme);
-
- const linkContent = (
-
-
- {icon && }
- {text}
-
- {target === '_blank' && (
-
- )}
-
- );
-
- let element = (
-
- );
- if (url) {
- element =
- !target && url.startsWith('/') ? (
-
- {linkContent}
-
- ) : (
-
- {linkContent}
-
- );
- }
-
- return isDivider ? : {element};
-};
-
-export default DropdownChild;
-
-const getStyles = (theme: GrafanaTheme2) => ({
- element: css`
- background-color: transparent;
- border: none;
- display: flex;
- width: 100%;
- `,
- externalLinkIcon: css`
- color: ${theme.colors.text.secondary};
- margin-left: ${theme.spacing(1)};
- `,
- icon: css`
- margin-right: ${theme.spacing(1)};
- `,
- linkContent: css`
- display: flex;
- flex: 1;
- flex-direction: row;
- justify-content: space-between;
- `,
-});
diff --git a/public/app/core/components/NavBar/NavBar.tsx b/public/app/core/components/NavBar/NavBar.tsx
index 4dbfd54cf54..c23ab43fa55 100644
--- a/public/app/core/components/NavBar/NavBar.tsx
+++ b/public/app/core/components/NavBar/NavBar.tsx
@@ -1,20 +1,31 @@
-import React, { FC, useCallback, useState } from 'react';
+import React, { FC, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { css, cx } from '@emotion/css';
import { cloneDeep } from 'lodash';
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
import { Icon, IconName, useTheme2 } from '@grafana/ui';
import { locationService } from '@grafana/runtime';
-import appEvents from '../../app_events';
import { Branding } from 'app/core/components/Branding/Branding';
import config from 'app/core/config';
-import { CoreEvents, KioskMode } from 'app/types';
-import { enrichConfigItems, isLinkActive, isSearchActive } from './utils';
+import { KioskMode } from 'app/types';
+import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from './utils';
import { OrgSwitcher } from '../OrgSwitcher';
import NavBarItem from './NavBarItem';
+import { NavBarSection } from './NavBarSection';
+import { NavBarMenu } from './NavBarMenu';
const homeUrl = config.appSubUrl || '/';
+const onOpenSearch = () => {
+ locationService.partial({ search: 'open' });
+};
+
+const searchItem: NavModelItem = {
+ id: SEARCH_ITEM_ID,
+ onClick: onOpenSearch,
+ text: 'Search dashboards',
+};
+
export const NavBar: FC = React.memo(() => {
const theme = useTheme2();
const styles = getStyles(theme);
@@ -32,78 +43,79 @@ export const NavBar: FC = React.memo(() => {
location,
toggleSwitcherModal
);
- const activeItemId = isSearchActive(location)
- ? 'search'
- : navTree.find((item) => isLinkActive(location.pathname, item))?.id;
+ const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
- const toggleNavBarSmallBreakpoint = useCallback(() => {
- appEvents.emit(CoreEvents.toggleSidemenuMobile);
- }, []);
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
if (kiosk !== null) {
return null;
}
- const onOpenSearch = () => {
- locationService.partial({ search: 'open' });
- };
-
return (