Skip to content

Latest commit

Β 

History

History
690 lines (617 loc) Β· 16.7 KB

File metadata and controls

690 lines (617 loc) Β· 16.7 KB
keywords
EuiContextMenu
EuiContextMenuPanel
EuiContextMenuItem

Context menu

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>
  );
};

Sizes

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>
  );
};

With single panel

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>
  );
};

Displaying custom elements

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>
  );
};

Using panels with mixed items & content

Custom panels

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.

Custom items

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&rsquo;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>
    </>
  );
};

Props

import docgen from '@elastic/eui-docgen/dist/components/context_menu';