diff --git a/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx b/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx index bcc3b3863f3..a865dac6196 100644 --- a/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx +++ b/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx @@ -8,10 +8,11 @@ import React, { ReactElement, useEffect, useRef } from 'react'; import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; import { useTheme2 } from '@grafana/ui'; -import { NavBarItemMenuItem } from '../NavBarItemMenuItem'; import { useNavBarItemMenuContext } from '../context'; import { getNavModelItemKey } from '../utils'; +import { NavBarItemMenuItem } from './NavBarItemMenuItem'; + export interface NavBarItemMenuProps extends SpectrumMenuProps { onNavigate: (item: NavModelItem) => void; adjustHeightForBorder: boolean; diff --git a/public/app/core/components/NavBar/Next/NavBarItemMenuItem.tsx b/public/app/core/components/NavBar/Next/NavBarItemMenuItem.tsx new file mode 100644 index 00000000000..122b4fb38a3 --- /dev/null +++ b/public/app/core/components/NavBar/Next/NavBarItemMenuItem.tsx @@ -0,0 +1,98 @@ +import { css } from '@emotion/css'; +import { useFocus, useKeyboard } from '@react-aria/interactions'; +import { useMenuItem } from '@react-aria/menu'; +import { mergeProps } from '@react-aria/utils'; +import { TreeState } from '@react-stately/tree'; +import { Node } from '@react-types/shared'; +import React, { ReactElement, useRef, useState } from 'react'; + +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; + +import { useNavBarItemMenuContext, useNavBarContext } from '../context'; + +export interface NavBarItemMenuItemProps { + item: Node; + state: TreeState; + onNavigate: (item: NavModelItem) => void; +} + +export function NavBarItemMenuItem({ item, state, onNavigate }: NavBarItemMenuItemProps): ReactElement { + const { onClose, onLeft } = useNavBarItemMenuContext(); + const { setMenuIdOpen } = useNavBarContext(); + const { key, rendered } = item; + const ref = useRef(null); + const isDisabled = state.disabledKeys.has(key); + + // style to the focused menu item + const [isFocused, setFocused] = useState(false); + const { focusProps } = useFocus({ onFocusChange: setFocused, isDisabled }); + const theme = useTheme2(); + const isSection = item.value.menuItemType === 'section'; + const styles = getStyles(theme, isFocused, isSection); + const onAction = () => { + setMenuIdOpen(undefined); + onNavigate(item.value); + onClose(); + }; + + let { menuItemProps } = useMenuItem( + { + isDisabled, + 'aria-label': item['aria-label'], + key, + closeOnSelect: true, + onClose, + onAction, + }, + state, + ref + ); + + const { keyboardProps } = useKeyboard({ + onKeyDown: (e) => { + if (e.key === 'ArrowLeft') { + onLeft(); + } + e.continuePropagation(); + }, + }); + + return ( + <> +
  • + {rendered} +
  • + + ); +} + +function getStyles(theme: GrafanaTheme2, isFocused: boolean, isSection: boolean) { + let backgroundColor = 'transparent'; + if (isFocused) { + backgroundColor = theme.colors.action.hover; + } else if (isSection) { + backgroundColor = theme.colors.background.secondary; + } + return { + menuItem: css` + background-color: ${backgroundColor}; + color: ${theme.colors.text.primary}; + + &:focus-visible { + background-color: ${theme.colors.action.hover}; + box-shadow: none; + color: ${theme.colors.text.primary}; + outline: 2px solid ${theme.colors.primary.main}; + outline-offset: -2px; + transition: none; + } + `, + upgradeBoxContainer: css` + padding: ${theme.spacing(1)}; + `, + upgradeBox: css` + width: 300px; + `, + }; +} diff --git a/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx b/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx index b2d148439b7..dbd3d26dc98 100644 --- a/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx +++ b/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx @@ -60,8 +60,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE useEffect(() => { // close the menu when changing submenus - // or when the state of the overlay changes (i.e hovering outside) - if (menuIdOpen !== item.id || !state.isOpen) { + if (menuIdOpen !== item.id) { state.close(); setMenuHasFocus(false); } else {