mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Fixes closing submenus on scrolling (#48207)
This commit is contained in:
parent
ab144bf05e
commit
cc114e24ca
@ -8,10 +8,11 @@ import React, { ReactElement, useEffect, useRef } from 'react';
|
|||||||
import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data';
|
||||||
import { useTheme2 } from '@grafana/ui';
|
import { useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { NavBarItemMenuItem } from '../NavBarItemMenuItem';
|
|
||||||
import { useNavBarItemMenuContext } from '../context';
|
import { useNavBarItemMenuContext } from '../context';
|
||||||
import { getNavModelItemKey } from '../utils';
|
import { getNavModelItemKey } from '../utils';
|
||||||
|
|
||||||
|
import { NavBarItemMenuItem } from './NavBarItemMenuItem';
|
||||||
|
|
||||||
export interface NavBarItemMenuProps extends SpectrumMenuProps<NavModelItem> {
|
export interface NavBarItemMenuProps extends SpectrumMenuProps<NavModelItem> {
|
||||||
onNavigate: (item: NavModelItem) => void;
|
onNavigate: (item: NavModelItem) => void;
|
||||||
adjustHeightForBorder: boolean;
|
adjustHeightForBorder: boolean;
|
||||||
|
@ -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<NavModelItem>;
|
||||||
|
state: TreeState<NavModelItem>;
|
||||||
|
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<HTMLLIElement>(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 (
|
||||||
|
<>
|
||||||
|
<li {...mergeProps(menuItemProps, focusProps, keyboardProps)} ref={ref} className={styles.menuItem}>
|
||||||
|
{rendered}
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
@ -60,8 +60,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// close the menu when changing submenus
|
// close the menu when changing submenus
|
||||||
// or when the state of the overlay changes (i.e hovering outside)
|
if (menuIdOpen !== item.id) {
|
||||||
if (menuIdOpen !== item.id || !state.isOpen) {
|
|
||||||
state.close();
|
state.close();
|
||||||
setMenuHasFocus(false);
|
setMenuHasFocus(false);
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user