diff --git a/src-docs/src/views/breadcrumbs/breadcrumbs_example.js b/src-docs/src/views/breadcrumbs/breadcrumbs_example.js
index 1c037a0c176..1f0c72bfee3 100644
--- a/src-docs/src/views/breadcrumbs/breadcrumbs_example.js
+++ b/src-docs/src/views/breadcrumbs/breadcrumbs_example.js
@@ -3,7 +3,12 @@ import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';
-import { EuiBreadcrumbs, EuiCode, EuiText } from '../../../../src/components';
+import {
+ EuiBreadcrumbs,
+ EuiCode,
+ EuiText,
+ EuiCallOut,
+} from '../../../../src/components';
import { BreadcrumbProps, BreadcrumbResponsiveMaxCount } from './props';
import { breadcrumbsConfig } from './playground';
@@ -22,12 +27,14 @@ import TruncateSingle from './truncate_single';
const truncateSingleSource = require('!!raw-loader!./truncate_single');
import Max from './max';
-import { EuiCallOut } from '../../../../src/components/call_out';
const maxSource = require('!!raw-loader!./max');
import Color from './color';
const colorSource = require('!!raw-loader!./color');
+import PopoverContent from './popover_content';
+const popoverContentSource = require('!!raw-loader!./popover_content');
+
const props = {
EuiBreadcrumbs,
EuiBreadcrumb: BreadcrumbProps,
@@ -244,6 +251,75 @@ export const BreadcrumbsExample = {
],
demo: ,
},
+ {
+ title: 'Popover content',
+ text: (
+ <>
+
+ If you want a breadcrumb that toggles a popover, e.g. for an account
+ switcher, you can use the popoverContent prop for
+ this purpose. EuiBreadcrumbs will automatically
+ handle rendering a popover indicator and popover accessibility best
+ practies for you. We recommend using components such as{' '}
+
+ EuiContextMenu
+ {' '}
+ or{' '}
+
+ EuiListGroup
+ {' '}
+ for displaying popover options, or potentially{' '}
+
+ EuiSelectable
+ {' '}
+ if you have many items that require filtering.
+
+
+ You may also pass popoverProps with almost any
+ prop that{' '}
+
+ EuiPopover
+ {' '}
+ accepts, such as customizing panelPaddingSize or{' '}
+ anchorPosition . However, props that affect
+ popover state such as closePopover ,{' '}
+ isOpen , and button are not
+ accepted as they are controlled automatically by{' '}
+ EuiBreadcrumbs .
+
+
+ Please note that creating a breadcrumb with a popover will
+ nullify any passed href or{' '}
+ onClick behavior, as the only {' '}
+ interaction the breadcrumb should have at that point is the
+ popover toggle.
+ >
+ }
+ >
+ >
+ ),
+ props,
+ demo: ,
+ snippet: ` ,
+ popoverProps: { panelPaddingSize: 's' },
+ }
+ ]}
+/>`,
+ source: [
+ {
+ type: GuideSectionTypes.TSX,
+ code: popoverContentSource,
+ },
+ ],
+ },
{
title: 'Color for emphasis',
text: (
diff --git a/src-docs/src/views/breadcrumbs/popover_content.tsx b/src-docs/src/views/breadcrumbs/popover_content.tsx
new file mode 100644
index 00000000000..e148ec85066
--- /dev/null
+++ b/src-docs/src/views/breadcrumbs/popover_content.tsx
@@ -0,0 +1,112 @@
+import React, { useState } from 'react';
+
+import {
+ EuiBreadcrumbs,
+ EuiBreadcrumb,
+ EuiPopoverTitle,
+ EuiPopoverFooter,
+ EuiContextMenuPanel,
+ EuiSelectable,
+ EuiSelectableOption,
+ EuiAvatar,
+ EuiButton,
+ EuiContextMenuItem,
+} from '../../../../src';
+
+export default () => {
+ const [spaces, setSpaces] = useState([
+ {
+ label: 'My space',
+ checked: 'on',
+ prepend: ,
+ },
+ {
+ label: "Jim's space",
+ prepend: ,
+ },
+ {
+ label: "Pam's space",
+ prepend: ,
+ },
+ {
+ label: "Michael's space",
+ prepend: ,
+ },
+ {
+ label: "Dwight's space",
+ prepend: ,
+ },
+ ]);
+
+ const breadcrumbs: EuiBreadcrumb[] = [
+ {
+ text: 'My deployment',
+ popoverContent: (
+ <>
+ Select a deployment
+ e.preventDefault()}
+ >
+ Go to Deployment A
+ ,
+ e.preventDefault()}
+ >
+ Go to Deployment B
+ ,
+ e.preventDefault()}
+ >
+ Go to all deployments
+ ,
+ ]}
+ />
+ >
+ ),
+ popoverProps: { panelPaddingSize: 'none' },
+ },
+ {
+ text: 'My space',
+ popoverContent: (
+ setSpaces(newOptions)}
+ searchable
+ searchProps={{ placeholder: 'Filter spaces', compressed: true }}
+ aria-label="Space switcher"
+ emptyMessage="No spaces available"
+ noMatchesMessage="No spaces found"
+ >
+ {(list, search) => (
+ <>
+ Select a space
+ {search}
+ {list}
+
+
+ Manage all spaces
+
+
+ >
+ )}
+
+ ),
+ popoverProps: { panelPaddingSize: 'none' },
+ },
+ {
+ text: 'Home',
+ },
+ ];
+
+ return ;
+};
diff --git a/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap b/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap
new file mode 100644
index 00000000000..f8c63f18a81
--- /dev/null
+++ b/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap
@@ -0,0 +1,107 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiBreadcrumbContent renders breadcrumbs with \`popoverContent\` with popovers 1`] = `
+
+
+
+
+
+ Toggles a popover
+
+
+ - Clicking this button will toggle a popover dialog.
+
+
+
+
+
+
+
+
+
+
+
+ You are in a dialog. Press Escape, or tap/click outside the dialog to close.
+
+
+ Hello popover world
+
+
+
+
+
+
+`;
+
+exports[`EuiBreadcrumbContent renders interactive breadcrumbs with href or onClick 1`] = `
+
+`;
+
+exports[`EuiBreadcrumbContent renders plain uninteractive breadcrumb text 1`] = `
+
+
+ Text
+
+
+`;
diff --git a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap
index 72b3380d75d..fce998e8fd9 100644
--- a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap
+++ b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap
@@ -45,15 +45,21 @@ exports[`EuiBreadcrumbs is rendered 1`] = `
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
@@ -144,15 +150,21 @@ exports[`EuiBreadcrumbs is rendered with final item as link 1`] = `
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
@@ -325,15 +337,21 @@ exports[`EuiBreadcrumbs props max renders 1 item 1`] = `
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
@@ -505,15 +523,21 @@ exports[`EuiBreadcrumbs props responsive is rendered 1`] = `
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
@@ -603,15 +627,21 @@ exports[`EuiBreadcrumbs props responsive is rendered as false 1`] = `
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
@@ -676,15 +706,21 @@ exports[`EuiBreadcrumbs props responsive is rendered with custom breakpoints 1`]
class="euiPopover__anchor css-16vtueo-render"
>
- …
+
+ …
+
+
+ >
+ - Clicking this button will toggle a popover dialog.
+
diff --git a/src/components/breadcrumbs/breadcrumb.test.tsx b/src/components/breadcrumbs/breadcrumb.test.tsx
new file mode 100644
index 00000000000..a5e2d4607fe
--- /dev/null
+++ b/src/components/breadcrumbs/breadcrumb.test.tsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { fireEvent } from '@testing-library/react';
+import { render, waitForEuiPopoverOpen } from '../../test/rtl';
+
+import { EuiBreadcrumbContent } from './breadcrumb';
+
+describe('EuiBreadcrumbContent', () => {
+ it('renders plain uninteractive breadcrumb text', () => {
+ const { container, getByText } = render(
+ <>
+
+ >
+ );
+ expect(getByText('Text').nodeName).toEqual('SPAN');
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders interactive breadcrumbs with href or onClick', () => {
+ const { container, getByText } = render(
+ <>
+
+ {}} />
+ >
+ );
+ expect(getByText('Link').nodeName).toEqual('A');
+ expect(getByText('Button').nodeName).toEqual('BUTTON');
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders breadcrumbs with `popoverContent` with popovers', async () => {
+ const { baseElement, getByTestSubject } = render(
+
+ );
+ fireEvent.click(getByTestSubject('popoverToggle'));
+ await waitForEuiPopoverOpen();
+
+ expect(getByTestSubject('popover')).toBeInTheDocument();
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ describe('highlightLastBreadcrumb', () => {
+ it('adds an aria-current attr', () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText('Home')).toHaveAttribute('aria-current', 'page');
+ });
+
+ it('colors both interactive and non-interactive breadcrumbs text-colored', () => {
+ const { getByTestSubject } = render(
+ <>
+
+
+
+
+ >
+ );
+ expect(getByTestSubject('control')).toHaveStyleRule('color', '#646a77');
+ expect(getByTestSubject('text')).toHaveStyleRule('color', '#343741');
+ expect(getByTestSubject('link')).toHaveStyleRule('color', '#343741');
+ expect(getByTestSubject('popover')).toHaveStyleRule('color', '#343741');
+ });
+ });
+});
diff --git a/src/components/breadcrumbs/breadcrumb.tsx b/src/components/breadcrumbs/breadcrumb.tsx
index c54172e336b..2d5d927c4e5 100644
--- a/src/components/breadcrumbs/breadcrumb.tsx
+++ b/src/components/breadcrumbs/breadcrumb.tsx
@@ -22,7 +22,7 @@ import { CommonProps } from '../common';
import { EuiInnerText } from '../inner_text';
import { EuiTextColor } from '../text';
import { EuiLink, EuiLinkColor } from '../link';
-import { EuiPopover } from '../popover';
+import { EuiPopover, EuiPopoverProps } from '../popover';
import { EuiIcon } from '../icon';
import { useEuiI18n } from '../i18n';
@@ -48,13 +48,25 @@ export type EuiBreadcrumbProps = Omit<
*/
truncate?: boolean;
/**
- * Accepts any EuiLink `color` when rendered as one (has `href` or `onClick`)
+ * Accepts any EuiLink `color` when rendered as one (has `href`, `onClick`, or `popoverContent`)
*/
color?: EuiLinkColor;
/**
* Override the existing `aria-current` which defaults to `page` for the last breadcrumb
*/
'aria-current'?: AriaAttributes['aria-current'];
+ /**
+ * Creates a breadcrumb that toggles a popover dialog
+ *
+ * If passed, both `href` and `onClick` will be ignored - the breadcrumb's
+ * click behavior should only trigger a popover.
+ */
+ popoverContent?: ReactNode;
+ /**
+ * Allows customizing the popover if necessary. Accepts any props that
+ * [EuiPopover](/#/layout/popover) accepts, except for props that control state.
+ */
+ popoverProps?: Omit;
};
// Used internally only by the parent EuiBreadcrumbs
@@ -102,6 +114,8 @@ export const EuiBreadcrumbContent: FunctionComponent<
href,
rel, // required by our local href-with-rel eslint rule
onClick,
+ popoverContent,
+ popoverProps,
className,
color,
isFirstBreadcrumb,
@@ -131,45 +145,79 @@ export const EuiBreadcrumbContent: FunctionComponent<
}
}
- const ariaCurrent = highlightLastBreadcrumb ? 'page' : undefined;
+ const isInteractiveBreadcrumb = href || onClick;
+ const linkColor = color || (highlightLastBreadcrumb ? 'text' : 'subdued');
+ const plainTextColor = highlightLastBreadcrumb ? 'default' : 'subdued'; // Does not inherit `color` prop
+ const ariaCurrent = highlightLastBreadcrumb ? ('page' as const) : undefined;
+
+ const isPopoverBreadcrumb = !!popoverContent;
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const popoverAriaLabel = useEuiI18n(
+ 'euiBreadcrumb.popoverAriaLabel',
+ 'Clicking this button will toggle a popover dialog.'
+ );
return (
{(ref, innerText) => {
const title = innerText === '' ? undefined : innerText;
- return !href && !onClick ? (
-
- setIsPopoverOpen(false)}
+ button={
+ setIsPopoverOpen((isOpen) => !isOpen)}
+ {...rest}
+ >
+ {text}{' '}
+
+
+ }
+ >
+ {popoverContent}
+
+ );
+ } else if (isInteractiveBreadcrumb) {
+ return (
+
{text}
-
-
- ) : (
-
- {text}
-
- );
+
+ );
+ } else {
+ return (
+
+
+ {text}
+
+
+ );
+ }
}}
);
@@ -180,8 +228,6 @@ export const EuiBreadcrumbCollapsed: FunctionComponent<_EuiBreadcrumbProps> = ({
isFirstBreadcrumb,
type,
}) => {
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
-
const euiTheme = useEuiTheme();
const styles = euiBreadcrumbStyles(euiTheme);
const cssStyles = [styles.isCollapsed];
@@ -191,31 +237,16 @@ export const EuiBreadcrumbCollapsed: FunctionComponent<_EuiBreadcrumbProps> = ({
'See collapsed breadcrumbs'
);
- const ellipsisButton = (
- setIsPopoverOpen(!isPopoverOpen)}
- truncate={false}
- text={
- <>
- …
- >
- }
- isFirstBreadcrumb={isFirstBreadcrumb}
- type={type}
- />
- );
-
return (
- setIsPopoverOpen(false)}
- >
- {children}
-
+ …}
+ title={ariaLabel}
+ truncate={false}
+ isFirstBreadcrumb={isFirstBreadcrumb}
+ type={type}
+ />
);
};
diff --git a/upcoming_changelogs/7031.md b/upcoming_changelogs/7031.md
new file mode 100644
index 00000000000..b0ff6ee2dcf
--- /dev/null
+++ b/upcoming_changelogs/7031.md
@@ -0,0 +1 @@
+- Updated `EuiBreadcrumbs` to support breadcrumbs that toggle popovers via `popoverContent` and `popoverProps`