mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana-UI: ContextMenu docs (#28508)
* Add story * Add docs * Get rid of selectThemeVariant * Minor tweaks
This commit is contained in:
parent
60d40fa99b
commit
75ebf3f15c
@ -0,0 +1,31 @@
|
||||
import { Props } from '@storybook/addon-docs/blocks';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { WithContextMenu } from "./WithContextMenu";
|
||||
|
||||
# ContextMenu
|
||||
|
||||
A menu displaying additional options when it's not possible to show them at all times due to a space constraint.
|
||||
|
||||
### Usage
|
||||
|
||||
There are controlled and uncontrolled versions of the component available. With the controlled component (`ContextMenu`) the open/close logic needs to be handled separately. Uncontrolled component (`WithContextMenu`) handles this logic internally.
|
||||
|
||||
#### Controlled component
|
||||
|
||||
```jsx
|
||||
<ContextMenu x={10} y={11} onClose={() => {}} items={[{ label: 'Test', items: [{ label: 'First' }, { label: 'Second' }] }]} />
|
||||
```
|
||||
|
||||
#### Uncontrolled component
|
||||
|
||||
```jsx
|
||||
<WithContextMenu getContextMenuItems={() => [{ label: 'Test', items: [{ label: 'First' }, { label: 'Second' }] }]}>
|
||||
{({ openMenu }) => <IconButton name="info-circle" onClick={openMenu} />}
|
||||
</WithContextMenu>
|
||||
```
|
||||
|
||||
### Props of ContextMenu
|
||||
<Props of={ContextMenu} />
|
||||
|
||||
### Props of WithContextMenu
|
||||
<Props of={WithContextMenu} />
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { WithContextMenu } from './WithContextMenu';
|
||||
import mdx from './ContextMenu.mdx';
|
||||
|
||||
export default {
|
||||
title: 'General/ContextMenu',
|
||||
component: ContextMenu,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const menuItems = [{ label: 'Test', items: [{ label: 'First' }, { label: 'Second' }] }];
|
||||
|
||||
export const Basic = () => {
|
||||
return <ContextMenu x={10} y={11} onClose={() => {}} items={menuItems} />;
|
||||
};
|
||||
|
||||
export const WithState = () => {
|
||||
return (
|
||||
<WithContextMenu getContextMenuItems={() => menuItems}>
|
||||
{({ openMenu }) => <IconButton name="info-circle" onClick={openMenu} />}
|
||||
</WithContextMenu>
|
||||
);
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React, { useContext, useRef, useState, useLayoutEffect } from 'react';
|
||||
import React, { useRef, useState, useLayoutEffect } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { selectThemeVariant, ThemeContext } from '../../index';
|
||||
import { useTheme } from '../../index';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
import { Portal, List } from '../index';
|
||||
@ -22,120 +22,86 @@ export interface ContextMenuGroup {
|
||||
label?: string;
|
||||
items: ContextMenuItem[];
|
||||
}
|
||||
|
||||
export interface ContextMenuProps {
|
||||
/** Starting horizontal position for the menu */
|
||||
x: number;
|
||||
/** Starting vertical position for the menu */
|
||||
y: number;
|
||||
/** Callback for closing the menu */
|
||||
onClose: () => void;
|
||||
/** List of the menu items to display */
|
||||
items?: ContextMenuGroup[];
|
||||
/** A function that returns header element */
|
||||
renderHeader?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
const getContextMenuStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const linkColor = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.dark2,
|
||||
dark: theme.colors.text,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const linkColorHover = selectThemeVariant(
|
||||
{
|
||||
light: theme.colors.link,
|
||||
dark: theme.palette.white,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const wrapperBg = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.gray7,
|
||||
dark: theme.palette.dark2,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const wrapperShadow = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.gray3,
|
||||
dark: theme.palette.black,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const itemColor = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.black,
|
||||
dark: theme.palette.white,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const { white, black, dark1, dark2, dark7, gray1, gray3, gray5, gray7 } = theme.palette;
|
||||
const lightThemeStyles = {
|
||||
linkColor: dark2,
|
||||
linkColorHover: theme.colors.link,
|
||||
wrapperBg: gray7,
|
||||
wrapperShadow: gray3,
|
||||
itemColor: black,
|
||||
groupLabelColor: gray1,
|
||||
itemBgHover: gray5,
|
||||
headerBg: white,
|
||||
headerSeparator: white,
|
||||
};
|
||||
const darkThemeStyles = {
|
||||
linkColor: theme.colors.text,
|
||||
linkColorHover: white,
|
||||
wrapperBg: dark2,
|
||||
wrapperShadow: black,
|
||||
itemColor: white,
|
||||
groupLabelColor: theme.colors.textWeak,
|
||||
itemBgHover: dark7,
|
||||
headerBg: dark1,
|
||||
headerSeparator: dark7,
|
||||
};
|
||||
|
||||
const groupLabelColor = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.gray1,
|
||||
dark: theme.colors.textWeak,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
|
||||
const itemBgHover = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.gray5,
|
||||
dark: theme.palette.dark7,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const headerBg = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.white,
|
||||
dark: theme.palette.dark1,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const headerSeparator = selectThemeVariant(
|
||||
{
|
||||
light: theme.palette.white,
|
||||
dark: theme.palette.dark7,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const styles = theme.isDark ? darkThemeStyles : lightThemeStyles;
|
||||
|
||||
return {
|
||||
header: css`
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid ${headerSeparator};
|
||||
background: ${headerBg};
|
||||
border-bottom: 1px solid ${styles.headerSeparator};
|
||||
background: ${styles.headerBg};
|
||||
margin-bottom: ${theme.spacing.xs};
|
||||
border-radius: ${theme.border.radius.sm} ${theme.border.radius.sm} 0 0;
|
||||
`,
|
||||
wrapper: css`
|
||||
background: ${wrapperBg};
|
||||
background: ${styles.wrapperBg};
|
||||
z-index: 1;
|
||||
box-shadow: 0 2px 5px 0 ${wrapperShadow};
|
||||
box-shadow: 0 2px 5px 0 ${styles.wrapperShadow};
|
||||
min-width: 200px;
|
||||
display: inline-block;
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
`,
|
||||
link: css`
|
||||
color: ${linkColor};
|
||||
color: ${styles.linkColor};
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: ${linkColorHover};
|
||||
color: ${styles.linkColorHover};
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
item: css`
|
||||
background: none;
|
||||
padding: 4px 8px;
|
||||
color: ${itemColor};
|
||||
color: ${styles.itemColor};
|
||||
border-left: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: ${itemBgHover};
|
||||
background: ${styles.itemBgHover};
|
||||
border-image: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
`,
|
||||
groupLabel: css`
|
||||
color: ${groupLabelColor};
|
||||
color: ${styles.groupLabelColor};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
line-height: ${theme.typography.lineHeight.md};
|
||||
padding: ${theme.spacing.xs} ${theme.spacing.sm};
|
||||
@ -149,7 +115,7 @@ const getContextMenuStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
});
|
||||
|
||||
export const ContextMenu: React.FC<ContextMenuProps> = React.memo(({ x, y, onClose, items, renderHeader }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = useTheme();
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const [positionStyles, setPositionStyles] = useState({});
|
||||
|
||||
@ -186,11 +152,7 @@ export const ContextMenu: React.FC<ContextMenuProps> = React.memo(({ x, y, onClo
|
||||
<List
|
||||
items={items || []}
|
||||
renderItem={(item, index) => {
|
||||
return (
|
||||
<>
|
||||
<ContextMenuGroupComponent group={item} onClick={onClose} />
|
||||
</>
|
||||
);
|
||||
return <ContextMenuGroupComponent group={item} onClick={onClose} />;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -209,7 +171,7 @@ interface ContextMenuItemProps {
|
||||
|
||||
const ContextMenuItemComponent: React.FC<ContextMenuItemProps> = React.memo(
|
||||
({ url, icon, label, target, onClick, className }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = useTheme();
|
||||
const styles = getContextMenuStyles(theme);
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
@ -236,7 +198,7 @@ interface ContextMenuGroupProps {
|
||||
}
|
||||
|
||||
const ContextMenuGroupComponent: React.FC<ContextMenuGroupProps> = ({ group, onClick }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = useTheme();
|
||||
const styles = getContextMenuStyles(theme);
|
||||
|
||||
if (group.items.length === 0) {
|
||||
|
@ -2,7 +2,9 @@ import React, { useState } from 'react';
|
||||
import { ContextMenu, ContextMenuGroup } from '../ContextMenu/ContextMenu';
|
||||
|
||||
interface WithContextMenuProps {
|
||||
/** Menu item trigger that accepts openMenu prop */
|
||||
children: (props: { openMenu: React.MouseEventHandler<HTMLElement> }) => JSX.Element;
|
||||
/** A function that returns an array of menu items */
|
||||
getContextMenuItems: () => ContextMenuGroup[];
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,7 @@ export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper';
|
||||
export * from './SingleStatShared/index';
|
||||
export { CallToActionCard } from './CallToActionCard/CallToActionCard';
|
||||
export { ContextMenu, ContextMenuItem, ContextMenuGroup, ContextMenuProps } from './ContextMenu/ContextMenu';
|
||||
export { WithContextMenu } from './ContextMenu/WithContextMenu';
|
||||
export { DataLinksInlineEditor } from './DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
|
||||
export { DataLinkInput } from './DataLinks/DataLinkInput';
|
||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||
|
Loading…
Reference in New Issue
Block a user