Skip to content

Commit feaa037

Browse files
authored
Menu: Improvements to menu component (grafana#52686)
1 parent da5bc5b commit feaa037

File tree

7 files changed

+210
-112
lines changed

7 files changed

+210
-112
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Meta, Props } from '@storybook/addon-docs/blocks';
2+
import { Overlay } from 'ol';
3+
4+
import { Menu } from './Menu';
5+
6+
<Meta title="MDX|Menu" component={Menu} />
7+
8+
# Menu
9+
10+
A simple menu component.
11+
12+
### When to use
13+
14+
When you need to display a list of actions or navigation options in a dropdown.
15+
16+
### Usage
17+
18+
```tsx
19+
import { Dropdown, Menu, Button } from '@grafana/ui';
20+
const menu = (
21+
<Menu>
22+
<Menu.Item label="Google" />
23+
<Menu.Divider />
24+
<Menu.Item label="Delete" icon="trash-alt" destructive />
25+
</Menu>
26+
);
27+
return (
28+
<Dropdown overlay={menu}>
29+
<Button icon="bars" />
30+
</Dropdown>
31+
);
32+
```
33+
34+
<Props of={Menu} />

packages/grafana-ui/src/components/Menu/Menu.story.internal.tsx

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { ComponentMeta } from '@storybook/react';
2+
import React from 'react';
3+
4+
import { GraphContextMenuHeader } from '..';
5+
import { StoryExample } from '../../utils/storybook/StoryExample';
6+
import { VerticalGroup } from '../Layout/Layout';
7+
8+
import { Menu } from './Menu';
9+
import mdx from './Menu.mdx';
10+
11+
const meta: ComponentMeta<typeof Menu> = {
12+
title: 'General/Menu',
13+
component: Menu,
14+
argTypes: {},
15+
parameters: {
16+
docs: {
17+
page: mdx,
18+
},
19+
knobs: {
20+
disabled: true,
21+
},
22+
controls: {
23+
disabled: true,
24+
},
25+
actions: {
26+
disabled: true,
27+
},
28+
},
29+
};
30+
31+
export function Examples() {
32+
return (
33+
<VerticalGroup>
34+
<StoryExample name="Plain">
35+
<Menu>
36+
<Menu.Item label="Google" />
37+
<Menu.Item label="Filter" />
38+
<Menu.Item label="Active" active />
39+
<Menu.Item label="I am a link" url="http://google.com" target="_blank" />
40+
<Menu.Item label="With destructive prop set" destructive />
41+
</Menu>
42+
</StoryExample>
43+
<StoryExample name="With icons and a divider">
44+
<Menu>
45+
<Menu.Item label="Google" icon="search-plus" />
46+
<Menu.Item label="Filter" icon="filter" />
47+
<Menu.Item label="History" icon="history" />
48+
<Menu.Divider />
49+
<Menu.Item label="With destructive prop set" icon="trash-alt" destructive />
50+
</Menu>
51+
</StoryExample>
52+
<StoryExample name="With header & groups">
53+
<Menu
54+
header={
55+
<GraphContextMenuHeader
56+
timestamp="2020-11-25 19:04:25"
57+
seriesColor="#00ff00"
58+
displayName="A-series"
59+
displayValue={{
60+
text: '128',
61+
suffix: 'km/h',
62+
}}
63+
/>
64+
}
65+
ariaLabel="Menu header"
66+
>
67+
<Menu.Group label="Group 1">
68+
<Menu.Item label="item1" icon="history" />
69+
<Menu.Item label="item2" icon="filter" />
70+
</Menu.Group>
71+
<Menu.Group label="Group 2">
72+
<Menu.Item label="item1" icon="history" />
73+
</Menu.Group>
74+
</Menu>
75+
</StoryExample>
76+
<StoryExample name="With submenu">
77+
<Menu>
78+
<Menu.Item label="item1" icon="history" />
79+
<Menu.Item
80+
label="item2"
81+
icon="apps"
82+
childItems={[
83+
<Menu.Item key="subitem1" label="subitem1" icon="history" />,
84+
<Menu.Item key="subitem2" label="subitem2" icon="apps" />,
85+
<Menu.Item
86+
key="subitem3"
87+
label="subitem3"
88+
icon="search-plus"
89+
childItems={[
90+
<Menu.Item key="subitem1" label="subitem1" icon="history" />,
91+
<Menu.Item key="subitem2" label="subitem2" icon="apps" />,
92+
<Menu.Item key="subitem3" label="subitem3" icon="search-plus" />,
93+
]}
94+
/>,
95+
]}
96+
/>
97+
<Menu.Item label="item3" icon="filter" />
98+
</Menu>
99+
</StoryExample>
100+
</VerticalGroup>
101+
);
102+
}
103+
104+
export default meta;

packages/grafana-ui/src/components/Menu/Menu.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { GrafanaTheme2 } from '@grafana/data';
55

66
import { useStyles2 } from '../../themes';
77

8+
import { MenuDivider } from './MenuDivider';
9+
import { MenuGroup } from './MenuGroup';
10+
import { MenuItem } from './MenuItem';
811
import { useMenuFocus } from './hooks';
912

10-
/** @internal */
1113
export interface MenuProps extends React.HTMLAttributes<HTMLDivElement> {
1214
/** React element rendered at the top of the menu */
1315
header?: React.ReactNode;
@@ -18,8 +20,7 @@ export interface MenuProps extends React.HTMLAttributes<HTMLDivElement> {
1820
onKeyDown?: React.KeyboardEventHandler;
1921
}
2022

21-
/** @internal */
22-
export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
23+
const MenuComp = React.forwardRef<HTMLDivElement, MenuProps>(
2324
({ header, children, ariaLabel, onOpen, onClose, onKeyDown, ...otherProps }, forwardedRef) => {
2425
const styles = useStyles2(getStyles);
2526

@@ -44,9 +45,15 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
4445
);
4546
}
4647
);
47-
Menu.displayName = 'Menu';
4848

49-
/** @internal */
49+
MenuComp.displayName = 'Menu';
50+
51+
export const Menu = Object.assign(MenuComp, {
52+
Item: MenuItem,
53+
Divider: MenuDivider,
54+
Group: MenuGroup,
55+
});
56+
5057
const getStyles = (theme: GrafanaTheme2) => {
5158
return {
5259
header: css`
@@ -58,6 +65,7 @@ const getStyles = (theme: GrafanaTheme2) => {
5865
box-shadow: ${theme.shadows.z3};
5966
display: inline-block;
6067
border-radius: ${theme.shape.borderRadius()};
68+
padding: ${theme.spacing(0.5, 0)};
6169
`,
6270
};
6371
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { css } from '@emotion/css';
2+
import React from 'react';
3+
4+
import { GrafanaTheme2 } from '@grafana/data';
5+
6+
import { useStyles2 } from '../../themes';
7+
8+
export function MenuDivider() {
9+
const styles = useStyles2(getStyles);
10+
return <div className={styles.divider} />;
11+
}
12+
13+
const getStyles = (theme: GrafanaTheme2) => {
14+
return {
15+
divider: css({
16+
height: 1,
17+
backgroundColor: theme.colors.border.weak,
18+
margin: theme.spacing(0.5, 0),
19+
}),
20+
};
21+
};

0 commit comments

Comments
 (0)