diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a2af320d1..a53da5b0f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `16.0.1`. +- Added `badge` prop and new styles `EuiHeaderAlert` ([#2506](https://github.com/elastic/eui/pull/2506)) ## [`16.0.1`](https://github.com/elastic/eui/tree/v16.0.1) diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js new file mode 100644 index 00000000000..6f7b739663e --- /dev/null +++ b/src-docs/src/views/header/header_alert.js @@ -0,0 +1,412 @@ +import React, { Component, Fragment } from 'react'; + +import { + EuiButton, + EuiFocusTrap, + EuiHorizontalRule, + EuiHeader, + EuiHeaderSection, + EuiHeaderSectionItem, + EuiHeaderSectionItemButton, + EuiHeaderLogo, + EuiHeaderLink, + EuiHeaderLinks, + EuiIcon, + EuiImage, + EuiNavDrawerGroup, + EuiNavDrawer, + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageContentBody, + EuiShowFor, + EuiTitle, +} from '../../../../src/components'; + +import { keyCodes } from '../../../../src/services'; + +import HeaderUserMenu from './header_user_menu'; +import HeaderSpacesMenu from './header_spaces_menu'; +import HeaderUpdates from './header_updates'; + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isFullScreen: false, + }; + + const faveExtraAction = { + color: 'subdued', + iconType: 'starEmpty', + iconSize: 's', + 'aria-label': 'Add to favorites', + }; + + const pinExtraAction = { + color: 'subdued', + iconType: 'pin', + iconSize: 's', + }; + + const pinExtraActionFn = val => { + pinExtraAction['aria-label'] = `Pin ${val} to top`; + return pinExtraAction; + }; + + this.topLinks = [ + { + label: 'Recently viewed', + iconType: 'clock', + flyoutMenu: { + title: 'Recent items', + listItems: [ + { + label: 'My dashboard', + href: '#/layout/nav-drawer', + iconType: 'dashboardApp', + extraAction: faveExtraAction, + }, + { + label: 'Workpad with title that wraps', + href: '#/layout/nav-drawer', + iconType: 'canvasApp', + extraAction: faveExtraAction, + }, + { + label: 'My logs', + href: '#/layout/nav-drawer', + iconType: 'logsApp', + 'aria-label': 'This is an alternate aria-label', + extraAction: faveExtraAction, + }, + ], + }, + }, + { + label: 'Favorites', + iconType: 'starEmpty', + flyoutMenu: { + title: 'Favorite items', + listItems: [ + { + label: 'My workpad', + href: '#/layout/nav-drawer', + iconType: 'canvasApp', + extraAction: { + color: 'subdued', + iconType: 'starFilled', + iconSize: 's', + 'aria-label': 'Remove from favorites', + alwaysShow: true, + }, + }, + { + label: 'My logs', + href: '#/layout/nav-drawer', + iconType: 'logsApp', + extraAction: { + color: 'subdued', + iconType: 'starFilled', + iconSize: 's', + 'aria-label': 'Remove from favorites', + alwaysShow: true, + }, + }, + ], + }, + }, + ]; + + this.exploreLinks = [ + { + label: 'Canvas', + href: '#/layout/nav-drawer', + iconType: 'canvasApp', + isActive: true, + extraAction: { + ...pinExtraActionFn('Canvas'), + alwaysShow: true, + }, + }, + { + label: 'Discover', + href: '#/layout/nav-drawer', + iconType: 'discoverApp', + extraAction: { ...pinExtraActionFn('Discover') }, + }, + { + label: 'Visualize', + href: '#/layout/nav-drawer', + iconType: 'visualizeApp', + extraAction: { ...pinExtraActionFn('Visualize') }, + }, + { + label: 'Dashboard', + href: '#/layout/nav-drawer', + iconType: 'dashboardApp', + extraAction: { ...pinExtraActionFn('Dashboard') }, + }, + { + label: 'Machine learning', + href: '#/layout/nav-drawer', + iconType: 'machineLearningApp', + extraAction: { ...pinExtraActionFn('Machine learning') }, + }, + { + label: 'Custom Plugin (no icon)', + href: '#/layout/nav-drawer', + extraAction: { ...pinExtraActionFn('Custom Plugin') }, + }, + { + label: 'Nature Plugin (image as icon)', + href: '#/layout/nav-drawer', + extraAction: { ...pinExtraActionFn('Nature Plugin') }, + icon: ( + + ), + }, + ]; + + this.solutionsLinks = [ + { + label: 'APM', + href: '#/layout/nav-drawer', + iconType: 'apmApp', + extraAction: { ...pinExtraActionFn('APM') }, + }, + { + label: 'Metrics', + href: '#/layout/nav-drawer', + iconType: 'metricsApp', + extraAction: { ...pinExtraActionFn('Infrastructure') }, + }, + { + label: 'Logs', + href: '#/layout/nav-drawer', + iconType: 'logsApp', + extraAction: { ...pinExtraActionFn('Log viewer') }, + }, + { + label: 'Uptime', + href: '#/layout/nav-drawer', + iconType: 'upgradeAssistantApp', + extraAction: { ...pinExtraActionFn('Uptime') }, + }, + { + label: 'Maps', + href: '#/layout/nav-drawer', + iconType: 'gisApp', + extraAction: { ...pinExtraActionFn('Maps') }, + }, + { + label: 'SIEM', + href: '#/layout/nav-drawer', + iconType: 'securityAnalyticsApp', + extraAction: { ...pinExtraActionFn('SIEM') }, + }, + ]; + + this.adminLinks = [ + { + label: 'Admin', + iconType: 'managementApp', + flyoutMenu: { + title: 'Tools and settings', + listItems: [ + { + label: 'Dev tools', + href: '#/layout/nav-drawer', + iconType: 'devToolsApp', + extraAction: { + color: 'subdued', + iconType: 'starEmpty', + iconSize: 's', + 'aria-label': 'Add to favorites', + }, + }, + { + label: 'Stack Monitoring', + href: '#/layout/nav-drawer', + iconType: 'monitoringApp', + extraAction: { + color: 'subdued', + iconType: 'starEmpty', + iconSize: 's', + 'aria-label': 'Add to favorites', + }, + }, + { + label: 'Stack Management', + href: '#/layout/nav-drawer', + iconType: 'managementApp', + extraAction: { + color: 'subdued', + iconType: 'starEmpty', + iconSize: 's', + 'aria-label': 'Add to favorites', + }, + }, + ], + }, + }, + ]; + } + + onKeyDown = event => { + if (event.keyCode === keyCodes.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + this.closeFullScreen(); + } + }; + + toggleFullScreen = () => { + this.setState(prevState => ({ + isFullScreen: !prevState.isFullScreen, + })); + }; + + closeFullScreen = () => { + this.setState({ + isFullScreen: false, + }); + }; + + renderLogo() { + return ( + + ); + } + + renderMenuTrigger() { + return ( + this.navDrawerRef.toggleOpen()}> + + + ); + } + + setNavDrawerRef = ref => (this.navDrawerRef = ref); + + render() { + let fullScreenDisplay; + + if (this.state.isFullScreen) { + fullScreenDisplay = ( + +
+ + + + + {this.renderMenuTrigger()} + + + + {this.renderLogo()} + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + +

Kibana news feed demo

+
+
+
+ + + +

+ Click the button to + see ‘What’s new?’ +

+
+
+ + + Exit fullscreen demo + + +
+
+
+
+
+ ); + } + return ( + + + Show fullscreen demo + + + {/* + If the below fullScreen code renders, it actually attaches to the body because of + EuiOverlayMask's React portal usage. + */} + + {fullScreenDisplay} + + ); + } +} diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 565aed13fc8..32e8ad7eca8 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -6,6 +6,7 @@ import { GuideSectionTypes } from '../../components'; import { EuiHeader, + EuiHeaderAlert, EuiHeaderBreadcrumbs, EuiHeaderSection, EuiHeaderSectionItem, @@ -20,6 +21,10 @@ import Header from './header'; const headerSource = require('!!raw-loader!./header'); const headerHtml = renderToHtml(Header); +import HeaderAlert from './header_alert'; +const headerAlertSource = require('!!raw-loader!./header_alert'); +const headerAlertHtml = renderToHtml(HeaderAlert); + import HeaderLinks from './header_links'; const headerLinksSource = require('!!raw-loader!./header_links'); const headerLinksHtml = renderToHtml(HeaderLinks); @@ -111,5 +116,33 @@ export const HeaderExample = { snippet: headerLinksSnippet, demo: , }, + { + title: 'Display header alerts', + source: [ + { + type: GuideSectionTypes.JS, + code: headerAlertSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerAlertHtml, + }, + ], + text: ( +

+ Use an EuiHeaderSectionItemButton to display + additional information in an EuiPopover or{' '} + EuiFlyout, such as a user profile or news feed. In + the latter example, this additional content can be presented in a list + style format using EuiHeaderAlert components, as + shown below. +

+ ), + props: { + EuiHeaderAlert, + }, + snippet: headerLinksSnippet, + demo: , + }, ], }; diff --git a/src-docs/src/views/header/header_updates.js b/src-docs/src/views/header/header_updates.js new file mode 100755 index 00000000000..526a03c3a62 --- /dev/null +++ b/src-docs/src/views/header/header_updates.js @@ -0,0 +1,194 @@ +import React, { Component, Fragment } from 'react'; + +import { + EuiIcon, + EuiHeaderAlert, + EuiHeaderSectionItemButton, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiNotificationBadge, + EuiLink, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiText, + EuiBadge, +} from '../../../../src/components'; + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isFlyoutVisible: false, + showBadge: true, + }; + + this.alerts = [ + { + title: 'Control access to features', + text: 'Show or hide applications and features per space in Kibana.', + action: Learn about feature controls, + date: '1 May 2019', + badge: 7.1, + }, + { + title: 'Kibana 7.0 is turning heads', + text: + 'Simplified navigation, responsive dashboards, dark mode… pick your favorite.', + action: ( + + Read the blog + + ), + date: '10 April 2019', + badge: 7.0, + }, + { + title: 'Enter dark mode', + text: + 'Kibana now supports the easy-on-the-eyes theme across the entire UI.', + action: Go to Advanced Settings, + date: '10 April 2019', + badge: 7.0, + }, + { + title: 'Pixel-perfect Canvas is production ready', + text: 'Your creative space for visualizing data awaits.', + action: ( + + Watch the webinar + + ), + date: '26 March 2019', + badge: 6.7, + }, + { + title: '6.7 release notes', + text: 'Stay up-to-date on the latest and greatest features.', + action: ( + + Check out the docs + + ), + date: '26 March 2019', + badge: 6.7, + }, + { + title: 'Rollups made simple in Kibana', + text: + 'Save space and preserve the integrity of your data directly in the UI.', + action: ( + + Read the blog + + ), + date: '10 January 2019', + badge: 6.5, + }, + ]; + } + + closeFlyout = () => { + this.setState({ isFlyoutVisible: false }); + }; + + showFlyout = () => { + this.setState({ showBadge: false }); + this.setState(prevState => ({ + isFlyoutVisible: !prevState.isFlyoutVisible, + })); + }; + + render() { + const button = ( + + + + {this.state.showBadge ? ( + + ▪ + + ) : null} + + ); + + let flyout; + const flyoutStyle = { + top: '49px', + height: 'calc(100vh - 49px)', + }; + if (this.state.isFlyoutVisible) { + flyout = ( + + + +

What's new

+
+
+ + {this.alerts.map((alert, i) => ( + + ))} + + + + + + Close + + + + +

Version 7.0

+
+
+
+
+
+ ); + } + + return ( + + {button} + {flyout} + + ); + } +} diff --git a/src-docs/src/views/header/header_user_menu.js b/src-docs/src/views/header/header_user_menu.js index 66d3e5164df..c0ff4802d52 100644 --- a/src-docs/src/views/header/header_user_menu.js +++ b/src-docs/src/views/header/header_user_menu.js @@ -4,9 +4,7 @@ import { EuiAvatar, EuiFlexGroup, EuiFlexItem, - EuiHeaderAlert, EuiHeaderSectionItemButton, - EuiNotificationBadge, EuiLink, EuiText, EuiSpacer, @@ -43,10 +41,6 @@ export default class extends Component { aria-label="Account menu" onClick={this.onMenuButtonClick}> - - - 3 - ); @@ -90,26 +84,6 @@ export default class extends Component { - - - - Download your thing here} - date="Nov. 14, 02:14PM." - /> - - Download your thing here} - date="Nov. 14, 02:14PM." - /> ); diff --git a/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap b/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap index eaf93871bfd..dfeac591855 100644 --- a/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap +++ b/src/components/header/header_alert/__snapshots__/header_alert.test.tsx.snap @@ -1,46 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiHeaderAlert is rendered 1`] = ` -
- -
- title -
-
- -
-
+

+ title +

+
+ `; exports[`EuiHeaderAlert renders action 1`] = ` -
- -
- title -
-
- -
-
-
+

+ title +

+
+ + `; exports[`EuiHeaderAlert renders date as an element 1`] = ` -
- -
- shazm -
-
- -
-
+

+ shazm +

+
+ `; exports[`EuiHeaderAlert renders title as an element 1`] = ` -
- -
-

- Circumambulate the city -

-
-
- -
-
+

+

+ Circumambulate the city +

+
+ `; diff --git a/src/components/header/header_alert/_header_alert.scss b/src/components/header/header_alert/_header_alert.scss index 09bd1678253..7e80509f2a4 100644 --- a/src/components/header/header_alert/_header_alert.scss +++ b/src/components/header/header_alert/_header_alert.scss @@ -1,8 +1,10 @@ .euiHeaderAlert { min-width: 300px; position: relative; - padding: $euiSize; - border-top: $euiBorderThin; + margin-bottom: $euiSizeL; + padding: 0 $euiSizeS $euiSizeL; + border-bottom: $euiBorderThin; + border-top: none; .euiHeaderAlert__dismiss { opacity: 0; @@ -19,16 +21,16 @@ .euiHeaderAlert__title { @include euiTitle('xs'); - padding-right: $euiSizeL; // Accounts for the dismiss button. + margin-bottom: $euiSizeS; } .euiHeaderAlert__text { - @include euiFontSizeXS; - margin-bottom: $euiSizeS; + @include euiFontSizeS; + margin-bottom: $euiSize; } .euiHeaderAlert__action { - @include euiFontSizeXS; + @include euiFontSizeS; } .euiHeaderAlert__date { diff --git a/src/components/header/header_alert/header_alert.test.tsx b/src/components/header/header_alert/header_alert.test.tsx index 3fbdc23646d..c64a0a6e272 100644 --- a/src/components/header/header_alert/header_alert.test.tsx +++ b/src/components/header/header_alert/header_alert.test.tsx @@ -4,6 +4,8 @@ import { requiredProps } from '../../../test/required_props'; import { EuiHeaderAlert } from './header_alert'; +jest.mock('./../../form/form_row/make_id', () => () => 'generated-id'); + describe('EuiHeaderAlert', () => { test('is rendered', () => { const component = render( diff --git a/src/components/header/header_alert/header_alert.tsx b/src/components/header/header_alert/header_alert.tsx index 32683fb9774..cc7bf71582a 100644 --- a/src/components/header/header_alert/header_alert.tsx +++ b/src/components/header/header_alert/header_alert.tsx @@ -1,19 +1,24 @@ import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; import classNames from 'classnames'; -import { EuiButtonIcon } from '../../button'; import { CommonProps } from '../../common'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; - -import { EuiI18n } from '../../i18n'; +import makeId from '../../form/form_row/make_id'; export type EuiHeaderAlertProps = CommonProps & Omit, 'title'> & { + /** + * Adds a link to the alert. + */ action?: ReactNode; date: ReactNode; text?: ReactNode; title: ReactNode; + /** + * Accepts an `EuiBadge` that displays on the alert + */ + badge?: ReactNode; }; export const EuiHeaderAlert: FunctionComponent = ({ @@ -22,36 +27,27 @@ export const EuiHeaderAlert: FunctionComponent = ({ date, text, title, + badge, ...rest }) => { const classes = classNames('euiHeaderAlert', className); - return ( - - {(dismiss: string) => ( -
- - -
{title}
+ const ariaId = makeId(); -
{text}
- - - -
{action}
-
- - -
{date}
-
-
-
- )} -
+ return ( +
+ + +
{date}
+
+ {badge && {badge}} +
+ +

+ {title} +

+
{text}
+ {action &&
{action}
} +
); };