From 94d528bcb5e4762d9cb06c622fe594edb628d6bc Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 14 Mar 2024 11:27:26 -0700 Subject: [PATCH] [PR feedback] Split out new `_breadcrumb_content` file + move shared types into a separate file - the breadcrumb types weren't super married to the actual component usage in any case + move unit tests for content to its own file, add new basic it renders test for other components --- .../_breadcrumb_content.test.tsx.snap | 104 +++++++ .../__snapshots__/breadcrumb.test.tsx.snap | 100 +++---- .../breadcrumbs/_breadcrumb_content.styles.ts | 143 +++++++++ .../breadcrumbs/_breadcrumb_content.test.tsx | 124 ++++++++ .../breadcrumbs/_breadcrumb_content.tsx | 222 ++++++++++++++ .../breadcrumbs/breadcrumb.styles.ts | 136 +-------- .../breadcrumbs/breadcrumb.test.tsx | 128 ++------ src/components/breadcrumbs/breadcrumb.tsx | 273 +----------------- .../breadcrumbs/breadcrumbs.stories.tsx | 3 +- src/components/breadcrumbs/breadcrumbs.tsx | 72 +---- src/components/breadcrumbs/index.ts | 4 +- src/components/breadcrumbs/types.ts | 126 ++++++++ 12 files changed, 808 insertions(+), 627 deletions(-) create mode 100644 src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap create mode 100644 src/components/breadcrumbs/_breadcrumb_content.styles.ts create mode 100644 src/components/breadcrumbs/_breadcrumb_content.test.tsx create mode 100644 src/components/breadcrumbs/_breadcrumb_content.tsx create mode 100644 src/components/breadcrumbs/types.ts diff --git a/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap b/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap new file mode 100644 index 00000000000..002da4ac433 --- /dev/null +++ b/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap @@ -0,0 +1,104 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiBreadcrumbContent breadcrumbs with popovers renders with \`popoverContent\` 1`] = ` + +
+
+ +
+
+
+
+
+ +
+
+ +`; + +exports[`EuiBreadcrumbContent renders interactive breadcrumbs with href or onClick 1`] = ` +
+ + Link + + +
+`; + +exports[`EuiBreadcrumbContent renders plain uninteractive breadcrumb text 1`] = ` + + Text + +`; diff --git a/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap b/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap index 7a767985644..aae72d83cd5 100644 --- a/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +++ b/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap @@ -1,30 +1,46 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiBreadcrumbContent breadcrumbs with popovers renders with \`popoverContent\` 1`] = ` +exports[`EuiBreadcrumb is a light
  • wrapper around the content 1`] = ` +
  • + Hello world +
  • +`; + +exports[`EuiBreadcrumbCollapsed renders a ... breadcrumb with collapsed content in a popover 1`] = `
    -
    - -
    + + + … + + + + - Clicking this button will toggle a popover dialog. + + +
    +
    `; - -exports[`EuiBreadcrumbContent renders interactive breadcrumbs with href or onClick 1`] = ` -
    - - Link - - -
    -`; - -exports[`EuiBreadcrumbContent renders plain uninteractive breadcrumb text 1`] = ` -
    - - Text - -
    -`; diff --git a/src/components/breadcrumbs/_breadcrumb_content.styles.ts b/src/components/breadcrumbs/_breadcrumb_content.styles.ts new file mode 100644 index 00000000000..50626c93609 --- /dev/null +++ b/src/components/breadcrumbs/_breadcrumb_content.styles.ts @@ -0,0 +1,143 @@ +/* + * 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 { css } from '@emotion/react'; +import { UseEuiTheme } from '../../services'; +import { transparentize } from '../../services/color'; +import { + euiFontSize, + euiTextTruncate, + euiFocusRing, + logicalCSS, + logicalBorderRadiusCSS, + mathWithUnits, +} from '../../global_styling'; + +/** + * Styles cast to inner , + )} + /> + ); + + fireEvent.click(getByTestSubject('popoverToggle')); + await waitForEuiPopoverOpen(); + + fireEvent.click(getByTestSubject('popoverClose')); + await waitForEuiPopoverClose(); + }); + }); + + 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_content.tsx b/src/components/breadcrumbs/_breadcrumb_content.tsx new file mode 100644 index 00000000000..9547d6718d9 --- /dev/null +++ b/src/components/breadcrumbs/_breadcrumb_content.tsx @@ -0,0 +1,222 @@ +/* + * 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, { + FunctionComponent, + HTMLAttributes, + useState, + useCallback, + forwardRef, +} from 'react'; +import { ArrayCSSInterpolation } from '@emotion/css'; +import classNames from 'classnames'; + +import { useEuiMemoizedStyles } from '../../services'; +import { EuiInnerText } from '../inner_text'; +import { EuiTextColor } from '../text'; +import { EuiLink } from '../link'; +import { EuiPopover } from '../popover'; +import { EuiIcon } from '../icon'; +import { useEuiI18n } from '../i18n'; + +import type { EuiBreadcrumbProps, _EuiBreadcrumbProps } from './types'; +import { + euiBreadcrumbContentStyles, + euiBreadcrumbPopoverStyles, +} from './_breadcrumb_content.styles'; + +export const EuiBreadcrumbContent: FunctionComponent< + EuiBreadcrumbProps & _EuiBreadcrumbProps +> = ({ + text, + truncate, + type, + href, + rel, // required by our local href-with-rel eslint rule + onClick, + popoverContent, + popoverProps, + className, + color, + isFirstBreadcrumb, + isLastBreadcrumb, + isOnlyBreadcrumb, + highlightLastBreadcrumb, + truncateLastBreadcrumb, + ...rest +}) => { + const classes = classNames('euiBreadcrumb__content', className); + + const styles = useEuiMemoizedStyles(euiBreadcrumbContentStyles); + const cssStyles = [styles.euiBreadcrumb__content, styles[type]]; + if (type === 'application') { + if (isOnlyBreadcrumb) { + cssStyles.push(styles.applicationStyles.onlyChild); + } else if (isFirstBreadcrumb) { + cssStyles.push(styles.applicationStyles.firstChild); + } else if (isLastBreadcrumb) { + cssStyles.push(styles.applicationStyles.lastChild); + } + } + const truncationStyles = [ + truncate && !truncateLastBreadcrumb && styles.isTruncated, + truncateLastBreadcrumb && styles.isTruncatedLast, + ]; + + const isBreadcrumbWithPopover = !!popoverContent; + 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; + + return ( + + {(ref, innerText) => { + const title = innerText === '' ? undefined : innerText; + const baseProps = { + ref, + title, + 'aria-current': ariaCurrent, + className: classes, + css: [...cssStyles, ...truncationStyles], + }; + + if (isBreadcrumbWithPopover) { + const { css: _, ...popoverButtonProps } = baseProps; + return ( + + {text} + + ); + } else if (isInteractiveBreadcrumb) { + return ( + + {text} + + ); + } else { + return ( + + + {text} + + + ); + } + }} + + ); +}; + +type EuiBreadcrumbPopoverProps = HTMLAttributes & + Pick & + Pick<_EuiBreadcrumbProps, 'type' | 'isLastBreadcrumb'> & { + breadcrumbCss: ArrayCSSInterpolation; + truncationCss: ArrayCSSInterpolation; + }; +const EuiBreadcrumbPopover = forwardRef< + HTMLButtonElement, + EuiBreadcrumbPopoverProps +>( + ( + { + popoverContent, + popoverProps, + color, + type, + title, + 'aria-current': ariaCurrent, + className, + isLastBreadcrumb, + breadcrumbCss, + truncationCss, + children, + ...rest + }, + ref + ) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const togglePopover = useCallback( + () => setIsPopoverOpen((isOpen) => !isOpen), + [] + ); + + const popoverAriaLabel = useEuiI18n( + // This component was moved into another file for organization/dev readability, + // but we're keeping the i18n token the same as before for consumer consistency + // eslint-disable-next-line local/i18n + 'euiBreadcrumb.popoverAriaLabel', + 'Clicking this button will toggle a popover dialog.' + ); + + const styles = useEuiMemoizedStyles(euiBreadcrumbPopoverStyles); + const wrapperStyles = [ + styles.popoverWrapper.euiBreadcrumb__popoverWrapper, + !isLastBreadcrumb && styles.popoverWrapper[type], + ]; + const buttonStyles = [ + styles.euiBreadcrumb__popoverButton, + ...breadcrumbCss, + ]; + const truncationStyles = [ + styles.euiBreadcrumb__popoverTruncation, + ...truncationCss, + ]; + + return ( + + {children} + + + } + > + {typeof popoverContent === 'function' + ? popoverContent(closePopover) + : popoverContent} + + ); + } +); +EuiBreadcrumbPopover.displayName = 'EuiBreadcrumbPopover'; diff --git a/src/components/breadcrumbs/breadcrumb.styles.ts b/src/components/breadcrumbs/breadcrumb.styles.ts index 508cc82280e..126c787cffa 100644 --- a/src/components/breadcrumbs/breadcrumb.styles.ts +++ b/src/components/breadcrumbs/breadcrumb.styles.ts @@ -8,18 +8,12 @@ import { css } from '@emotion/react'; import { UseEuiTheme } from '../../services'; -import { transparentize } from '../../services/color'; -import { - euiFontSize, - euiTextTruncate, - euiFocusRing, - logicalCSS, - logicalBorderRadiusCSS, - mathWithUnits, -} from '../../global_styling'; +import { logicalCSS } from '../../global_styling'; +/** + * Styles cast to
  • element + */ export const euiBreadcrumbStyles = (euiThemeContext: UseEuiTheme) => { - // Styles cast to
  • element const { euiTheme } = euiThemeContext; return { euiBreadcrumb: css` @@ -61,125 +55,3 @@ export const euiBreadcrumbStyles = (euiThemeContext: UseEuiTheme) => { `, }; }; - -export const euiBreadcrumbContentStyles = (euiThemeContext: UseEuiTheme) => { - // Styles cast to , , or collapsed - )} - /> - ); - - fireEvent.click(getByTestSubject('popoverToggle')); - await waitForEuiPopoverOpen(); - - fireEvent.click(getByTestSubject('popoverClose')); - await waitForEuiPopoverClose(); - }); - }); - - 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'); - }); + fireEvent.click(getByRole('button')); + waitForEuiPopoverOpen(); + expect(getByText('I render inside the popover')).toBeInTheDocument(); + expect(baseElement).toMatchSnapshot(); }); }); diff --git a/src/components/breadcrumbs/breadcrumb.tsx b/src/components/breadcrumbs/breadcrumb.tsx index 62753093668..80c5c813a21 100644 --- a/src/components/breadcrumbs/breadcrumb.tsx +++ b/src/components/breadcrumbs/breadcrumb.tsx @@ -7,87 +7,24 @@ */ import React, { - PropsWithChildren, FunctionComponent, HTMLAttributes, - AriaAttributes, - MouseEventHandler, - ReactNode, - useState, - useCallback, - forwardRef, + PropsWithChildren, } from 'react'; -import { ArrayCSSInterpolation } from '@emotion/css'; import classNames from 'classnames'; import { useEuiMemoizedStyles } from '../../services'; -import { CommonProps } from '../common'; -import { EuiInnerText } from '../inner_text'; -import { EuiTextColor } from '../text'; -import { EuiLink, EuiLinkColor } from '../link'; -import { EuiPopover, EuiPopoverProps } from '../popover'; -import { EuiIcon } from '../icon'; import { useEuiI18n } from '../i18n'; -import { - euiBreadcrumbStyles, - euiBreadcrumbContentStyles, - euiBreadcrumbPopoverStyles, -} from './breadcrumb.styles'; +import type { EuiBreadcrumbProps, _EuiBreadcrumbProps } from './types'; +import { EuiBreadcrumbContent } from './_breadcrumb_content'; -export type EuiBreadcrumbProps = Omit< - HTMLAttributes, - 'color' | 'aria-current' -> & - CommonProps & { - href?: string; - rel?: string; - onClick?: MouseEventHandler; - /** - * Visible label of the breadcrumb - */ - text: ReactNode; - /** - * Force a max-width on the breadcrumb text - */ - truncate?: boolean; - /** - * 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. Takes any rendered node(s), - * or a render function that will pass callback allowing you to close the - * breadcrumb popover from within your popover content. - * - * If passed, both `href` and `onClick` will be ignored - the breadcrumb's - * click behavior should only trigger a popover. - */ - popoverContent?: ReactNode | ((closePopover: () => void) => 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 -type _EuiBreadcrumbProps = PropsWithChildren & - Pick & { - type: 'page' | 'application'; - isFirstBreadcrumb?: boolean; - isLastBreadcrumb?: boolean; - isOnlyBreadcrumb?: boolean; - highlightLastBreadcrumb?: boolean; - truncateLastBreadcrumb?: boolean; - }; +import { euiBreadcrumbStyles } from './breadcrumb.styles'; export const EuiBreadcrumb: FunctionComponent< - HTMLAttributes & _EuiBreadcrumbProps + HTMLAttributes & + Pick<_EuiBreadcrumbProps, 'type'> & + Pick > = ({ children, className, type, truncate, ...rest }) => { const classes = classNames('euiBreadcrumb', className); @@ -110,199 +47,9 @@ export const EuiBreadcrumb: FunctionComponent< ); }; -export const EuiBreadcrumbContent: FunctionComponent< - EuiBreadcrumbProps & _EuiBreadcrumbProps -> = ({ - text, - truncate, - type, - href, - rel, // required by our local href-with-rel eslint rule - onClick, - popoverContent, - popoverProps, - className, - color, - isFirstBreadcrumb, - isLastBreadcrumb, - isOnlyBreadcrumb, - highlightLastBreadcrumb, - truncateLastBreadcrumb, - ...rest -}) => { - const classes = classNames('euiBreadcrumb__content', className); - - const styles = useEuiMemoizedStyles(euiBreadcrumbContentStyles); - const cssStyles = [styles.euiBreadcrumb__content, styles[type]]; - if (type === 'application') { - if (isOnlyBreadcrumb) { - cssStyles.push(styles.applicationStyles.onlyChild); - } else if (isFirstBreadcrumb) { - cssStyles.push(styles.applicationStyles.firstChild); - } else if (isLastBreadcrumb) { - cssStyles.push(styles.applicationStyles.lastChild); - } - } - const truncationStyles = [ - truncate && !truncateLastBreadcrumb && styles.isTruncated, - truncateLastBreadcrumb && styles.isTruncatedLast, - ]; - - const isBreadcrumbWithPopover = !!popoverContent; - 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; - - return ( - - {(ref, innerText) => { - const title = innerText === '' ? undefined : innerText; - const baseProps = { - ref, - title, - 'aria-current': ariaCurrent, - className: classes, - css: [...cssStyles, ...truncationStyles], - }; - - if (isBreadcrumbWithPopover) { - const { css: _, ...popoverButtonProps } = baseProps; - return ( - - {text} - - ); - } else if (isInteractiveBreadcrumb) { - return ( - - {text} - - ); - } else { - return ( - - - {text} - - - ); - } - }} - - ); -}; - -type EuiBreadcrumbPopoverProps = HTMLAttributes & - Pick & - Pick<_EuiBreadcrumbProps, 'type' | 'isLastBreadcrumb'> & { - breadcrumbCss: ArrayCSSInterpolation; - truncationCss: ArrayCSSInterpolation; - }; -const EuiBreadcrumbPopover = forwardRef< - HTMLButtonElement, - EuiBreadcrumbPopoverProps ->( - ( - { - popoverContent, - popoverProps, - color, - type, - title, - 'aria-current': ariaCurrent, - className, - isLastBreadcrumb, - breadcrumbCss, - truncationCss, - children, - ...rest - }, - ref - ) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const togglePopover = useCallback( - () => setIsPopoverOpen((isOpen) => !isOpen), - [] - ); - - const popoverAriaLabel = useEuiI18n( - 'euiBreadcrumb.popoverAriaLabel', - 'Clicking this button will toggle a popover dialog.' - ); - - const styles = useEuiMemoizedStyles(euiBreadcrumbPopoverStyles); - const wrapperStyles = [ - styles.popoverWrapper.euiBreadcrumb__popoverWrapper, - !isLastBreadcrumb && styles.popoverWrapper[type], - ]; - const buttonStyles = [ - styles.euiBreadcrumb__popoverButton, - ...breadcrumbCss, - ]; - const truncationStyles = [ - styles.euiBreadcrumb__popoverTruncation, - ...truncationCss, - ]; - - return ( - - {children} - - - } - > - {typeof popoverContent === 'function' - ? popoverContent(closePopover) - : popoverContent} - - ); - } -); -EuiBreadcrumbPopover.displayName = 'EuiBreadcrumbPopover'; - -export const EuiBreadcrumbCollapsed: FunctionComponent<_EuiBreadcrumbProps> = ({ - children, - isFirstBreadcrumb, - type, -}) => { +export const EuiBreadcrumbCollapsed: FunctionComponent< + PropsWithChildren & Pick<_EuiBreadcrumbProps, 'type' | 'isFirstBreadcrumb'> +> = ({ children, isFirstBreadcrumb, type }) => { const styles = useEuiMemoizedStyles(euiBreadcrumbStyles); const cssStyles = [styles.isCollapsed]; diff --git a/src/components/breadcrumbs/breadcrumbs.stories.tsx b/src/components/breadcrumbs/breadcrumbs.stories.tsx index 8bab5a82205..e2a7241f11f 100644 --- a/src/components/breadcrumbs/breadcrumbs.stories.tsx +++ b/src/components/breadcrumbs/breadcrumbs.stories.tsx @@ -8,7 +8,8 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { EuiBreadcrumbs, EuiBreadcrumbsProps } from './breadcrumbs'; +import type { EuiBreadcrumbsProps } from './types'; +import { EuiBreadcrumbs } from './breadcrumbs'; const meta: Meta = { title: 'Navigation/EuiBreadcrumbs', diff --git a/src/components/breadcrumbs/breadcrumbs.tsx b/src/components/breadcrumbs/breadcrumbs.tsx index f5775e10191..5eb4f4937a3 100644 --- a/src/components/breadcrumbs/breadcrumbs.tsx +++ b/src/components/breadcrumbs/breadcrumbs.tsx @@ -9,74 +9,20 @@ import React, { FunctionComponent, useMemo } from 'react'; import classNames from 'classnames'; -import { CommonProps, ExclusiveUnion } from '../common'; +import { ExclusiveUnion } from '../common'; import { useEuiI18n } from '../i18n'; -import { - useEuiMemoizedStyles, - EuiBreakpointSize, - useCurrentEuiBreakpoint, -} from '../../services'; - -import { - EuiBreadcrumb, - EuiBreadcrumbContent, - EuiBreadcrumbCollapsed, +import { useEuiMemoizedStyles, useCurrentEuiBreakpoint } from '../../services'; + +import type { + EuiBreadcrumbResponsiveMaxCount, + EuiBreadcrumbsProps, EuiBreadcrumbProps, -} from './breadcrumb'; +} from './types'; +import { EuiBreadcrumb, EuiBreadcrumbCollapsed } from './breadcrumb'; +import { EuiBreadcrumbContent } from './_breadcrumb_content'; import { euiBreadcrumbsListStyles } from './breadcrumbs.styles'; -export type EuiBreadcrumbResponsiveMaxCount = { - /** - * Any of the following keys are allowed: `'xs' | 's' | 'm' | 'l' | 'xl'` - * Omitting a key will display all breadcrumbs at that breakpoint - */ - [key in EuiBreakpointSize]?: number; -}; - -export type EuiBreadcrumbsProps = CommonProps & { - /** - * Hides extra (above the max) breadcrumbs under a collapsed item as the window gets smaller. - * Pass a custom #EuiBreadcrumbResponsiveMaxCount object to change the number of breadcrumbs to show at the particular breakpoints. - * - * Pass `false` to turn this behavior off. - * - * Default: `{ xs: 1, s: 2, m: 4 }` - */ - responsive?: boolean | EuiBreadcrumbResponsiveMaxCount; - - /** - * Forces all breadcrumbs to single line and - * truncates each breadcrumb to a particular width, - * except for the last item - */ - truncate?: boolean; - - /** - * Collapses the inner items past the maximum set here - * into a single ellipses item. - * Omitting or passing a `0` value will show all breadcrumbs. - */ - max?: number | null; - - /** - * The array of individual #EuiBreadcrumb items - */ - breadcrumbs: EuiBreadcrumbProps[]; - - /** - * Determines breadcrumbs appearance, with `page` being the default styling. - * Application breadcrumbs should only be once per page, in (e.g.) EuiHeader - */ - type?: 'page' | 'application'; - - /** - * Whether the last breadcrumb should visually (and accessibly, to screen readers) - * be highlighted as the current page. Defaults to true. - */ - lastBreadcrumbIsCurrentPage?: boolean; -}; - const responsiveDefault: EuiBreadcrumbResponsiveMaxCount = { xs: 1, s: 2, diff --git a/src/components/breadcrumbs/index.ts b/src/components/breadcrumbs/index.ts index 34fd3ae44dd..a90c66c7d51 100644 --- a/src/components/breadcrumbs/index.ts +++ b/src/components/breadcrumbs/index.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -export type { EuiBreadcrumbProps as EuiBreadcrumb } from './breadcrumb'; export type { + EuiBreadcrumbProps as EuiBreadcrumb, EuiBreadcrumbsProps, EuiBreadcrumbResponsiveMaxCount, -} from './breadcrumbs'; +} from './types'; export { EuiBreadcrumbs } from './breadcrumbs'; diff --git a/src/components/breadcrumbs/types.ts b/src/components/breadcrumbs/types.ts new file mode 100644 index 00000000000..21fd76f9a7e --- /dev/null +++ b/src/components/breadcrumbs/types.ts @@ -0,0 +1,126 @@ +/* + * 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 type { + ReactNode, + HTMLAttributes, + AriaAttributes, + MouseEventHandler, +} from 'react'; +import type { EuiBreakpointSize } from '../../services'; +import type { CommonProps } from '../common'; +import type { EuiLinkColor } from '../link'; +import type { EuiPopoverProps } from '../popover'; + +/** + * Consumer facing type exports + */ + +export type EuiBreadcrumbResponsiveMaxCount = { + /** + * Any of the following keys are allowed: `'xs' | 's' | 'm' | 'l' | 'xl'` + * Omitting a key will display all breadcrumbs at that breakpoint + */ + [key in EuiBreakpointSize]?: number; +}; + +export type EuiBreadcrumbsProps = CommonProps & { + /** + * Hides extra (above the max) breadcrumbs under a collapsed item as the window gets smaller. + * Pass a custom #EuiBreadcrumbResponsiveMaxCount object to change the number of breadcrumbs to show at the particular breakpoints. + * + * Pass `false` to turn this behavior off. + * + * Default: `{ xs: 1, s: 2, m: 4 }` + */ + responsive?: boolean | EuiBreadcrumbResponsiveMaxCount; + + /** + * Forces all breadcrumbs to single line and + * truncates each breadcrumb to a particular width, + * except for the last item + */ + truncate?: boolean; + + /** + * Collapses the inner items past the maximum set here + * into a single ellipses item. + * Omitting or passing a `0` value will show all breadcrumbs. + */ + max?: number | null; + + /** + * The array of individual #EuiBreadcrumb items + */ + breadcrumbs: EuiBreadcrumbProps[]; + + /** + * Determines breadcrumbs appearance, with `page` being the default styling. + * Application breadcrumbs should only be once per page, in (e.g.) EuiHeader + */ + type?: 'page' | 'application'; + + /** + * Whether the last breadcrumb should visually (and accessibly, to screen readers) + * be highlighted as the current page. Defaults to true. + */ + lastBreadcrumbIsCurrentPage?: boolean; +}; + +export type EuiBreadcrumbProps = Omit< + HTMLAttributes, + 'color' | 'aria-current' +> & + CommonProps & { + href?: string; + rel?: string; + onClick?: MouseEventHandler; + /** + * Visible label of the breadcrumb + */ + text: ReactNode; + /** + * Force a max-width on the breadcrumb text + */ + truncate?: boolean; + /** + * 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. Takes any rendered node(s), + * or a render function that will pass callback allowing you to close the + * breadcrumb popover from within your popover content. + * + * If passed, both `href` and `onClick` will be ignored - the breadcrumb's + * click behavior should only trigger a popover. + */ + popoverContent?: ReactNode | ((closePopover: () => void) => ReactNode); + /** + * Allows customizing the popover if necessary. Accepts any props that + * [EuiPopover](/#/layout/popover) accepts, except for props that control state. + */ + popoverProps?: Omit; + }; + +/** + * Internal props set by parent EuiBreadcrumbs only + */ + +export type _EuiBreadcrumbProps = { + type: NonNullable; + isFirstBreadcrumb?: boolean; + isLastBreadcrumb?: boolean; + isOnlyBreadcrumb?: boolean; + highlightLastBreadcrumb?: boolean; + truncateLastBreadcrumb?: boolean; +};