PanelHeaderMenu: Use UI/Menu component (#63040)

This commit is contained in:
kay delaney
2023-02-24 04:23:56 +00:00
committed by GitHub
parent e77621649d
commit 36e474d109
14 changed files with 84 additions and 88 deletions

View File

@@ -12,6 +12,7 @@ import { LoadingState, PreferredVisualisationType } from './data';
import { DataFrame, FieldType } from './dataFrame';
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { FieldConfigSource } from './fieldOverrides';
import { IconName } from './icon';
import { OptionEditorConfig } from './options';
import { PluginMeta } from './plugin';
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
@@ -156,7 +157,7 @@ export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = an
export interface PanelMenuItem {
type?: 'submenu' | 'divider';
text: string;
iconClassName?: string;
iconClassName?: IconName;
onClick?: (event: React.MouseEvent<any>) => void;
shortcut?: string;
href?: string;

View File

@@ -30,7 +30,7 @@ export interface MenuItemProps<T = any> {
/** Url of the menu item */
url?: string;
/** Handler for the click behaviour */
onClick?: (event?: React.MouseEvent<HTMLElement>, payload?: T) => void;
onClick?: (event: React.MouseEvent<HTMLElement>, payload?: T) => void;
/** Custom MenuItem styles*/
className?: string;
/** Active */

View File

@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import React, { CSSProperties, ReactElement, useRef } from 'react';
import { css, cx } from '@emotion/css';
import React, { CSSProperties, ReactElement, useEffect, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -9,7 +9,7 @@ import { Icon } from '../Icon/Icon';
import { MenuItemProps } from './MenuItem';
import { useMenuFocus } from './hooks';
import { getPosition } from './utils';
import { isElementOverflowing } from './utils';
/** @internal */
export interface SubMenuProps {
@@ -40,6 +40,13 @@ export const SubMenu: React.FC<SubMenuProps> = React.memo(
close,
});
const [pushLeft, setPushLeft] = useState(false);
useEffect(() => {
if (isOpen && localRef.current) {
setPushLeft(isElementOverflowing(localRef.current));
}
}, [isOpen]);
return (
<>
<div className={styles.iconWrapper} aria-label={selectors.components.Menu.SubMenu.icon}>
@@ -48,7 +55,7 @@ export const SubMenu: React.FC<SubMenuProps> = React.memo(
{isOpen && (
<div
ref={localRef}
className={styles.subMenu(localRef.current)}
className={cx(styles.subMenu, { [styles.pushLeft]: pushLeft })}
aria-label={selectors.components.Menu.SubMenu.container}
style={customStyle}
>
@@ -83,11 +90,15 @@ const getStyles = (theme: GrafanaTheme2) => {
display: inline-block;
border-radius: ${theme.shape.borderRadius()};
`,
subMenu: (element: HTMLElement | null) => css`
pushLeft: css`
right: 100%;
left: unset;
`,
subMenu: css`
position: absolute;
top: 0;
left: 100%;
z-index: ${theme.zIndex.dropdown};
${getPosition(element)}: 100%;
`,
};
};

View File

@@ -1,7 +1,7 @@
import { getPosition } from './utils';
import { isElementOverflowing } from './utils';
describe('utils', () => {
it('getPosition', () => {
it('isElementOverflowing', () => {
const getElement = (right: number, width: number) =>
({
parentElement: {
@@ -12,9 +12,9 @@ describe('utils', () => {
Object.defineProperty(window, 'innerWidth', { value: 1000 });
expect(getPosition(null)).toBe('left');
expect(getPosition(getElement(900, 100))).toBe('right');
expect(getPosition(getElement(800, 100))).toBe('left');
expect(getPosition(getElement(1200, 0))).toBe('left');
expect(isElementOverflowing(null)).toBe(false);
expect(isElementOverflowing(getElement(900, 100))).toBe(true);
expect(isElementOverflowing(getElement(800, 100))).toBe(false);
expect(isElementOverflowing(getElement(1200, 0))).toBe(false);
});
});

View File

@@ -1,23 +1,15 @@
/**
* Returns where the subMenu should be positioned (left or right)
* Returns whether the provided element overflows the viewport bounds
*
* @param element HTMLElement for the subMenu wrapper
* @param element The element we want to know about
*/
export const getPosition = (element: HTMLElement | null) => {
export const isElementOverflowing = (element: HTMLElement | null) => {
if (!element) {
return 'left';
return false;
}
const wrapperPos = element.parentElement!.getBoundingClientRect();
const pos = element.getBoundingClientRect();
if (pos.width === 0) {
return 'left';
}
if (wrapperPos.right + pos.width + 10 > window.innerWidth) {
return 'right';
} else {
return 'left';
}
return pos.width !== 0 && wrapperPos.right + pos.width + 10 > window.innerWidth;
};

View File

@@ -165,6 +165,7 @@ export function PanelChrome({
<PanelMenu
menu={menu}
title={title}
placement="bottom-end"
menuButtonClass={cx(styles.menuItem, dragClassCancel, 'show-on-hover')}
/>
)}