PanelChrome: Menu is wrapped in a render prop for full outside control (#60537)

* setup menu as a render prop sent down from PanelStateWrapper to PanelChrome

* let the Dropdown take care of opening the menu in PanelChrome

* menu and leftItems are on the right side of the header together

* add storybook examples with menu

* menu does not need to be a callback because it's opened in a Dropdown anyway

* pass down to getPanelMenu whether or not data is streaming atm

* stop loading data as well as streaming from menu

* override menu's style where needed

* reduce snapshot matching in tests
This commit is contained in:
Polina Boneva
2023-01-12 11:10:09 +02:00
committed by GitHub
parent f85a948214
commit 84eb275c8d
11 changed files with 338 additions and 195 deletions

View File

@@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import React, { FC } from 'react';
import React from 'react';
import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -27,7 +27,7 @@ export interface Props {
data: PanelData;
}
export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, data, alertState, dashboard }) => {
export function PanelHeader({ panel, error, isViewing, isEditing, data, alertState, dashboard }: Props) {
const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
const title = panel.getDisplayTitle();
const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
@@ -81,7 +81,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
</div>
</>
);
};
}
const panelStyles = (theme: GrafanaTheme2) => {
return {

View File

@@ -6,12 +6,17 @@ import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
export interface Props {
items: PanelMenuItem[];
style?: React.CSSProperties;
}
export class PanelHeaderMenu extends PureComponent<Props> {
renderItems = (menu: PanelMenuItem[], isSubMenu = false) => {
return (
<ul className="dropdown-menu dropdown-menu--menu panel-menu" role={isSubMenu ? '' : 'menu'}>
<ul
className="dropdown-menu dropdown-menu--menu panel-menu"
style={this.props.style}
role={isSubMenu ? '' : 'menu'}
>
{menu.map((menuItem, idx: number) => {
return (
<PanelHeaderMenuItem

View File

@@ -1,6 +1,6 @@
import { FC, ReactElement, useEffect, useState } from 'react';
import { ReactElement, useEffect, useState } from 'react';
import { PanelMenuItem } from '@grafana/data';
import { LoadingState, PanelMenuItem } from '@grafana/data';
import { getPanelStateForModel } from 'app/features/panel/state/selectors';
import { useSelector } from 'app/types';
@@ -14,16 +14,17 @@ interface PanelHeaderMenuProviderApi {
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
loadingState?: LoadingState;
children: (props: PanelHeaderMenuProviderApi) => ReactElement;
}
export const PanelHeaderMenuProvider: FC<Props> = ({ panel, dashboard, children }) => {
export function PanelHeaderMenuProvider({ panel, dashboard, loadingState, children }: Props) {
const [items, setItems] = useState<PanelMenuItem[]>([]);
const angularComponent = useSelector((state) => getPanelStateForModel(state, panel)?.angularComponent);
useEffect(() => {
setItems(getPanelMenu(dashboard, panel, angularComponent));
}, [dashboard, panel, angularComponent, setItems]);
setItems(getPanelMenu(dashboard, panel, loadingState, angularComponent));
}, [dashboard, panel, angularComponent, loadingState, setItems]);
return children({ items });
};
}

View File

@@ -11,7 +11,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
children: (props: PanelHeaderMenuTriggerApi) => ReactElement;
}
export const PanelHeaderMenuTrigger = ({ children, ...divProps }: Props) => {
export function PanelHeaderMenuTrigger({ children, ...divProps }: Props) {
const [clickCoordinates, setClickCoordinates] = useState<CartesianCoords2D>({ x: 0, y: 0 });
const [panelMenuOpen, setPanelMenuOpen] = useState<boolean>(false);
@@ -38,7 +38,7 @@ export const PanelHeaderMenuTrigger = ({ children, ...divProps }: Props) => {
{children({ panelMenuOpen, closeMenu: () => setPanelMenuOpen(false) })}
</header>
);
};
}
function isClick(current: CartesianCoords2D, clicked: CartesianCoords2D, deadZone = 3.5): boolean {
// A "deadzone" radius is added so that if the cursor is moved within this radius

View File

@@ -1,4 +1,6 @@
import React, { FC } from 'react';
import React from 'react';
import { LoadingState } from '@grafana/data';
import { DashboardModel, PanelModel } from '../../state';
@@ -8,15 +10,17 @@ import { PanelHeaderMenuProvider } from './PanelHeaderMenuProvider';
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
loadingState?: LoadingState;
onClose: () => void;
style?: React.CSSProperties;
}
export const PanelHeaderMenuWrapper: FC<Props> = ({ panel, dashboard }) => {
export function PanelHeaderMenuWrapper({ style, panel, dashboard, loadingState }: Props) {
return (
<PanelHeaderMenuProvider panel={panel} dashboard={dashboard}>
<PanelHeaderMenuProvider panel={panel} dashboard={dashboard} loadingState={loadingState}>
{({ items }) => {
return <PanelHeaderMenu items={items} />;
return <PanelHeaderMenu style={style} items={items} />;
}}
</PanelHeaderMenuProvider>
);
};
}

View File

@@ -46,6 +46,7 @@ import { DashboardModel, PanelModel } from '../state';
import { loadSnapshotData } from '../utils/loadSnapshotData';
import { PanelHeader } from './PanelHeader/PanelHeader';
import { PanelHeaderMenuWrapper } from './PanelHeader/PanelHeaderMenuWrapper';
import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory';
import { liveTimer } from './liveTimer';
@@ -589,6 +590,21 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
const title = panel.getDisplayTitle();
const padding: PanelPadding = plugin.noPadding ? 'none' : 'md';
let menu;
if (!dashboard.meta.publicDashboardAccessToken) {
menu = (
<div data-testid="panel-dropdown">
<PanelHeaderMenuWrapper
style={{ top: 0 }}
panel={panel}
dashboard={dashboard}
loadingState={data.state}
onClose={() => {}}
/>
</div>
);
}
if (config.featureToggles.newPanelChromeUI) {
return (
<PanelChrome
@@ -596,6 +612,7 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
height={height}
padding={padding}
title={title}
menu={menu}
loadingState={data.state}
status={{
message: errorMessage,