| keywords |
|
|---|
EuiContextMenu is a nested menu system useful for navigating complicated trees. It lives within an EuiPopover which itself can be wrapped around any component (like a button in this example).
import React, { useState } from 'react';
import {
EuiButton,
EuiContextMenu,
EuiFormRow,
EuiIcon,
EuiPopover,
EuiSwitch,
EuiSpacer,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isPopoverOpen, setPopover] = useState(false);
const embeddedCodeSwitchId__1 = useGeneratedHtmlId({
prefix: 'embeddedCodeSwitch',
suffix: 'first',
});
const embeddedCodeSwitchId__2 = useGeneratedHtmlId({
prefix: 'embeddedCodeSwitch',
suffix: 'second',
});
const contextMenuPopoverId = useGeneratedHtmlId({
prefix: 'contextMenuPopover',
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const panels = [
{
id: 0,
title: 'This is a context menu',
items: [
{
name: 'Handle an onClick',
icon: 'magnify',
onClick: closePopover,
},
{
name: 'Go to a link',
icon: 'user',
href: 'http://elastic.co',
target: '_blank',
},
{
name: 'Nest panels',
icon: 'wrench',
panel: 1,
},
{
name: 'Add a tooltip',
icon: 'document',
toolTipContent: 'Optional content for a tooltip',
toolTipProps: {
title: 'Optional tooltip title',
position: 'right',
},
onClick: closePopover,
},
{
name: 'Use an app icon',
icon: 'visualizeApp',
onClick: closePopover,
},
{
name: 'Pass an icon as a component to customize it',
icon: <EuiIcon type="trash" size="m" color="danger" />,
onClick: closePopover,
},
{
name: 'Disabled option',
icon: 'user',
toolTipContent: 'For reasons, this item is disabled',
toolTipProps: { position: 'right' },
disabled: true,
onClick: closePopover,
},
],
},
{
id: 1,
initialFocusedItemIndex: 1,
title: 'Nest panels',
items: [
{
name: 'PDF reports',
icon: 'user',
onClick: closePopover,
},
{
name: 'Embed code',
icon: 'user',
panel: 2,
},
{
name: 'Permalinks',
icon: 'user',
onClick: closePopover,
},
],
},
{
id: 2,
title: 'Embed code',
content: (
<div style={{ padding: 16 }}>
<EuiFormRow label="Generate a public snapshot?" hasChildLabel={false}>
<EuiSwitch
name="switch"
id={embeddedCodeSwitchId__1}
label="Snapshot data"
checked={true}
onChange={() => {}}
/>
</EuiFormRow>
<EuiFormRow
label="Include the following in the embed"
hasChildLabel={false}
>
<EuiSwitch
name="switch"
id={embeddedCodeSwitchId__2}
label="Current time range"
checked={true}
onChange={() => {}}
/>
</EuiFormRow>
<EuiSpacer />
<EuiButton fill>Copy iFrame code</EuiButton>
</div>
),
},
];
const button = (
<EuiButton iconType="chevronSingleDown" iconSide="right" onClick={onButtonClick}>
Click me to load a context menu
</EuiButton>
);
return (
<EuiPopover
id={contextMenuPopoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
);
};EuiContextMenu supports a small and medium size. The default size is medium, m, and should be used for most menus and major actions such as top application menus. Use the smaller size, s, for a more compressed version containing minor actions or repeated menus like in EuiTable pagination.
import React, { useState } from 'react';
import {
EuiButton,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover,
EuiCopy,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isPopoverOpen, setPopover] = useState(false);
const smallContextMenuPopoverId = useGeneratedHtmlId({
prefix: 'smallContextMenuPopover',
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const items = [
<EuiCopy textToCopy="Copied some text!" tooltipProps={{ anchorClassName: "eui-fullWidth" }}>
{(copy) => (
<EuiContextMenuItem key="copy" icon="copy" onClick={copy} size="s">
Copy
</EuiContextMenuItem>
)}
</EuiCopy>,
<EuiContextMenuItem key="edit" icon="pencil" onClick={closePopover}>
Edit
</EuiContextMenuItem>,
<EuiContextMenuItem key="share" icon="share" onClick={closePopover}>
Share
</EuiContextMenuItem>,
];
const button = (
<EuiButton iconType="chevronSingleDown" iconSide="right" onClick={onButtonClick}>
Click to show a single panel
</EuiButton>
);
return (
<EuiPopover
id={smallContextMenuPopoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel size="s" items={items} />
</EuiPopover>
);
};Use EuiContextMenuPanel for simple, non-nested context menus. The below pagination example has no nesting and no title.
:::accessibility Accessibility recommendation
The selected context menu item should receive the aria-current="true" attribute, which tells screen readers which item is selected.
:::
import React, { useState } from 'react';
import {
EuiButtonEmpty,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isPopoverOpen, setPopover] = useState(false);
const [rowSize, setRowSize] = useState(50);
const singleContextMenuPopoverId = useGeneratedHtmlId({
prefix: 'singleContextMenuPopover',
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const isSelectedProps = (size: number) => {
return size === rowSize
? { icon: 'check', 'aria-current': 'true' }
: { icon: 'empty', 'aria-current': undefined };
};
const button = (
<EuiButtonEmpty
size="s"
iconType="chevronSingleDown"
iconSide="right"
onClick={onButtonClick}
>
Rows per page: {rowSize}
</EuiButtonEmpty>
);
const items = [
<EuiContextMenuItem
{...isSelectedProps(10)}
key="10 rows"
onClick={() => {
closePopover();
setRowSize(10);
}}
>
10 rows
</EuiContextMenuItem>,
<EuiContextMenuItem
{...isSelectedProps(20)}
key="20 rows"
onClick={() => {
closePopover();
setRowSize(20);
}}
>
20 rows
</EuiContextMenuItem>,
<EuiContextMenuItem
{...isSelectedProps(50)}
key="50 rows"
onClick={() => {
closePopover();
setRowSize(50);
}}
>
50 rows
</EuiContextMenuItem>,
<EuiContextMenuItem
{...isSelectedProps(100)}
key="100 rows"
onClick={() => {
closePopover();
setRowSize(100);
}}
>
100 rows
</EuiContextMenuItem>,
];
return (
<EuiPopover
id={singleContextMenuPopoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel size="s" items={items} />
</EuiPopover>
);
};If you have custom content to show instead of a list of options, you can pass a React element as a child to EuiContextMenuPanel.
import React, { useState } from 'react';
import {
EuiButton,
EuiContextMenuPanel,
EuiPopover,
EuiSelectable,
EuiSelectableOption,
EuiHorizontalRule,
EuiContextMenuItem,
EuiTitle,
EuiSpacer,
EuiPanel,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isPopoverOpen, setPopover] = useState(false);
const customContextMenuPopoverId = useGeneratedHtmlId({
prefix: 'customContextMenuPopover',
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const button = (
<EuiButton
size="s"
iconType="chevronSingleDown"
iconSide="right"
onClick={onButtonClick}
>
Click to show some content
</EuiButton>
);
const [options, setOptions] = useState<EuiSelectableOption[]>([
{
label: 'APM',
},
{
label: 'filebeat-*',
},
{
label: 'logs-*',
},
{
label: 'metrics-*',
},
]);
return (
<EuiPopover
id={customContextMenuPopoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel>
<EuiContextMenuItem
key="item-1"
icon="indexOpen"
size="s"
onClick={closePopover}
>
Add a field to this data view
</EuiContextMenuItem>
<EuiContextMenuItem
key="item-2"
icon="indexSettings"
size="s"
onClick={closePopover}
>
Manage this data view
</EuiContextMenuItem>
<EuiHorizontalRule margin="none" />
<EuiPanel color="transparent" paddingSize="s">
<EuiTitle size="xxxs">
<h3>Data views</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiSelectable
aria-label="Find a data view"
searchable
searchProps={{
compressed: true,
placeholder: 'Find a data view',
}}
options={options}
onChange={(newOptions) => setOptions(newOptions)}
>
{(list, search) => (
<>
{search}
{list}
</>
)}
</EuiSelectable>
</EuiPanel>
</EuiContextMenuPanel>
</EuiPopover>
);
};Context menu panels can be passed React elements through the content prop instead of items. The panel will display your custom content without modification.
If your panel contents have different widths or you need to ensure that a specific context menu panel has a certain width, add width: [number of pixels] to the panel tree.
You can add separator lines in the items prop if you define an item as {isSeparator: true}. This will pass the rest of the object properties to an EuiHorizontalRule component.
For completely custom rendered items, you can use the {renderItem} property to pass a component or any function that returns a JSX node.
import React, { useState } from 'react';
import {
EuiButton,
EuiContextMenu,
EuiIcon,
EuiPopover,
EuiPopoverFooter,
EuiSpacer,
EuiText,
EuiTabbedContent,
useGeneratedHtmlId,
} from '@elastic/eui';
function flattenPanelTree(tree, array = []) {
array.push(tree);
if (tree.items) {
tree.items.forEach((item) => {
if (item.panel) {
flattenPanelTree(item.panel, array);
item.panel = item.panel.id;
}
});
}
return array;
}
export default () => {
const [isPopoverOpen, setPopover] = useState(false);
const [isDynamicPopoverOpen, setDynamicPopover] = useState(false);
const normalContextMenuPopoverId = useGeneratedHtmlId({
prefix: 'normalContextMenuPopover',
});
const dynamicContextMenuPopoverId = useGeneratedHtmlId({
prefix: 'dynamicContextMenuPopover',
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const onDynamicButtonClick = () => {
setDynamicPopover(!isDynamicPopoverOpen);
};
const closeDynamicPopover = () => {
setDynamicPopover(false);
};
const createPanelTree = (Content) => {
return flattenPanelTree({
id: 0,
title: 'View options',
items: [
{
name: 'Show fullscreen',
icon: <EuiIcon type="magnify" size="m" />,
onClick: closePopover,
},
{
name: 'See more',
icon: 'plusCircle',
panel: {
id: 1,
width: 400,
title: 'See more',
content: <Content />,
},
},
{
isSeparator: true,
key: 'sep',
},
{
renderItem: () => (
<EuiPopoverFooter paddingSize="s">
<EuiButton size="s" style={{ marginInlineStart: 'auto' }}>
I'm a custom item!
</EuiButton>
</EuiPopoverFooter>
),
},
],
});
};
const panels = createPanelTree(() => (
<EuiText style={{ padding: 24 }} textAlign="center">
<p>
<EuiIcon type="faceHappy" size="xxl" />
</p>
<h3>Context panels can contain anything</h3>
<p>
You can stuff just about anything into these panels. Be mindful of size
though. This panel is set to 400px and the height will grow as space
allows.
</p>
</EuiText>
));
const dynamicPanels = createPanelTree(() => (
<EuiTabbedContent
tabs={[
{
id: 'cobalt--id',
name: 'Cobalt',
content: (
<EuiText>
<p>
Cobalt is a chemical element with symbol Co and atomic number 27.
Like nickel, cobalt is found in the Earth’s crust only in
chemically combined form, save for small deposits found in alloys
of natural meteoric iron. The free element, produced by reductive
smelting, is a hard, lustrous, silver-gray metal.
</p>
</EuiText>
),
},
{
id: 'dextrose--id',
name: 'Dextrose',
content: (
<EuiText>
<p>
Intravenous sugar solution, also known as dextrose solution, is a
mixture of dextrose (glucose) and water. It is used to treat low
blood sugar or water loss without electrolyte loss.
</p>
</EuiText>
),
},
{
id: 'hydrogen--id',
name: 'Hydrogen',
prepend: <EuiIcon type="chartHeatmap" />,
content: (
<EuiText>
<p>
Hydrogen is a chemical element with symbol H and atomic number 1.
With a standard atomic weight of 1.008, hydrogen is the lightest
element on the periodic table
</p>
</EuiText>
),
},
]}
/>
));
const button = (
<EuiButton iconType="chevronSingleDown" iconSide="right" onClick={onButtonClick}>
Click me to load mixed content menu
</EuiButton>
);
const dynamicButton = (
<EuiButton
iconType="chevronSingleDown"
iconSide="right"
onClick={onDynamicButtonClick}
>
Click me to load dynamic mixed content menu
</EuiButton>
);
return (
<>
<EuiPopover
id={normalContextMenuPopoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="upLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
<EuiSpacer size="l" />
<EuiPopover
id={dynamicContextMenuPopoverId}
button={dynamicButton}
isOpen={isDynamicPopoverOpen}
closePopover={closeDynamicPopover}
panelPaddingSize="none"
anchorPosition="upLeft"
>
<EuiContextMenu initialPanelId={0} panels={dynamicPanels} />
</EuiPopover>
</>
);
};import docgen from '@elastic/eui-docgen/dist/components/context_menu';