diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx
index 08714bb0dd3..773b78d2584 100644
--- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx
+++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx
@@ -4,6 +4,8 @@ import { IconButton } from '../IconButton/IconButton';
import { ContextMenu } from './ContextMenu';
import { WithContextMenu } from './WithContextMenu';
import mdx from './ContextMenu.mdx';
+import { MenuGroup } from '../Menu/MenuGroup';
+import { MenuItem } from '../Menu/MenuItem';
export default {
title: 'General/ContextMenu',
@@ -26,13 +28,23 @@ const menuItems = [
},
];
+const renderMenuItems = () => {
+ return menuItems?.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
+};
+
export const Basic = () => {
- return {}} itemsGroup={menuItems} />;
+ return {}} renderMenuItems={renderMenuItems} />;
};
export const WithState = () => {
return (
- menuItems}>
+
{({ openMenu }) => }
);
diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx
index cc106c22ad0..26b7019e857 100644
--- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx
+++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx
@@ -3,8 +3,6 @@ import { selectors } from '@grafana/e2e-selectors';
import { useClickAway } from 'react-use';
import { Portal } from '../Portal/Portal';
import { Menu } from '../Menu/Menu';
-import { MenuGroup, MenuItemsGroup } from '../Menu/MenuGroup';
-import { MenuItem } from '../Menu/MenuItem';
export interface ContextMenuProps {
/** Starting horizontal position for the menu */
@@ -13,69 +11,57 @@ export interface ContextMenuProps {
y: number;
/** Callback for closing the menu */
onClose?: () => void;
- /** List of the menu items to display */
- itemsGroup?: MenuItemsGroup[];
+ /** RenderProp function that returns menu items to display */
+ renderMenuItems?: () => React.ReactNode;
/** A function that returns header element */
renderHeader?: () => React.ReactNode;
}
-export const ContextMenu: React.FC = React.memo(({ x, y, onClose, itemsGroup, renderHeader }) => {
- const menuRef = useRef(null);
- const [positionStyles, setPositionStyles] = useState({});
+export const ContextMenu: React.FC = React.memo(
+ ({ x, y, onClose, renderMenuItems, renderHeader }) => {
+ const menuRef = useRef(null);
+ const [positionStyles, setPositionStyles] = useState({});
- useLayoutEffect(() => {
- const menuElement = menuRef.current;
- if (menuElement) {
- const rect = menuElement.getBoundingClientRect();
- const OFFSET = 5;
- const collisions = {
- right: window.innerWidth < x + rect.width,
- bottom: window.innerHeight < rect.bottom + rect.height + OFFSET,
- };
+ useLayoutEffect(() => {
+ const menuElement = menuRef.current;
+ if (menuElement) {
+ const rect = menuElement.getBoundingClientRect();
+ const OFFSET = 5;
+ const collisions = {
+ right: window.innerWidth < x + rect.width,
+ bottom: window.innerHeight < rect.bottom + rect.height + OFFSET,
+ };
- setPositionStyles({
- position: 'fixed',
- left: collisions.right ? x - rect.width - OFFSET : x - OFFSET,
- top: collisions.bottom ? y - rect.height - OFFSET : y + OFFSET,
- });
- }
- }, [x, y]);
+ setPositionStyles({
+ position: 'fixed',
+ left: collisions.right ? x - rect.width - OFFSET : x - OFFSET,
+ top: collisions.bottom ? y - rect.height - OFFSET : y + OFFSET,
+ });
+ }
+ }, [x, y]);
- useClickAway(menuRef, () => {
- if (onClose) {
- onClose();
- }
- });
+ useClickAway(menuRef, () => {
+ if (onClose) {
+ onClose();
+ }
+ });
+ const header = renderHeader && renderHeader();
+ const menuItems = renderMenuItems && renderMenuItems();
- const header = renderHeader && renderHeader();
- return (
-
-
-
- );
-});
+ return (
+
+
+
+ );
+ }
+);
ContextMenu.displayName = 'ContextMenu';
diff --git a/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx
index 51a14559805..f7c69446d0b 100644
--- a/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx
+++ b/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx
@@ -1,18 +1,16 @@
import React, { useState } from 'react';
import { ContextMenu } from '../ContextMenu/ContextMenu';
-import { MenuItemsGroup } from '../Menu/MenuGroup';
interface WithContextMenuProps {
/** Menu item trigger that accepts openMenu prop */
children: (props: { openMenu: React.MouseEventHandler }) => JSX.Element;
/** A function that returns an array of menu items */
- getContextMenuItems: () => MenuItemsGroup[];
+ renderMenuItems: () => React.ReactNode;
}
-export const WithContextMenu: React.FC = ({ children, getContextMenuItems }) => {
+export const WithContextMenu: React.FC = ({ children, renderMenuItems }) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
-
return (
<>
{children({
@@ -30,7 +28,7 @@ export const WithContextMenu: React.FC = ({ children, getC
onClose={() => setIsMenuOpen(false)}
x={menuPosition.x}
y={menuPosition.y}
- itemsGroup={getContextMenuItems()}
+ renderMenuItems={renderMenuItems}
/>
)}
>
diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx
index 14a300e92e4..1effb787e7f 100644
--- a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx
+++ b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx
@@ -4,6 +4,8 @@ import { selectors } from '@grafana/e2e-selectors';
import { css } from 'emotion';
import { WithContextMenu } from '../ContextMenu/WithContextMenu';
import { linkModelToContextMenuItems } from '../../utils/dataLinks';
+import { MenuGroup, MenuItemsGroup } from '../Menu/MenuGroup';
+import { MenuItem } from '../Menu/MenuItem';
interface DataLinksContextMenuProps {
children: (props: DataLinksContextMenuApi) => JSX.Element;
@@ -18,8 +20,24 @@ export interface DataLinksContextMenuApi {
export const DataLinksContextMenu: React.FC = ({ children, links, config }) => {
const linksCounter = config.links!.length;
- const getDataLinksContextMenuItems = () => {
- return [{ items: linkModelToContextMenuItems(links), label: 'Data links' }];
+ const itemsGroup: MenuItemsGroup[] = [{ items: linkModelToContextMenuItems(links), label: 'Data links' }];
+ const renderMenuGroupItems = () => {
+ return itemsGroup.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
};
// Use this class name (exposed via render prop) to add context menu indicator to the click target of the visualization
@@ -29,7 +47,7 @@ export const DataLinksContextMenu: React.FC = ({ chil
if (linksCounter > 1) {
return (
-
+
{({ openMenu }) => {
return children({ openMenu, targetClassName });
}}
diff --git a/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx b/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx
index 28aa8b385d6..bce86baa805 100644
--- a/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx
+++ b/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx
@@ -15,12 +15,15 @@ import { HorizontalGroup } from '../Layout/Layout';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
import { SeriesIcon } from '../VizLegend/SeriesIcon';
import { css } from 'emotion';
+import { MenuGroup, MenuGroupProps } from '../Menu/MenuGroup';
+import { MenuItem } from '../Menu/MenuItem';
export type ContextDimensions = { [key in keyof T]: [number, number | undefined] | null };
export type GraphContextMenuProps = ContextMenuProps & {
getContextMenuSource: () => FlotDataPoint | null;
timeZone?: TimeZone;
+ itemsGroup?: MenuGroupProps[];
dimensions?: GraphDimensions;
contextDimensions?: ContextDimensions;
};
@@ -40,7 +43,7 @@ export const GraphContextMenu: React.FC = ({
const itemsToRender = itemsGroup
? itemsGroup.map((group) => ({
...group,
- items: group.items.filter((item) => item.label),
+ items: group.items?.filter((item) => item.label),
}))
: [];
@@ -80,8 +83,26 @@ export const GraphContextMenu: React.FC = ({
/>
);
};
+ const renderMenuGroupItems = () => {
+ return itemsToRender?.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
+ };
- return ;
+ return ;
};
/** @internal */
diff --git a/packages/grafana-ui/src/components/NodeGraph/useContextMenu.tsx b/packages/grafana-ui/src/components/NodeGraph/useContextMenu.tsx
index 6303376c8e2..5bb5f6666ae 100644
--- a/packages/grafana-ui/src/components/NodeGraph/useContextMenu.tsx
+++ b/packages/grafana-ui/src/components/NodeGraph/useContextMenu.tsx
@@ -6,6 +6,8 @@ import { useTheme } from '../../themes/ThemeContext';
import { stylesFactory } from '../../themes/stylesFactory';
import { getEdgeFields, getNodeFields } from './utils';
import { css } from 'emotion';
+import { MenuGroup } from '../Menu/MenuGroup';
+import { MenuItem } from '../Menu/MenuItem';
/**
* Hook that contains state of the context menu, both for edges and nodes and provides appropriate component when
@@ -30,11 +32,26 @@ export function useContextMenu(
if (openedNode) {
const items = getItems(getLinks(nodes, openedNode.node.dataFrameRowIndex));
+ const renderMenuGroupItems = () => {
+ return items?.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
+ };
if (items.length) {
MenuComponent = (
}
- itemsGroup={items}
+ renderMenuItems={renderMenuGroupItems}
onClose={() => setOpenedNode(undefined)}
x={openedNode.event.pageX}
y={openedNode.event.pageY}
@@ -45,11 +62,26 @@ export function useContextMenu(
if (openedEdge) {
const items = getItems(getLinks(edges, openedEdge.edge.dataFrameRowIndex));
+ const renderMenuGroupItems = () => {
+ return items?.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
+ };
if (items.length) {
MenuComponent = (
}
- itemsGroup={items}
+ renderMenuItems={renderMenuGroupItems}
onClose={() => setOpenedEdge(undefined)}
x={openedEdge.event.pageX}
y={openedEdge.event.pageY}
diff --git a/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx
index 8b120a24ca6..f276d2c356f 100644
--- a/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx
+++ b/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx
@@ -6,6 +6,8 @@ import {
IconName,
MenuItemProps,
MenuItemsGroup,
+ MenuGroup,
+ MenuItem,
Portal,
useGraphNGContext,
} from '@grafana/ui';
@@ -31,9 +33,9 @@ interface ContextMenuPluginProps {
export const ContextMenuPlugin: React.FC = ({
data,
+ defaultItems,
onClose,
timeZone,
- defaultItems,
replaceVariables,
}) => {
const [isOpen, setIsOpen] = useState(false);
@@ -105,7 +107,6 @@ export const ContextMenuView: React.FC = ({
if (!xField) {
return null;
}
-
const items = defaultItems ? [...defaultItems] : [];
let renderHeader: () => JSX.Element | null = () => null;
@@ -160,9 +161,28 @@ export const ContextMenuView: React.FC = ({
);
}
+ const renderMenuGroupItems = () => {
+ return items?.map((group, index) => (
+
+ {(group.items || []).map((item) => (
+
+ ))}
+
+ ));
+ };
+
return (