diff --git a/src-docs/src/views/breadcrumbs/breadcrumbs_example.js b/src-docs/src/views/breadcrumbs/breadcrumbs_example.js index 93be4bc44343..3a4fe3b65ff3 100644 --- a/src-docs/src/views/breadcrumbs/breadcrumbs_example.js +++ b/src-docs/src/views/breadcrumbs/breadcrumbs_example.js @@ -23,6 +23,10 @@ import Truncate from './truncate'; const truncateSource = require('!!raw-loader!./truncate'); const truncateHtml = renderToHtml(Truncate); +import Max from './max'; +const maxSource = require('!!raw-loader!./max'); +const maxHtml = renderToHtml(Max); + export const BreadcrumbsExample = { title: 'Breadcrumbs', sections: [{ @@ -59,7 +63,7 @@ export const BreadcrumbsExample = { props: { EuiBreadcrumbs }, demo: , }, { - title: 'Truncate', + title: 'Truncate each breadcrumb', source: [{ type: GuideSectionTypes.JS, code: truncateSource, @@ -74,5 +78,21 @@ export const BreadcrumbsExample = { ), props: { EuiBreadcrumbs }, demo: , + }, { + title: 'Limit the number of breadcrumbs', + source: [{ + type: GuideSectionTypes.JS, + code: maxSource, + }, { + type: GuideSectionTypes.HTML, + code: maxHtml, + }], + text: ( +

+ Use the max prop to cull breadcrumbs beyond a certain number. +

+ ), + props: { EuiBreadcrumbs }, + demo: , }], }; diff --git a/src-docs/src/views/breadcrumbs/max.js b/src-docs/src/views/breadcrumbs/max.js new file mode 100644 index 000000000000..ae9529458c29 --- /dev/null +++ b/src-docs/src/views/breadcrumbs/max.js @@ -0,0 +1,34 @@ +import React from 'react'; + +import { + EuiBreadcrumbs, +} from '../../../../src/components'; + +export default () => { + const breadcrumbs = [{ + text: 'Animals', + href: '#', + }, { + text: 'Metazoans', + href: '#', + }, { + text: 'Chordates', + href: '#', + }, { + text: 'Vertebrates', + href: '#', + }, { + text: 'Tetrapods', + href: '#', + }, { + text: 'Reptiles', + href: '#', + }, { + text: 'Boa constrictor', + href: '#', + }, { + text: 'Nebulosa subspecies', + }]; + + return ; +}; diff --git a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.js.snap b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.js.snap index 12c6598184ad..d33aaeebfb98 100644 --- a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.js.snap +++ b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.js.snap @@ -42,6 +42,132 @@ exports[`EuiBreadcrumbs is rendered 1`] = ` `; +exports[`EuiBreadcrumbs props max doesn't break when max exceeds the number of breadcrumbs 1`] = ` +
+ +
+ +
+ +
+ + Edit + +
+`; + +exports[`EuiBreadcrumbs props max renders 1 item 1`] = ` +
+
+ … +
+
+ + Edit + +
+`; + +exports[`EuiBreadcrumbs props max renders 2 items 1`] = ` +
+ +
+
+ … +
+
+ + Edit + +
+`; + +exports[`EuiBreadcrumbs props max renders 3 items 1`] = ` +
+ +
+
+ … +
+
+ +
+ + Edit + +
+`; + exports[`EuiBreadcrumbs props responsive is rendered 1`] = `
+ +
+ +
@@ -76,6 +220,24 @@ exports[`EuiBreadcrumbs props truncate is rendered 1`] = `
+ +
+ +
diff --git a/src/components/breadcrumbs/_breadcrumbs.scss b/src/components/breadcrumbs/_breadcrumbs.scss index 40143f132a3f..f345f8c5d471 100644 --- a/src/components/breadcrumbs/_breadcrumbs.scss +++ b/src/components/breadcrumbs/_breadcrumbs.scss @@ -4,7 +4,6 @@ .euiBreadcrumb { display: inline-block; - position: relative; &:not(.euiBreadcrumb--last) { margin-right: $euiBreadcrumbSpacing; @@ -15,13 +14,17 @@ font-weight: 500; } +.euiBreadcrumb--collapsed { + color: $euiColorLightShade; +} + .euiBreadcrumbSeparator { pointer-events: none; display: inline-block; margin-right: $euiBreadcrumbSpacing; width: 1px; height: $euiSize; - transform: rotate(15deg) translateY(0.2em); + transform: translateY(0.2em) rotate(15deg); background: $euiColorLightShade; } diff --git a/src/components/breadcrumbs/breadcrumbs.js b/src/components/breadcrumbs/breadcrumbs.js index 93bd64fe40d2..b74f08463bde 100644 --- a/src/components/breadcrumbs/breadcrumbs.js +++ b/src/components/breadcrumbs/breadcrumbs.js @@ -4,11 +4,55 @@ import classNames from 'classnames'; import { EuiLink } from '../link'; +const limitBreadcrumbs = (breadcrumbs, max) => { + const breadcrumbsAtStart = []; + const breadcrumbsAtEnd = []; + const limit = Math.min(max, breadcrumbs.length); + + for (let i = 0; i < limit; i++) { + // We'll alternate with displaying breadcrumbs at the end and at the start, but be biased + // towards breadcrumbs the end so that if max is an odd number, we'll have one more + // breadcrumb visible at the end than at the beginning. + const isEven = i % 2 === 0; + + // We're picking breadcrumbs from the front AND the back, so we treat each iteration as a + // half-iteration. + const normalizedIndex = Math.floor(i * 0.5); + const indexOfBreadcrumb = isEven ? breadcrumbs.length - 1 - normalizedIndex : normalizedIndex; + const breadcrumb = breadcrumbs[indexOfBreadcrumb]; + + if (isEven) { + breadcrumbsAtEnd.unshift(breadcrumb); + } else { + breadcrumbsAtStart.push(breadcrumb); + } + } + + if (max < breadcrumbs.length) { + breadcrumbsAtStart.push(); + } + + return [ + ...breadcrumbsAtStart, + ...breadcrumbsAtEnd, + ]; +} + +const EuiBreadcrumbCollapsed = () => ( + +
+ +
+); + +const EuiBreadcrumbSeparator = () =>
; + export const EuiBreadcrumbs = ({ breadcrumbs, className, responsive, truncate, + max, ...rest, }) => { const breadcrumbElements = breadcrumbs.map((breadcrumb, index) => { @@ -51,7 +95,7 @@ export const EuiBreadcrumbs = ({ let separator; if (!isLastBreadcrumb) { - separator =
; + separator = ; } return ( @@ -62,6 +106,8 @@ export const EuiBreadcrumbs = ({ ); }) + const limitedBreadcrumbs = max ? limitBreadcrumbs(breadcrumbElements, max) : breadcrumbElements; + const classes = classNames('euiBreadcrumbs', className, { 'euiBreadcrumbs--truncate': truncate, 'euiBreadcrumbs--responsive': responsive, @@ -69,7 +115,7 @@ export const EuiBreadcrumbs = ({ return (
- {breadcrumbElements} + {limitedBreadcrumbs}
); }; @@ -78,6 +124,7 @@ EuiBreadcrumbs.propTypes = { className: PropTypes.string, responsive: PropTypes.bool, truncate: PropTypes.bool, + max: PropTypes.number, breadcrumbs: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.node.isRequired, href: PropTypes.string, diff --git a/src/components/breadcrumbs/breadcrumbs.test.js b/src/components/breadcrumbs/breadcrumbs.test.js index 0a1999175c04..6f7030817779 100644 --- a/src/components/breadcrumbs/breadcrumbs.test.js +++ b/src/components/breadcrumbs/breadcrumbs.test.js @@ -32,6 +32,10 @@ describe('EuiBreadcrumbs', () => { describe('props', () => { const breadcrumbs = [{ text: 'Animals', + }, { + text: 'Reptiles', + }, { + text: 'Boa constrictor', }, { text: 'Edit', }]; @@ -49,5 +53,27 @@ describe('EuiBreadcrumbs', () => { expect(component).toMatchSnapshot(); }); }); + + describe('max', () => { + test('renders 1 item', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); + + test('renders 2 items', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); + + test('renders 3 items', () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); + + test(`doesn't break when max exceeds the number of breadcrumbs`, () => { + const component = render(); + expect(component).toMatchSnapshot(); + }); + }); }); }); diff --git a/src/components/breadcrumbs/max.js b/src/components/breadcrumbs/max.js new file mode 100644 index 000000000000..e69de29bb2d1