From 6552f8c8a8cad52b4fe2936542e412cae042cb5c Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 25 Feb 2020 17:09:46 -0500 Subject: [PATCH 1/6] [EuiHeader] Added `sections` and `position` props --- .../header/__snapshots__/header.test.tsx.snap | 38 +++++++++- src/components/header/_header.scss | 13 ++++ src/components/header/header.test.tsx | 14 ++++ src/components/header/header.tsx | 76 +++++++++++++++++-- .../header_section/_header_section_item.scss | 2 + .../header_section/header_section_item.tsx | 14 ++-- 6 files changed, 142 insertions(+), 15 deletions(-) diff --git a/src/components/header/__snapshots__/header.test.tsx.snap b/src/components/header/__snapshots__/header.test.tsx.snap index 2ac2a381dee..08f23c4b9cf 100644 --- a/src/components/header/__snapshots__/header.test.tsx.snap +++ b/src/components/header/__snapshots__/header.test.tsx.snap @@ -3,17 +3,51 @@ exports[`EuiHeader is rendered 1`] = `
`; exports[`EuiHeader renders children 1`] = `
Hello!
`; + +exports[`EuiHeader renders sections 1`] = ` +
+
+
+ Left +
+
+
+
+ Center +
+
+
+
+ Right +
+
+
+`; diff --git a/src/components/header/_header.scss b/src/components/header/_header.scss index ca9c05b1145..389fb60f949 100644 --- a/src/components/header/_header.scss +++ b/src/components/header/_header.scss @@ -6,6 +6,19 @@ position: relative; z-index: $euiZHeader; // ensure the shadow shows above content display: flex; + justify-content: space-between; background: $euiHeaderBackgroundColor; border-bottom: $euiBorderThin; + + &--fixed { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: $euiZLevel7; + } +} + +.euiBody--headerIsFixed { + padding-top: $euiHeaderChildSize + $euiSizeS; // Extra padding to accound for the shadow } diff --git a/src/components/header/header.test.tsx b/src/components/header/header.test.tsx index 5a36ac03da4..d1ef842e7a8 100644 --- a/src/components/header/header.test.tsx +++ b/src/components/header/header.test.tsx @@ -20,4 +20,18 @@ describe('EuiHeader', () => { expect(component).toMatchSnapshot(); }); + + test('renders sections', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 1c59a263e1d..68f063c19e7 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,20 +1,86 @@ -import React, { FunctionComponent, HTMLAttributes } from 'react'; +import React, { FunctionComponent, HTMLAttributes, useEffect } from 'react'; import classNames from 'classnames'; - import { CommonProps } from '../common'; -export type EuiHeaderProps = CommonProps & HTMLAttributes; +import { EuiHeaderSectionItem, EuiHeaderSection } from './header_section'; +import { EuiHeaderSectionItemProps } from './header_section/header_section_item'; + +export interface EuiHeaderSectionsProp { + left?: EuiHeaderSectionItemProps[]; + center?: EuiHeaderSectionItemProps[]; + right?: EuiHeaderSectionItemProps[]; +} + +function createHeaderSection(sections: EuiHeaderSectionItemProps[]) { + return sections.map((section, index) => { + return ; + }); +} + +export type EuiHeaderProps = CommonProps & + HTMLAttributes & { + /** + * Takes an object with `left`, `right`, and `center` as arrays of #EuiHeaderSectionItem props. + * Wraps the each key's contents in a #EuiHeaderSection and positions it according to the key's name. + * This prop disregards the prop `children` if both are passed. + */ + sections?: EuiHeaderSectionsProp; + /** + * Helper that positions the header against the window body and + * adds the correct amount of top padding to the window when in `fixed` mode + */ + position?: 'static' | 'fixed'; + }; export const EuiHeader: FunctionComponent = ({ children, className, + sections, + position = 'static', ...rest }) => { - const classes = classNames('euiHeader', className); + const classes = classNames( + 'euiHeader', + `euiHeader--${position} ${className}`, + className + ); + + useEffect(() => { + if (position === 'fixed') { + document.body.classList.add('euiBody--headerIsFixed'); + } + return () => { + document.body.classList.remove('euiBody--headerIsFixed'); + }; + }, [position]); + + let contents; + + if (sections) { + contents = [ + sections.left && ( + + {createHeaderSection(sections.left)} + + ), + sections.center && ( + + {createHeaderSection(sections.center)} + + ), + sections.right && ( + + {createHeaderSection(sections.right)} + + ), + ]; + } else { + contents = children; + } return (
- {children} + {contents}
); }; diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index 098b6a98667..a6ea23c1710 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -2,6 +2,8 @@ .euiHeaderSectionItem { position: relative; + display: flex; + align-items: center; &:hover { background: $euiColorLightestShade; diff --git a/src/components/header/header_section/header_section_item.tsx b/src/components/header/header_section/header_section_item.tsx index 4bcdff3937d..2989c03c321 100644 --- a/src/components/header/header_section/header_section_item.tsx +++ b/src/components/header/header_section/header_section_item.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, ReactNode } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; @@ -11,16 +11,14 @@ const borderToClassNameMap: { [border in Border]: string | undefined } = { none: undefined, }; -type Props = CommonProps & { +export type EuiHeaderSectionItemProps = CommonProps & { border?: Border; + children?: ReactNode; }; -export const EuiHeaderSectionItem: FunctionComponent = ({ - border = 'left', - children, - className, - ...rest -}) => { +export const EuiHeaderSectionItem: FunctionComponent< + EuiHeaderSectionItemProps +> = ({ border = 'left', children, className, ...rest }) => { const classes = classNames( 'euiHeaderSectionItem', borderToClassNameMap[border], From 26808f8a6a6a1f408f2efe1af8da36932f41f91a Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 25 Feb 2020 17:47:29 -0500 Subject: [PATCH 2/6] Added an example of using `sections` --- src-docs/src/views/header/header_example.js | 66 ++++++++++- src-docs/src/views/header/header_sections.js | 104 ++++++++++++++++++ .../header/__snapshots__/header.test.tsx.snap | 34 ++++++ src/components/header/_header_logo.scss | 4 + src/components/header/header.test.tsx | 35 ++++++ src/components/header/header.tsx | 8 +- .../header_section/_header_section_item.scss | 8 +- .../list_group_item.test.tsx.snap | 2 +- .../list_group/list_group_item.test.tsx | 2 +- 9 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 src-docs/src/views/header/header_sections.js diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 32e8ad7eca8..5dc0dc8bdb7 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -21,6 +21,10 @@ import Header from './header'; const headerSource = require('!!raw-loader!./header'); const headerHtml = renderToHtml(Header); +import HeaderSections from './header_sections'; +const headerSectionsSource = require('!!raw-loader!./header_sections'); +const headerSectionsHtml = renderToHtml(HeaderSections); + import HeaderAlert from './header_alert'; const headerAlertSource = require('!!raw-loader!./header_alert'); const headerAlertHtml = renderToHtml(HeaderAlert); @@ -45,10 +49,28 @@ const headerSnippet = ` `; +const headerSectionsSnippet = ``; + const headerLinksSnippet = ` @@ -78,7 +100,11 @@ export const HeaderExample = { code: headerHtml, }, ], - text:

The header is made up of several individual components.

, + text: ( +

+ The header is made up of many individual components. +

+ ), props: { EuiHeader, EuiHeaderBreadcrumbs, @@ -90,6 +116,42 @@ export const HeaderExample = { snippet: headerSnippet, demo:
, }, + { + title: 'Sections', + source: [ + { + type: GuideSectionTypes.JS, + code: headerSectionsSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerSectionsHtml, + }, + ], + text: ( + <> +

+ Alternatively, you can add a sections object for{' '} + left, center, and{' '} + right EuiHeaderSections which accepts an array of + EuiHeaderSectionItem props. +

+

+ Note: Passing sections and{' '} + children will disregard the{' '} + children as it is not easily interpreted at what + location the children should be placed. +

+ + ), + props: { + EuiHeader, + EuiHeaderSection, + EuiHeaderSectionItem, + }, + snippet: headerSectionsSnippet, + demo: , + }, { title: 'Links', source: [ diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js new file mode 100644 index 00000000000..0adb23ff8f8 --- /dev/null +++ b/src-docs/src/views/header/header_sections.js @@ -0,0 +1,104 @@ +import React, { Component } from 'react'; + +import { + EuiHeader, + EuiHeaderBreadcrumbs, + EuiHeaderSectionItemButton, + EuiHeaderLogo, + EuiIcon, +} from '../../../../src/components'; + +import HeaderAppMenu from './header_app_menu'; +import HeaderUserMenu from './header_user_menu'; +import HeaderSpacesMenu from './header_spaces_menu'; + +export default class extends Component { + constructor(props) { + super(props); + } + + renderLogo() { + return ( + + ); + } + + renderBreadcrumbs() { + const breadcrumbs = [ + { + text: 'Management', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked management'); + }, + 'data-test-subj': 'breadcrumbsAnimals', + className: 'customClass', + }, + { + text: 'Truncation test is here for a really long item', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked truncation test'); + }, + }, + { + text: 'hidden', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked hidden'); + }, + }, + { + text: 'Users', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked users'); + }, + }, + { + text: 'Create', + }, + ]; + + return ; + } + + renderSearch() { + return ( + + + + ); + } + + render() { + return ( + }, + { children: this.renderBreadcrumbs() }, + ], + right: [ + { children: this.renderSearch() }, + { children: }, + { children: }, + ], + }}> + Child here + + ); + } +} diff --git a/src/components/header/__snapshots__/header.test.tsx.snap b/src/components/header/__snapshots__/header.test.tsx.snap index 08f23c4b9cf..c721d066932 100644 --- a/src/components/header/__snapshots__/header.test.tsx.snap +++ b/src/components/header/__snapshots__/header.test.tsx.snap @@ -51,3 +51,37 @@ exports[`EuiHeader renders sections 1`] = `
`; + +exports[`EuiHeader throws a warning if both children and sections were passed 1`] = ` +
+
+
+ Left +
+
+
+
+ Center +
+
+
+
+ Right +
+
+
+`; diff --git a/src/components/header/_header_logo.scss b/src/components/header/_header_logo.scss index 1ac01250f81..b8687ef6639 100644 --- a/src/components/header/_header_logo.scss +++ b/src/components/header/_header_logo.scss @@ -12,6 +12,10 @@ vertical-align: middle; white-space: nowrap; + &:hover { + background: $euiColorLightestShade; + } + &:focus, &:hover { text-decoration: none; diff --git a/src/components/header/header.test.tsx b/src/components/header/header.test.tsx index d1ef842e7a8..c90c3e15b9d 100644 --- a/src/components/header/header.test.tsx +++ b/src/components/header/header.test.tsx @@ -34,4 +34,39 @@ describe('EuiHeader', () => { expect(component).toMatchSnapshot(); }); + + describe('throws a warning', () => { + const oldConsoleError = console.warn; + let consoleStub: jest.Mock; + + beforeEach(() => { + // We don't use jest.spyOn() here, because EUI's tests apply a global + // console.error() override that throws an exception. For these + // tests, we just want to know if console.error() was called. + console.warn = consoleStub = jest.fn(); + }); + + afterEach(() => { + console.warn = oldConsoleError; + }); + + test('if both children and sections were passed', () => { + const component = render( + + Child + + ); + + expect(consoleStub).toBeCalled(); + expect(consoleStub.mock.calls[0][0]).toMatch( + 'cannot accept both `children` and `sections`' + ); + expect(component).toMatchSnapshot(); + }); + }); }); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 68f063c19e7..a2023c4a731 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -55,8 +55,14 @@ export const EuiHeader: FunctionComponent = ({ }, [position]); let contents; - if (sections) { + if (children) { + // In case both children and sections are passed, warn in the console that the children will be disregarded + console.warn( + 'EuiHeader cannot accept both `children` and `sections`. It will disregard the `children`.' + ); + } + contents = [ sections.left && ( diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index a6ea23c1710..ac58b1774c1 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -5,10 +5,6 @@ display: flex; align-items: center; - &:hover { - background: $euiColorLightestShade; - } - &:after { position: absolute; content: ''; @@ -25,6 +21,10 @@ text-align: center; font-size: 0; // aligns icons better vertically + &:hover { + background: $euiColorLightestShade; + } + &:focus { background: $euiFocusBackgroundColor; } diff --git a/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap b/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap index cc4c0775932..623e6ad1ce6 100644 --- a/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap +++ b/src/components/list_group/__snapshots__/list_group_item.test.tsx.snap @@ -294,7 +294,7 @@ exports[`EuiListGroupItem renders a disabled button even if provided an href 2`] `; -exports[`EuiListGroupItem throws an warning if both iconType and icon are provided but still renders 1`] = ` +exports[`EuiListGroupItem throws a warning if both iconType and icon are provided but still renders 1`] = `
  • diff --git a/src/components/list_group/list_group_item.test.tsx b/src/components/list_group/list_group_item.test.tsx index ef4e9264c32..6582f7303b2 100644 --- a/src/components/list_group/list_group_item.test.tsx +++ b/src/components/list_group/list_group_item.test.tsx @@ -140,7 +140,7 @@ describe('EuiListGroupItem', () => { expect(component).toMatchSnapshot(); }); - describe('throws an warning', () => { + describe('throws a warning', () => { const oldConsoleError = console.warn; let consoleStub: jest.Mock; From 32f77519b0246e672ba579eef2aaaaa606e31121 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 25 Feb 2020 18:10:08 -0500 Subject: [PATCH 3/6] Added example for `position` --- src-docs/src/views/header/header_example.js | 30 ++++++++++++ src-docs/src/views/header/header_position.js | 48 +++++++++++++++++++ src-docs/src/views/header/header_sections.js | 5 +- .../header/__snapshots__/header.test.tsx.snap | 8 ++-- src/components/header/header.tsx | 6 +-- 5 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src-docs/src/views/header/header_position.js diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 5dc0dc8bdb7..b664b7e335e 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -25,6 +25,10 @@ import HeaderSections from './header_sections'; const headerSectionsSource = require('!!raw-loader!./header_sections'); const headerSectionsHtml = renderToHtml(HeaderSections); +import HeaderPosition from './header_position'; +const headerPositionSource = require('!!raw-loader!./header_position'); +const headerPositionHtml = renderToHtml(HeaderPosition); + import HeaderAlert from './header_alert'; const headerAlertSource = require('!!raw-loader!./header_alert'); const headerAlertHtml = renderToHtml(HeaderAlert); @@ -152,6 +156,32 @@ export const HeaderExample = { snippet: headerSectionsSnippet, demo: , }, + { + title: 'Position', + source: [ + { + type: GuideSectionTypes.JS, + code: headerPositionSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerPositionHtml, + }, + ], + text: ( + <> +

    + Most consumer need a header that does not scroll way with the page + contents. You can apply this display by changing{' '} + position to fixed. It will + also add the appropriate padding to the window body by applying a + class. +

    + + ), + snippet: '', + demo: , + }, { title: 'Links', source: [ diff --git a/src-docs/src/views/header/header_position.js b/src-docs/src/views/header/header_position.js new file mode 100644 index 00000000000..4331921b6aa --- /dev/null +++ b/src-docs/src/views/header/header_position.js @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; + +import { + EuiHeader, + EuiHeaderLogo, + EuiSwitch, +} from '../../../../src/components'; + +export default () => { + const [position, setPosition] = useState('static'); + + return ( + + ), + border: 'none', + }, + ], + right: [ + { + children: ( + + setPosition(e.target.checked ? 'fixed' : 'static') + } + /> + ), + border: 'none', + style: { + padding: 16, + }, + }, + ], + }} + /> + ); +}; diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js index 0adb23ff8f8..a75de0b47b5 100644 --- a/src-docs/src/views/header/header_sections.js +++ b/src-docs/src/views/header/header_sections.js @@ -96,9 +96,8 @@ export default class extends Component { { children: }, { children: }, ], - }}> - Child here - + }} + /> ); } } diff --git a/src/components/header/__snapshots__/header.test.tsx.snap b/src/components/header/__snapshots__/header.test.tsx.snap index c721d066932..0ddd7ba83fb 100644 --- a/src/components/header/__snapshots__/header.test.tsx.snap +++ b/src/components/header/__snapshots__/header.test.tsx.snap @@ -3,14 +3,14 @@ exports[`EuiHeader is rendered 1`] = `
    `; exports[`EuiHeader renders children 1`] = `
    Hello! @@ -20,7 +20,7 @@ exports[`EuiHeader renders children 1`] = ` exports[`EuiHeader renders sections 1`] = `
    = ({ position = 'static', ...rest }) => { - const classes = classNames( - 'euiHeader', - `euiHeader--${position} ${className}`, - className - ); + const classes = classNames('euiHeader', `euiHeader--${position}`, className); useEffect(() => { if (position === 'fixed') { From 55b7e8072eaf271fc93868f8842052a739bc999f Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 25 Feb 2020 18:25:30 -0500 Subject: [PATCH 4/6] cl --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e720bef827b..d6f48862e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added `sections` and `position` props to `EuiHeader` ([#2928](https://github.com/elastic/eui/pull/2928)) + **Bug Fixes** - Fixed `EuiDataGrid`'s sort popover to behave properly on mobile screens ([#2979](https://github.com/elastic/eui/pull/2979)) From 62d307d8e711f0cd66e214ff4c9d2c3358e7c754 Mon Sep 17 00:00:00 2001 From: cchaos Date: Fri, 28 Feb 2020 17:49:21 -0500 Subject: [PATCH 5/6] Changing the way `sections` works --- src-docs/src/views/header/header_example.js | 52 +++--- src-docs/src/views/header/header_position.js | 62 +++----- src-docs/src/views/header/header_sections.js | 149 ++++++++---------- src-docs/src/views/header/props.tsx | 7 + .../header/__snapshots__/header.test.tsx.snap | 64 +++++--- src/components/header/header.test.tsx | 57 +++++-- src/components/header/header.tsx | 92 +++++++---- src/components/header/header_section/index.ts | 5 +- src/components/header/index.ts | 2 +- 9 files changed, 280 insertions(+), 210 deletions(-) create mode 100644 src-docs/src/views/header/props.tsx diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index b664b7e335e..72b503e9988 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router'; import { renderToHtml } from '../../services'; @@ -17,6 +18,8 @@ import { EuiHeaderLink, } from '../../../../src/components'; +import { EuiHeaderSectionsProp } from './props'; + import Header from './header'; const headerSource = require('!!raw-loader!./header'); const headerHtml = renderToHtml(Header); @@ -54,21 +57,20 @@ const headerSnippet = ` `; const headerSectionsSnippet = ``; const headerLinksSnippet = ` @@ -116,6 +118,7 @@ export const HeaderExample = { EuiHeaderSectionItem, EuiHeaderSectionItemButton, EuiHeaderLogo, + EuiHeaderSectionsProp, }, snippet: headerSnippet, demo:
    , @@ -135,10 +138,14 @@ export const HeaderExample = { text: ( <>

    - Alternatively, you can add a sections object for{' '} - left, center, and{' '} - right EuiHeaderSections which accepts an array of - EuiHeaderSectionItem props. + Alternatively, you can pass an array objects to the{' '} + sections props that takes a key of{' '} + items (array of children to wrap in an{' '} + EuiHeaderSectionItem) and/or{' '} + breadcrumbs (array of{' '} + breadcrumb objects). Each + item in the array will be wrapped in an{' '} + EuiHeaderSection.

    Note: Passing sections and{' '} @@ -150,6 +157,7 @@ export const HeaderExample = { ), props: { EuiHeader, + EuiHeaderSectionsProp, EuiHeaderSection, EuiHeaderSectionItem, }, @@ -157,7 +165,7 @@ export const HeaderExample = { demo: , }, { - title: 'Position', + title: 'Fixed header', source: [ { type: GuideSectionTypes.JS, @@ -183,7 +191,7 @@ export const HeaderExample = { demo: , }, { - title: 'Links', + title: 'Header links', source: [ { type: GuideSectionTypes.JS, @@ -209,7 +217,7 @@ export const HeaderExample = { demo: , }, { - title: 'Display header alerts', + title: 'Alerts in the header', source: [ { type: GuideSectionTypes.JS, diff --git a/src-docs/src/views/header/header_position.js b/src-docs/src/views/header/header_position.js index 4331921b6aa..9e9d1de9fe9 100644 --- a/src-docs/src/views/header/header_position.js +++ b/src-docs/src/views/header/header_position.js @@ -9,40 +9,30 @@ import { export default () => { const [position, setPosition] = useState('static'); - return ( - - ), - border: 'none', - }, - ], - right: [ - { - children: ( - - setPosition(e.target.checked ? 'fixed' : 'static') - } - /> - ), - border: 'none', - style: { - padding: 16, - }, - }, - ], - }} - /> - ); + const sections = [ + { + items: [ + , + ], + borders: 'none', + }, + { + items: [ +

    + setPosition(e.target.checked ? 'fixed' : 'static')} + /> +
    , + ], + borders: 'none', + }, + ]; + + return ; }; diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js index a75de0b47b5..32df23aa84c 100644 --- a/src-docs/src/views/header/header_sections.js +++ b/src-docs/src/views/header/header_sections.js @@ -1,103 +1,82 @@ -import React, { Component } from 'react'; +import React from 'react'; import { EuiHeader, - EuiHeaderBreadcrumbs, - EuiHeaderSectionItemButton, + EuiFieldSearch, EuiHeaderLogo, - EuiIcon, } from '../../../../src/components'; import HeaderAppMenu from './header_app_menu'; import HeaderUserMenu from './header_user_menu'; import HeaderSpacesMenu from './header_spaces_menu'; -export default class extends Component { - constructor(props) { - super(props); - } +export default () => { + const renderLogo = ( + + ); - renderLogo() { - return ( - - ); - } - - renderBreadcrumbs() { - const breadcrumbs = [ - { - text: 'Management', - href: '#', - onClick: e => { - e.preventDefault(); - console.log('You clicked management'); - }, - 'data-test-subj': 'breadcrumbsAnimals', - className: 'customClass', - }, - { - text: 'Truncation test is here for a really long item', - href: '#', - onClick: e => { - e.preventDefault(); - console.log('You clicked truncation test'); - }, + const breadcrumbs2 = [ + { + text: 'Management', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked management'); }, - { - text: 'hidden', - href: '#', - onClick: e => { - e.preventDefault(); - console.log('You clicked hidden'); - }, + 'data-test-subj': 'breadcrumbsAnimals', + className: 'customClass', + }, + { + text: 'Truncation test is here for a really long item', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked truncation test'); }, - { - text: 'Users', - href: '#', - onClick: e => { - e.preventDefault(); - console.log('You clicked users'); - }, + }, + { + text: 'hidden', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked hidden'); }, - { - text: 'Create', + }, + { + text: 'Users', + href: '#', + onClick: e => { + e.preventDefault(); + console.log('You clicked users'); }, - ]; + }, + { + text: 'Create', + }, + ]; - return ; - } + const renderSearch = ( + + ); - renderSearch() { - return ( - - - - ); - } + const sections = [ + { + items: [renderLogo, ], + borders: 'right', + breadcrumbs: breadcrumbs2, + }, + { + items: [renderSearch,
    ], + borders: 'none', + }, + { + items: [, ], + }, + ]; - render() { - return ( - }, - { children: this.renderBreadcrumbs() }, - ], - right: [ - { children: this.renderSearch() }, - { children: }, - { children: }, - ], - }} - /> - ); - } -} + return ; +}; diff --git a/src-docs/src/views/header/props.tsx b/src-docs/src/views/header/props.tsx new file mode 100644 index 00000000000..4259e7d9842 --- /dev/null +++ b/src-docs/src/views/header/props.tsx @@ -0,0 +1,7 @@ +import React, { FunctionComponent } from 'react'; + +import { EuiHeaderSections } from '../../../../src/components/header'; + +export const EuiHeaderSectionsProp: FunctionComponent< + EuiHeaderSections +> = () =>
    ; diff --git a/src/components/header/__snapshots__/header.test.tsx.snap b/src/components/header/__snapshots__/header.test.tsx.snap index 0ddd7ba83fb..3654b8e49a9 100644 --- a/src/components/header/__snapshots__/header.test.tsx.snap +++ b/src/components/header/__snapshots__/header.test.tsx.snap @@ -18,7 +18,35 @@ exports[`EuiHeader renders children 1`] = `
    `; -exports[`EuiHeader renders sections 1`] = ` +exports[`EuiHeader renders in fixed position 1`] = ` +
    + + Hello! + +
    +`; + +exports[`EuiHeader sections render breadcrumbs and props 1`] = ` +
    + +
    +`; + +exports[`EuiHeader sections render simple items and borders 1`] = `
    @@ -26,9 +54,14 @@ exports[`EuiHeader renders sections 1`] = ` class="euiHeaderSection euiHeaderSection--dontGrow euiHeaderSection--left" >
    - Left + Item 1 +
    +
    + Item 2
    - Center + Item A
    -
    -
    - Right + Item B
    @@ -62,25 +91,12 @@ exports[`EuiHeader throws a warning if both children and sections were passed 1`
    - Left + Item 1
    -
    -
    -
    - Center -
    -
    -
    - Right + Item 2
    diff --git a/src/components/header/header.test.tsx b/src/components/header/header.test.tsx index c90c3e15b9d..8be9d11b706 100644 --- a/src/components/header/header.test.tsx +++ b/src/components/header/header.test.tsx @@ -21,20 +21,51 @@ describe('EuiHeader', () => { expect(component).toMatchSnapshot(); }); - test('renders sections', () => { + test('renders in fixed position', () => { const component = render( - + + Hello! + ); expect(component).toMatchSnapshot(); }); + describe('sections', () => { + test('render simple items and borders', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('render breadcrumbs and props', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + describe('throws a warning', () => { const oldConsoleError = console.warn; let consoleStub: jest.Mock; @@ -53,11 +84,11 @@ describe('EuiHeader', () => { test('if both children and sections were passed', () => { const component = render( + sections={[ + { + items: ['Item 1', 'Item 2'], + }, + ]}> Child ); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 2cf4af7f242..2da6d3eeb3c 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -2,29 +2,59 @@ import React, { FunctionComponent, HTMLAttributes, useEffect } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; -import { EuiHeaderSectionItem, EuiHeaderSection } from './header_section'; -import { EuiHeaderSectionItemProps } from './header_section/header_section_item'; +import { + EuiHeaderSectionItem, + EuiHeaderSectionItemProps, + EuiHeaderSection, +} from './header_section'; +import { EuiHeaderBreadcrumbs } from './header_breadcrumbs'; +import { Breadcrumb, EuiBreadcrumbsProps } from '../breadcrumbs'; -export interface EuiHeaderSectionsProp { - left?: EuiHeaderSectionItemProps[]; - center?: EuiHeaderSectionItemProps[]; - right?: EuiHeaderSectionItemProps[]; +type EuiHeaderSectionItemType = EuiHeaderSectionItemProps['children']; +type EuiHeaderSectionBorderType = EuiHeaderSectionItemProps['border']; + +export interface EuiHeaderSections { + /** + * An arry of items that will be wrapped in a #EuiHeaderSectionItem + */ + items?: EuiHeaderSectionItemType[]; + /** + * Apply the passed border side to each #EuiHeaderSectionItem + */ + borders?: EuiHeaderSectionBorderType; + /** + * Breadcrumbs in the header cannot be wrapped in a #EuiHeaderSection in order for truncation to work. + * Simply pass the array of Breadcrumb objects + */ + breadcrumbs?: Breadcrumb[]; + /** + * Other props to pass to #EuiHeaderBreadcrumbs + */ + breadcrumbProps?: Omit; } -function createHeaderSection(sections: EuiHeaderSectionItemProps[]) { +function createHeaderSection( + sections: EuiHeaderSectionItemType[], + border?: EuiHeaderSectionBorderType +) { return sections.map((section, index) => { - return ; + return ( + + {section} + + ); }); } export type EuiHeaderProps = CommonProps & HTMLAttributes & { /** - * Takes an object with `left`, `right`, and `center` as arrays of #EuiHeaderSectionItem props. - * Wraps the each key's contents in a #EuiHeaderSection and positions it according to the key's name. + * An array of objects to wrap in a #EuiHeaderSection. + * Each section is spaced using `space-between`. + * See #EuiHeaderSectionsProp for object details. * This prop disregards the prop `children` if both are passed. */ - sections?: EuiHeaderSectionsProp; + sections?: EuiHeaderSections[]; /** * Helper that positions the header against the window body and * adds the correct amount of top padding to the window when in `fixed` mode @@ -59,23 +89,29 @@ export const EuiHeader: FunctionComponent = ({ ); } - contents = [ - sections.left && ( - - {createHeaderSection(sections.left)} - - ), - sections.center && ( - - {createHeaderSection(sections.center)} - - ), - sections.right && ( - - {createHeaderSection(sections.right)} - - ), - ]; + contents = sections.map((section, index) => { + const content = []; + if (section.items) { + // Items get wrapped in EuiHeaderSection and each item in a EuiHeaderSectionItem + content.push( + + {createHeaderSection(section.items, section.borders)} + + ); + } + if (section.breadcrumbs) { + content.push( + // Breadcrumbs are separate and cannot be contained in a EuiHeaderSection + // in order for truncation to work + + ); + } + return content; + }); } else { contents = children; } diff --git a/src/components/header/header_section/index.ts b/src/components/header/header_section/index.ts index 472a50057a3..2d032f7b088 100644 --- a/src/components/header/header_section/index.ts +++ b/src/components/header/header_section/index.ts @@ -1,5 +1,8 @@ export { EuiHeaderSection } from './header_section'; -export { EuiHeaderSectionItem } from './header_section_item'; +export { + EuiHeaderSectionItem, + EuiHeaderSectionItemProps, +} from './header_section_item'; export { EuiHeaderSectionItemButton } from './header_section_item_button'; diff --git a/src/components/header/index.ts b/src/components/header/index.ts index 731612722e6..029216ab1b4 100644 --- a/src/components/header/index.ts +++ b/src/components/header/index.ts @@ -1,4 +1,4 @@ -export { EuiHeader, EuiHeaderProps } from './header'; +export { EuiHeader, EuiHeaderProps, EuiHeaderSections } from './header'; export { EuiHeaderAlert, EuiHeaderAlertProps } from './header_alert'; From 64ecbb8068cff15ccf398bc3985211a0441a43f2 Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 2 Mar 2020 15:59:27 -0500 Subject: [PATCH 6/6] cleanup --- src-docs/src/views/header/header_sections.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-docs/src/views/header/header_sections.js b/src-docs/src/views/header/header_sections.js index 32df23aa84c..dbe68843a00 100644 --- a/src-docs/src/views/header/header_sections.js +++ b/src-docs/src/views/header/header_sections.js @@ -19,7 +19,7 @@ export default () => { /> ); - const breadcrumbs2 = [ + const breadcrumbs = [ { text: 'Management', href: '#', @@ -67,7 +67,7 @@ export default () => { { items: [renderLogo, ], borders: 'right', - breadcrumbs: breadcrumbs2, + breadcrumbs: breadcrumbs, }, { items: [renderSearch,
    ],