From 915a2d16f594fbf6778a6be0f6138b8b17c68807 Mon Sep 17 00:00:00 2001 From: miukimiu Date: Thu, 14 Jan 2021 12:03:31 +0000 Subject: [PATCH 01/22] Adding animation and hasBackground props to EuiHeaderSectionItemButton --- src-docs/src/views/header/header_alert.js | 275 +++++++++++------- src-docs/src/views/header/header_example.js | 27 +- src/components/header/_mixins.scss | 8 +- .../__snapshots__/header_links.test.tsx.snap | 14 +- .../header_section_item_button.test.tsx.snap | 109 ++++++- .../header/header_section/_animations.scss | 26 ++ .../header_section/_header_section_item.scss | 53 +--- .../_header_section_item_button.scss | 88 ++++++ .../header/header_section/_index.scss | 2 + .../header_section_item_button.test.tsx | 16 + .../header_section_item_button.tsx | 78 +++-- 11 files changed, 498 insertions(+), 198 deletions(-) create mode 100644 src/components/header/header_section/_animations.scss create mode 100644 src/components/header/header_section/_header_section_item_button.scss diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index 88e440e08b4..c7a1aada581 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { EuiAvatar, EuiBadge, + EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, @@ -29,42 +30,14 @@ import { } from '../../../../src/components'; import { htmlIdGenerator } from '../../../../src/services'; -export default () => { - const [position, setPosition] = useState('static'); - - return ( - <> - setPosition(e.target.checked ? 'fixed' : 'static')} - /> - - - - - Elastic - - - - - - - - - - - - - - ); -}; - -const HeaderUpdates = ({ flyoutOrPopover = 'flyout' }) => { +const HeaderUpdates = ({ + showNotification, + setShowNotification, + notificationsNumber, + isAnimating, +}) => { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [showBadge, setShowBadge] = useState(true); + const [isPopoverVisible, setIsPopoverVisible] = useState(false); const alerts = [ { @@ -146,84 +119,107 @@ const HeaderUpdates = ({ flyoutOrPopover = 'flyout' }) => { setIsFlyoutVisible(false); }; + const closePopover = () => { + setIsPopoverVisible(false); + }; + const showFlyout = () => { - setShowBadge(false); + setShowNotification(false); setIsFlyoutVisible(!isFlyoutVisible); }; + const showPopover = () => { + setShowNotification(false); + setIsPopoverVisible(!isPopoverVisible); + }; + const button = ( showFlyout()} - notification={showBadge}> + onClick={() => showPopover()} + notification={showNotification && notificationsNumber} + animation={isAnimating}> ); - let content; - if (flyoutOrPopover === 'flyout') { - content = ( - <> - {button} - {isFlyoutVisible && ( - - closeFlyout()} - size="s" - id="headerNewsFeed" - aria-labelledby="flyoutSmallTitle"> - - -

What's new

-
-
- - {alerts.map((alert, i) => ( - - ))} - - - - - closeFlyout()} - flush="left"> - Close - - - - -

Version 7.0

-
-
-
-
-
-
- )} - - ); - } + const alertButton = ( + showFlyout()} + notification={showNotification} + animation={isAnimating} + hasBackground> + + + ); + + const flyout = ( + + closeFlyout()} + size="s" + id="headerFlyoutNewsFeed" + aria-labelledby="flyoutSmallTitle"> + + +

What's new

+
+
+ + {alerts.map((alert, i) => ( + + ))} + + + + + closeFlyout()} + flush="left"> + Close + + + + +

Version 7.0

+
+
+
+
+
+
+ ); - if (flyoutOrPopover === 'popover') { - content = ( + return ( + <> + {alertButton} + {isFlyoutVisible && flyout} closeFlyout()} + isOpen={isPopoverVisible} + closePopover={() => closePopover()} panelPaddingSize="none"> What's new
@@ -245,10 +241,8 @@ const HeaderUpdates = ({ flyoutOrPopover = 'flyout' }) => { - ); - } - - return content; + + ); }; const HeaderUserMenu = () => { @@ -319,3 +313,84 @@ const HeaderUserMenu = () => { ); }; + +export default () => { + const [position, setPosition] = useState('static'); + const [showNotification, setShowNotification] = useState(true); + const [isAnimating, setIsAnimating] = useState(false); + const [theme, setTheme] = useState('light'); + const [notificationsNumber, setNotificationsNumber] = useState(1); + + const animate = () => { + setIsAnimating(false); + + setTimeout(() => { + setIsAnimating(true); + }, 100); + }; + + const notify = () => { + if (!showNotification) { + setNotificationsNumber(1); + setShowNotification(true); + } else { + setNotificationsNumber(notificationsNumber + 1); + } + }; + + return ( + <> + + + setPosition(e.target.checked ? 'fixed' : 'static')} + /> + + + + setTheme(e.target.checked ? 'dark' : 'light')} + /> + + + + + Notify + + + + + + Animate + + + + + + + + + Elastic + + + + + setShowNotification()} + notificationsNumber={notificationsNumber} + isAnimating={isAnimating} + /> + + + + + + + + ); +}; diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 2a674e0ddd8..78cc7a35537 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -279,7 +279,7 @@ export const HeaderExample = { demo: , }, { - title: 'Portal content in the header', + title: 'Portal content in the header and buttons', source: [ { type: GuideSectionTypes.JS, @@ -311,15 +311,36 @@ export const HeaderExample = { in conjunction with a fixed header, be sure to add the repositionOnScroll prop to the popover.

+

+ To alert or notify users about the additional information they are + receiving, use the EuiHeaderSectionItemButton{' '} + notification prop. You can pass a{' '} + node that will render inside a{' '} + EuiBadgeNotification or pass{' '} + true to render a simple dot. You can also animate + the button by passing true into the{' '} + animation prop. +

+

The example below shows how to incorporate{' '} - EuiHeaderAlert components to show a list of - updates. + EuiHeaderAlert components to show a list of updates + inside a{' '} + + EuiFlyout + {' '} + and a{' '} + + EuiPopover + {' '} + . It also shows how to animate and add different types of + notifications.

), props: { EuiHeaderAlert, + EuiHeaderSectionItemButton, }, demo: , }, diff --git a/src/components/header/_mixins.scss b/src/components/header/_mixins.scss index b693f759d2b..385f6674929 100644 --- a/src/components/header/_mixins.scss +++ b/src/components/header/_mixins.scss @@ -4,7 +4,7 @@ .euiHeaderLogo__text, .euiHeaderLink, - .euiHeaderSectionItem__button { + .euiHeaderSectionItemButton { color: $euiColorGhost; } @@ -20,7 +20,7 @@ .euiHeaderLogo, .euiHeaderLink, - .euiHeaderSectionItem__button { + .euiHeaderSectionItemButton { &:hover { background: transparentize(lightOrDarkTheme($euiColorDarkShade, $euiColorLightestShade), .5); } @@ -30,6 +30,10 @@ } } + .euiHeaderSectionItemButton__inner--hasBackground { + background: $euiColorFullShade; + } + .euiHeaderSectionItemButton__notification--badge { box-shadow: 0 0 0 1px $backgroundColor; } diff --git a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap index aa42a20fce1..4b384c01bf0 100644 --- a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap +++ b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap @@ -80,12 +80,20 @@ exports[`EuiHeaderLinks popover props is rendered 1`] = ` >
diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index a8f3b53c283..2128eb82a5e 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -3,58 +3,135 @@ exports[`EuiHeaderSectionItemButton is rendered 1`] = ` +`; + +exports[`EuiHeaderSectionItemButton renders animation 1`] = ` + +`; + +exports[`EuiHeaderSectionItemButton renders background 1`] = ` + `; exports[`EuiHeaderSectionItemButton renders children 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification as a badge 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification as a dot 1`] = ` `; exports[`EuiHeaderSectionItemButton renders notification color 1`] = ` `; diff --git a/src/components/header/header_section/_animations.scss b/src/components/header/header_section/_animations.scss new file mode 100644 index 00000000000..65750222118 --- /dev/null +++ b/src/components/header/header_section/_animations.scss @@ -0,0 +1,26 @@ +@keyframes bellRinging { + 0% { transform: rotate(0); } + 1% { transform: rotate(30deg); } + 3% { transform: rotate(-28deg); } + 5% { transform: rotate(34deg); } + 7% { transform: rotate(-32deg); } + 9% { transform: rotate(30deg); } + 11% { transform: rotate(-28deg); } + 13% { transform: rotate(26deg); } + 15% { transform: rotate(-24deg); } + 17% { transform: rotate(22deg); } + 19% { transform: rotate(-20deg); } + 21% { transform: rotate(18deg); } + 23% { transform: rotate(-16deg); } + 25% { transform: rotate(14deg); } + 27% { transform: rotate(-12deg); } + 29% { transform: rotate(10deg); } + 31% { transform: rotate(-8deg); } + 33% { transform: rotate(6deg); } + 35% { transform: rotate(-4deg); } + 37% { transform: rotate(2deg); } + 39% { transform: rotate(-1deg); } + 41% { transform: rotate(1deg); } + 43% { transform: rotate(0); } + 100% { transform: rotate(0); } +} \ No newline at end of file diff --git a/src/components/header/header_section/_header_section_item.scss b/src/components/header/header_section/_header_section_item.scss index b54be94d58d..f68b4a17672 100644 --- a/src/components/header/header_section/_header_section_item.scss +++ b/src/components/header/header_section/_header_section_item.scss @@ -15,22 +15,6 @@ } } -.euiHeaderSectionItem__button { - position: relative; // For positioning the notification - height: $euiHeaderChildSize; - min-width: $euiHeaderChildSize; - text-align: center; - font-size: 0; // aligns icons better vertically - - &:hover { - background: $euiColorLightestShade; - } - - &:focus { - background: $euiFocusBackgroundColor; - } -} - .euiHeaderSectionItem--borderLeft { &:after { left: 0; @@ -46,25 +30,8 @@ } } -.euiHeaderSectionItemButton__notification { - position: absolute; -} - -.euiHeaderSectionItemButton__notification--badge { - top: 9%; - right: 9%; - box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; -} - -.euiHeaderSectionItemButton__notification--dot { - top: 0; - right: 0; - stroke: $euiHeaderBackgroundColor; -} - @include euiBreakpoint('xs') { - .euiHeaderSectionItem, - .euiHeaderSectionItem__button { + .euiHeaderSectionItem { min-width: $euiHeaderChildSize * .75; } @@ -74,20 +41,4 @@ display: none; } } - - // On small screens just show a small dot indicating there are notifications - .euiHeaderSectionItemButton__notification--badge { - @include size($euiSizeS); - top: 20%; - min-width: 0; - border-radius: $euiSizeS; - color: $euiColorAccent; - overflow: hidden; - } - - // Using specificty to override the default icon size - .euiHeaderSectionItemButton__notification.euiHeaderSectionItemButton__notification--dot { - @include size($euiSize); - top: 9%; - } -} +} \ No newline at end of file diff --git a/src/components/header/header_section/_header_section_item_button.scss b/src/components/header/header_section/_header_section_item_button.scss new file mode 100644 index 00000000000..9abc4268e15 --- /dev/null +++ b/src/components/header/header_section/_header_section_item_button.scss @@ -0,0 +1,88 @@ +.euiHeaderSectionItemButton { + position: relative; // For positioning the notification + height: $euiHeaderChildSize; + min-width: $euiHeaderChildSize; + text-align: center; + font-size: 0; // aligns icons better vertically + + &:hover { + background: $euiColorLightestShade; + } + + &:focus { + background: $euiFocusBackgroundColor; + } +} + +.euiHeaderSectionItemButton__notification { + position: absolute; +} + +.euiHeaderSectionItemButton__notification--badge { + top: 9%; + right: 9%; + box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; +} + +.euiHeaderSectionItemButton--isAnimating { + animation: 5s bellRinging ease-in-out; +} + +.euiHeaderSectionItemButton__notification--dot { + position: absolute; + stroke: $euiHeaderBackgroundColor; +} + +.euiHeaderSectionItemButton__inner { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + .euiHeaderSectionItemButton__notification--dot { + top: 0; + right: 0; + } +} + +.euiHeaderSectionItemButton__inner--hasBackground { + position: relative; + background: $euiColorLightestShade; + height: $euiSizeXL; + width: $euiSizeXL; + min-width: $euiSizeXL; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + margin: 0 auto; + + .euiHeaderSectionItemButton__notification--dot { + top: -$euiSizeXS; + right: -$euiSizeXS; + } +} + +@include euiBreakpoint('xs') { + .euiHeaderSectionItemButton { + min-width: $euiHeaderChildSize * .75; + } + + // On small screens just show a small dot indicating there are notifications + .euiHeaderSectionItemButton__notification--badge { + @include size($euiSizeS); + top: 20%; + min-width: 0; + border-radius: $euiSizeS; + color: $euiColorAccent; + overflow: hidden; + } + + // Using specificity to override the default icon size + .euiHeaderSectionItemButton__notification.euiHeaderSectionItemButton__notification--dot { + @include size($euiSize); + top: 9%; + } +} \ No newline at end of file diff --git a/src/components/header/header_section/_index.scss b/src/components/header/header_section/_index.scss index 1ff2956d129..8e83b2f64dc 100644 --- a/src/components/header/header_section/_index.scss +++ b/src/components/header/header_section/_index.scss @@ -1,2 +1,4 @@ +@import 'animations'; @import 'header_section'; @import 'header_section_item'; +@import 'header_section_item_button'; \ No newline at end of file diff --git a/src/components/header/header_section/header_section_item_button.test.tsx b/src/components/header/header_section/header_section_item_button.test.tsx index 1f273633359..420010fabf5 100644 --- a/src/components/header/header_section/header_section_item_button.test.tsx +++ b/src/components/header/header_section/header_section_item_button.test.tsx @@ -67,6 +67,22 @@ describe('EuiHeaderSectionItemButton', () => { }); }); + test('renders animation', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('renders background', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + describe('onClick', () => { test("isn't called upon instantiation", () => { const onClickHandler = jest.fn(); diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 9fe1035f13c..f5bd03587ad 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -38,6 +38,16 @@ export type EuiHeaderSectionItemButtonProps = CommonProps & * Changes the color of the notification background */ notificationColor?: EuiNotificationBadgeProps['color']; + + /** + * Adds a circular background to the button to emphasize the content + */ + hasBackground?: boolean; + + /** + * Pass `true` to trigger an animation + */ + animation?: boolean; }; export type EuiHeaderSectionItemButtonRef = HTMLButtonElement; @@ -53,32 +63,55 @@ export const EuiHeaderSectionItemButton = React.forwardRef< className, notification, notificationColor = 'accent', + animation = false, + hasBackground = false, ...rest }, ref ) => { - const classes = classNames('euiHeaderSectionItem__button', className); + const classes = classNames('euiHeaderSectionItemButton', className); + const animationClasses = classNames({ + 'euiHeaderSectionItemButton--isAnimating': animation, + }); + + let buttonContent; + + if (hasBackground) { + buttonContent = ( + + {notification && ( + + )} + {children} + + ); + } else { + buttonContent = ( + + {children} + + {notification && typeof notification === 'boolean' && ( + + )} - let notificationBadge; - if (notification) { - if (notification === true) { - notificationBadge = ( - - ); - } else { - notificationBadge = ( - - {notification} - - ); - } + {notification && typeof notification !== 'boolean' && ( + + {notification} + + )} + + ); } return ( @@ -88,8 +121,7 @@ export const EuiHeaderSectionItemButton = React.forwardRef< onClick={onClick} type="button" {...rest}> - {children} - {notificationBadge} + {buttonContent} ); } From 14d5b5166a3b47d91dcb0fee50137e8cfee29f39 Mon Sep 17 00:00:00 2001 From: miukimiu Date: Thu, 14 Jan 2021 13:44:47 +0000 Subject: [PATCH 02/22] A few improvements --- src-docs/src/views/header/header_alert.js | 84 ++++++++++--------- .../header_section_item_button.test.tsx.snap | 8 +- .../header/header_section/_animations.scss | 2 +- .../_header_section_item_button.scss | 51 +++++++---- .../header_section_item_button.tsx | 63 ++++++-------- 5 files changed, 110 insertions(+), 98 deletions(-) diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index c7a1aada581..0a9c5401e5b 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -133,7 +133,7 @@ const HeaderUpdates = ({ setIsPopoverVisible(!isPopoverVisible); }; - const button = ( + const bellButton = ( showPopover()} - notification={showNotification && notificationsNumber} - animation={isAnimating}> - + onClick={() => showFlyout()} + notification={showNotification} + animation={isAnimating} + hasBackground> + ); - const alertButton = ( + const cheerButton = ( showFlyout()} - notification={showNotification} - animation={isAnimating} - hasBackground> - + onClick={() => showPopover()} + notification={showNotification && notificationsNumber} + animation={isAnimating}> + ); @@ -209,38 +209,42 @@ const HeaderUpdates = ({ ); + const popover = ( + closePopover()} + panelPaddingSize="none"> + What's new +
+ + {alerts.map((alert, i) => ( + + ))} +
+ + +

Version 7.0

+
+
+
+ ); + return ( <> - {alertButton} + {bellButton} + {popover} {isFlyoutVisible && flyout} - closePopover()} - panelPaddingSize="none"> - What's new -
- - {alerts.map((alert, i) => ( - - ))} -
- - -

Version 7.0

-
-
-
); }; diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index 2128eb82a5e..c50813b5dad 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -46,12 +46,12 @@ exports[`EuiHeaderSectionItemButton renders background 1`] = ` class="euiHeaderSectionItemButton__inner--hasBackground" > diff --git a/src/components/header/header_section/_animations.scss b/src/components/header/header_section/_animations.scss index 65750222118..ca7f87d8d30 100644 --- a/src/components/header/header_section/_animations.scss +++ b/src/components/header/header_section/_animations.scss @@ -1,4 +1,4 @@ -@keyframes bellRinging { +@keyframes euiButtonRinging { 0% { transform: rotate(0); } 1% { transform: rotate(30deg); } 3% { transform: rotate(-28deg); } diff --git a/src/components/header/header_section/_header_section_item_button.scss b/src/components/header/header_section/_header_section_item_button.scss index 9abc4268e15..6dff91df0ed 100644 --- a/src/components/header/header_section/_header_section_item_button.scss +++ b/src/components/header/header_section/_header_section_item_button.scss @@ -16,23 +16,17 @@ .euiHeaderSectionItemButton__notification { position: absolute; -} - -.euiHeaderSectionItemButton__notification--badge { - top: 9%; - right: 9%; - box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; -} -.euiHeaderSectionItemButton--isAnimating { - animation: 5s bellRinging ease-in-out; -} + &--dot { + stroke: $euiHeaderBackgroundColor; + } -.euiHeaderSectionItemButton__notification--dot { - position: absolute; - stroke: $euiHeaderBackgroundColor; + &--badge { + box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; + } } +// button without a background .euiHeaderSectionItemButton__inner { position: relative; width: 100%; @@ -45,8 +39,14 @@ top: 0; right: 0; } + + .euiHeaderSectionItemButton__notification--badge { + top: 9%; + right: 9%; + } } +// button with a background .euiHeaderSectionItemButton__inner--hasBackground { position: relative; background: $euiColorLightestShade; @@ -60,11 +60,20 @@ margin: 0 auto; .euiHeaderSectionItemButton__notification--dot { + top: -$euiSizeS; + right: -$euiSizeS; + } + + .euiHeaderSectionItemButton__notification--badge { top: -$euiSizeXS; right: -$euiSizeXS; } } +.euiHeaderSectionItemButton--isAnimating { + animation: 5s euiButtonRinging ease-in-out; +} + @include euiBreakpoint('xs') { .euiHeaderSectionItemButton { min-width: $euiHeaderChildSize * .75; @@ -73,7 +82,6 @@ // On small screens just show a small dot indicating there are notifications .euiHeaderSectionItemButton__notification--badge { @include size($euiSizeS); - top: 20%; min-width: 0; border-radius: $euiSizeS; color: $euiColorAccent; @@ -83,6 +91,19 @@ // Using specificity to override the default icon size .euiHeaderSectionItemButton__notification.euiHeaderSectionItemButton__notification--dot { @include size($euiSize); - top: 9%; + } + + .euiHeaderSectionItemButton__inner { + .euiHeaderSectionItemButton__notification--badge { + top: 20%; + } + } + + .euiHeaderSectionItemButton__inner--hasBackground { + .euiHeaderSectionItemButton__notification--dot, + .euiHeaderSectionItemButton__notification--badge { + top: 0; + right: 0; + } } } \ No newline at end of file diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index f5bd03587ad..042bb05ccd3 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -73,46 +73,33 @@ export const EuiHeaderSectionItemButton = React.forwardRef< const animationClasses = classNames({ 'euiHeaderSectionItemButton--isAnimating': animation, }); + const buttonClasses = classNames({ + euiHeaderSectionItemButton__inner: !hasBackground, + 'euiHeaderSectionItemButton__inner--hasBackground': hasBackground, + }); - let buttonContent; - - if (hasBackground) { - buttonContent = ( - - {notification && ( - - )} - {children} - - ); - } else { - buttonContent = ( - - {children} + const buttonInner = ( + + {children} - {notification && typeof notification === 'boolean' && ( - - )} + {notification && typeof notification === 'boolean' && ( + + )} - {notification && typeof notification !== 'boolean' && ( - - {notification} - - )} - - ); - } + {notification && typeof notification !== 'boolean' && ( + + {notification} + + )} + + ); return ( ); } From cc5c874513716aa72560d423dfe1fe828c0ef48a Mon Sep 17 00:00:00 2001 From: miukimiu Date: Fri, 15 Jan 2021 19:21:44 +0000 Subject: [PATCH 03/22] Docs props --- src-docs/src/views/header/header_example.js | 2 +- .../header/header_section/header_section_item_button.tsx | 8 ++++++-- src/components/header/index.ts | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 78cc7a35537..f9f6d1d2c9c 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -279,7 +279,7 @@ export const HeaderExample = { demo: , }, { - title: 'Portal content in the header and buttons', + title: 'Portal content and buttons in the header ', source: [ { type: GuideSectionTypes.JS, diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 042bb05ccd3..66d7c444598 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -17,7 +17,11 @@ * under the License. */ -import React, { ButtonHTMLAttributes, PropsWithChildren } from 'react'; +import React, { + ButtonHTMLAttributes, + PropsWithChildren, + forwardRef, +} from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; @@ -52,7 +56,7 @@ export type EuiHeaderSectionItemButtonProps = CommonProps & export type EuiHeaderSectionItemButtonRef = HTMLButtonElement; -export const EuiHeaderSectionItemButton = React.forwardRef< +export const EuiHeaderSectionItemButton = forwardRef< EuiHeaderSectionItemButtonRef, PropsWithChildren >( diff --git a/src/components/header/index.ts b/src/components/header/index.ts index 7d1b5f7058d..6c1276d0612 100644 --- a/src/components/header/index.ts +++ b/src/components/header/index.ts @@ -31,4 +31,5 @@ export { EuiHeaderSection, EuiHeaderSectionItem, EuiHeaderSectionItemButton, + EuiHeaderSectionItemButtonProps, } from './header_section'; From 4f5c09f9a44124750ef2a965c05f891e519d862d Mon Sep 17 00:00:00 2001 From: miukimiu Date: Mon, 18 Jan 2021 15:58:51 +0000 Subject: [PATCH 04/22] Removing hasBackground prop --- src/components/header/_mixins.scss | 4 - .../__snapshots__/header_links.test.tsx.snap | 10 +- .../header_section_item_button.test.tsx.snap | 100 +++++------------- .../_header_section_item_button.scss | 65 ++---------- .../header_section_item_button.test.tsx | 8 -- .../header_section_item_button.tsx | 24 ++--- 6 files changed, 45 insertions(+), 166 deletions(-) diff --git a/src/components/header/_mixins.scss b/src/components/header/_mixins.scss index 385f6674929..129edf89532 100644 --- a/src/components/header/_mixins.scss +++ b/src/components/header/_mixins.scss @@ -30,10 +30,6 @@ } } - .euiHeaderSectionItemButton__inner--hasBackground { - background: $euiColorFullShade; - } - .euiHeaderSectionItemButton__notification--badge { box-shadow: 0 0 0 1px $backgroundColor; } diff --git a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap index 4b384c01bf0..03152dcf9c8 100644 --- a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap +++ b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap @@ -84,15 +84,11 @@ exports[`EuiHeaderLinks popover props is rendered 1`] = ` type="button" > - - + data-euiicon-type="bolt" + /> diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index c50813b5dad..7c767907128 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -8,12 +8,8 @@ exports[`EuiHeaderSectionItemButton is rendered 1`] = ` type="button" > - - + class="" + /> `; @@ -23,37 +19,13 @@ exports[`EuiHeaderSectionItemButton renders animation 1`] = ` type="button" > - - - - -`; - -exports[`EuiHeaderSectionItemButton renders background 1`] = ` - `; @@ -63,14 +35,10 @@ exports[`EuiHeaderSectionItemButton renders children 1`] = ` type="button" > - - - Ahoy! - + + Ahoy! @@ -82,16 +50,12 @@ exports[`EuiHeaderSectionItemButton renders notification as a badge 1`] = ` type="button" > + - - - 1 - + 1 `; @@ -102,17 +66,13 @@ exports[`EuiHeaderSectionItemButton renders notification as a dot 1`] = ` type="button" > - - - + class="" + /> + `; @@ -122,16 +82,12 @@ exports[`EuiHeaderSectionItemButton renders notification color 1`] = ` type="button" > + - - - 1 - + 1 `; diff --git a/src/components/header/header_section/_header_section_item_button.scss b/src/components/header/header_section/_header_section_item_button.scss index 6dff91df0ed..4d41f6c082e 100644 --- a/src/components/header/header_section/_header_section_item_button.scss +++ b/src/components/header/header_section/_header_section_item_button.scss @@ -18,59 +18,22 @@ position: absolute; &--dot { - stroke: $euiHeaderBackgroundColor; - } - - &--badge { - box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; - } -} - -// button without a background -.euiHeaderSectionItemButton__inner { - position: relative; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - - .euiHeaderSectionItemButton__notification--dot { top: 0; right: 0; + stroke: $euiHeaderBackgroundColor; } - .euiHeaderSectionItemButton__notification--badge { + &--badge { top: 9%; right: 9%; + box-shadow: 0 0 0 1px $euiHeaderBackgroundColor; } } -// button with a background -.euiHeaderSectionItemButton__inner--hasBackground { - position: relative; - background: $euiColorLightestShade; - height: $euiSizeXL; - width: $euiSizeXL; - min-width: $euiSizeXL; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - margin: 0 auto; - - .euiHeaderSectionItemButton__notification--dot { - top: -$euiSizeS; - right: -$euiSizeS; - } - - .euiHeaderSectionItemButton__notification--badge { - top: -$euiSizeXS; - right: -$euiSizeXS; - } -} -.euiHeaderSectionItemButton--isAnimating { +.euiHeaderSectionItemButton__content--isAnimating { + // This element is a span and we're changing the display because inline elements can’t take a transform + display: inline-block; animation: 5s euiButtonRinging ease-in-out; } @@ -86,24 +49,12 @@ border-radius: $euiSizeS; color: $euiColorAccent; overflow: hidden; + top: 20%; } // Using specificity to override the default icon size .euiHeaderSectionItemButton__notification.euiHeaderSectionItemButton__notification--dot { @include size($euiSize); - } - - .euiHeaderSectionItemButton__inner { - .euiHeaderSectionItemButton__notification--badge { - top: 20%; - } - } - - .euiHeaderSectionItemButton__inner--hasBackground { - .euiHeaderSectionItemButton__notification--dot, - .euiHeaderSectionItemButton__notification--badge { - top: 0; - right: 0; - } + top: 9%; } } \ No newline at end of file diff --git a/src/components/header/header_section/header_section_item_button.test.tsx b/src/components/header/header_section/header_section_item_button.test.tsx index 420010fabf5..23e36e93059 100644 --- a/src/components/header/header_section/header_section_item_button.test.tsx +++ b/src/components/header/header_section/header_section_item_button.test.tsx @@ -75,14 +75,6 @@ describe('EuiHeaderSectionItemButton', () => { expect(component).toMatchSnapshot(); }); - test('renders background', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - describe('onClick', () => { test("isn't called upon instantiation", () => { const onClickHandler = jest.fn(); diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 66d7c444598..86391b84cf3 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -42,12 +42,6 @@ export type EuiHeaderSectionItemButtonProps = CommonProps & * Changes the color of the notification background */ notificationColor?: EuiNotificationBadgeProps['color']; - - /** - * Adds a circular background to the button to emphasize the content - */ - hasBackground?: boolean; - /** * Pass `true` to trigger an animation */ @@ -68,24 +62,17 @@ export const EuiHeaderSectionItemButton = forwardRef< notification, notificationColor = 'accent', animation = false, - hasBackground = false, ...rest }, ref ) => { const classes = classNames('euiHeaderSectionItemButton', className); const animationClasses = classNames({ - 'euiHeaderSectionItemButton--isAnimating': animation, + 'euiHeaderSectionItemButton__content--isAnimating': animation, }); - const buttonClasses = classNames({ - euiHeaderSectionItemButton__inner: !hasBackground, - 'euiHeaderSectionItemButton__inner--hasBackground': hasBackground, - }); - - const buttonInner = ( - - {children} + const buttonNotification = ( + <> {notification && typeof notification === 'boolean' && ( )} - + ); return ( @@ -112,7 +99,8 @@ export const EuiHeaderSectionItemButton = forwardRef< onClick={onClick} type="button" {...rest}> - {buttonInner} + {children} + {buttonNotification} ); } From e020d7a6e61f4e2c63f6cb6fdbbace72550946ad Mon Sep 17 00:00:00 2001 From: miukimiu Date: Mon, 18 Jan 2021 16:59:27 +0000 Subject: [PATCH 05/22] Using JS do hide and show on xs breakpoints --- .../_header_section_item_button.scss | 10 ------ .../header_section_item_button.tsx | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/header/header_section/_header_section_item_button.scss b/src/components/header/header_section/_header_section_item_button.scss index 4d41f6c082e..8b979b57a1a 100644 --- a/src/components/header/header_section/_header_section_item_button.scss +++ b/src/components/header/header_section/_header_section_item_button.scss @@ -42,16 +42,6 @@ min-width: $euiHeaderChildSize * .75; } - // On small screens just show a small dot indicating there are notifications - .euiHeaderSectionItemButton__notification--badge { - @include size($euiSizeS); - min-width: 0; - border-radius: $euiSizeS; - color: $euiColorAccent; - overflow: hidden; - top: 20%; - } - // Using specificity to override the default icon size .euiHeaderSectionItemButton__notification.euiHeaderSectionItemButton__notification--dot { @include size($euiSize); diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 86391b84cf3..2048e2a988e 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -23,13 +23,13 @@ import React, { forwardRef, } from 'react'; import classNames from 'classnames'; - import { CommonProps } from '../../common'; import { EuiNotificationBadgeProps, EuiNotificationBadge, } from '../../badge/notification_badge/badge_notification'; import { EuiIcon } from '../../icon'; +import { EuiHideFor, EuiShowFor } from '../../responsive'; export type EuiHeaderSectionItemButtonProps = CommonProps & ButtonHTMLAttributes & { @@ -71,23 +71,30 @@ export const EuiHeaderSectionItemButton = forwardRef< 'euiHeaderSectionItemButton__content--isAnimating': animation, }); + const notificationDot = ( + + ); + const buttonNotification = ( <> - {notification && typeof notification === 'boolean' && ( - - )} + {notification && typeof notification === 'boolean' && notificationDot} {notification && typeof notification !== 'boolean' && ( - - {notification} - + <> + + + {notification} + + + {notificationDot} + )} ); From c6f7bc3daf1676248bb2512a90e013a6bef230c6 Mon Sep 17 00:00:00 2001 From: miukimiu Date: Mon, 18 Jan 2021 22:20:30 +0000 Subject: [PATCH 06/22] Adding a snippet --- src-docs/src/views/header/header_alert.js | 3 +-- src-docs/src/views/header/header_example.js | 30 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index 0a9c5401e5b..abd255751ea 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -143,8 +143,7 @@ const HeaderUpdates = ({ }`} onClick={() => showFlyout()} notification={showNotification} - animation={isAnimating} - hasBackground> + animation={isAnimating}> ); diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index f9f6d1d2c9c..385270c11b8 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -67,7 +67,8 @@ const headerSnippet = ` -`; + +`; const headerSectionsSnippet = ` `; +const headerAlertSnippet = ` + + + + + + + + + + showPortalConten()} + notification={showNotification} + animation={isAnimating} + > + + + + +`; + export const HeaderExample = { title: 'Header', sections: [ @@ -279,7 +304,7 @@ export const HeaderExample = { demo: , }, { - title: 'Portal content and buttons in the header ', + title: 'Portal content and buttons in the header', source: [ { type: GuideSectionTypes.JS, @@ -342,6 +367,7 @@ export const HeaderExample = { EuiHeaderAlert, EuiHeaderSectionItemButton, }, + snippet: headerAlertSnippet, demo: , }, { From 41a54c82a0c22b24630a090cba0d0cfec5f4d7ce Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 29 Jan 2021 14:39:13 -0700 Subject: [PATCH 07/22] refactor animation prop into a ref method --- src-docs/src/views/header/header_alert.js | 434 +++++++++--------- .../header_section_item_button.test.tsx.snap | 26 +- .../_header_section_item_button.scss | 6 +- .../header_section_item_button.test.tsx | 14 +- .../header_section_item_button.tsx | 155 ++++++- 5 files changed, 382 insertions(+), 253 deletions(-) diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index abd255751ea..21a3c544af1 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -1,4 +1,10 @@ -import React, { useState } from 'react'; +import React, { + forwardRef, + useCallback, + useImperativeHandle, + useRef, + useState, +} from 'react'; import { EuiAvatar, @@ -30,152 +36,206 @@ import { } from '../../../../src/components'; import { htmlIdGenerator } from '../../../../src/services'; -const HeaderUpdates = ({ - showNotification, - setShowNotification, - notificationsNumber, - isAnimating, -}) => { - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [isPopoverVisible, setIsPopoverVisible] = useState(false); +const HeaderUpdates = forwardRef( + ({ showNotification, setShowNotification, notificationsNumber }, ref) => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [isPopoverVisible, setIsPopoverVisible] = useState(false); - const 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, - }, - ]; + const 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, + }, + ]; - const closeFlyout = () => { - setIsFlyoutVisible(false); - }; + const closeFlyout = () => { + setIsFlyoutVisible(false); + }; - const closePopover = () => { - setIsPopoverVisible(false); - }; + const closePopover = () => { + setIsPopoverVisible(false); + }; - const showFlyout = () => { - setShowNotification(false); - setIsFlyoutVisible(!isFlyoutVisible); - }; + const showFlyout = () => { + setShowNotification(false); + setIsFlyoutVisible(!isFlyoutVisible); + }; - const showPopover = () => { - setShowNotification(false); - setIsPopoverVisible(!isPopoverVisible); - }; + const showPopover = () => { + setShowNotification(false); + setIsPopoverVisible(!isPopoverVisible); + }; - const bellButton = ( - showFlyout()} - notification={showNotification} - animation={isAnimating}> - - - ); + const bellRef = useRef(); + const cheerRef = useRef(); + const animate = useCallback(() => { + bellRef.current?.triggerAnimation(); + cheerRef.current?.triggerAnimation(); + }, []); + useImperativeHandle( + ref, + () => ({ + animate, + }), + [animate] + ); - const cheerButton = ( - showPopover()} - notification={showNotification && notificationsNumber} - animation={isAnimating}> - - - ); + const bellButton = ( + showFlyout()} + notification={showNotification}> + + + ); - const flyout = ( - - closeFlyout()} - size="s" - id="headerFlyoutNewsFeed" - aria-labelledby="flyoutSmallTitle"> - - -

What's new

-
-
- + const cheerButton = ( + showPopover()} + notification={showNotification && notificationsNumber}> + + + ); + + const flyout = ( + + closeFlyout()} + size="s" + id="headerFlyoutNewsFeed" + aria-labelledby="flyoutSmallTitle"> + + +

What's new

+
+
+ + {alerts.map((alert, i) => ( + + ))} + + + + + closeFlyout()} + flush="left"> + Close + + + + +

Version 7.0

+
+
+
+
+
+
+ ); + + const popover = ( + closePopover()} + panelPaddingSize="none"> + What's new +
+ {alerts.map((alert, i) => ( ))} - - - - - closeFlyout()} - flush="left"> - Close - - - - -

Version 7.0

-
-
-
-
- - - ); +
+ + +

Version 7.0

+
+
+
+ ); - const popover = ( - closePopover()} - panelPaddingSize="none"> - What's new -
- - {alerts.map((alert, i) => ( - - ))} -
- - -

Version 7.0

-
-
-
- ); - - return ( - <> - {bellButton} - {popover} - {isFlyoutVisible && flyout} - - ); -}; + return ( + <> + {bellButton} + {popover} + {isFlyoutVisible && flyout} + + ); + } +); +HeaderUpdates.displayName = 'HeaderUpdates'; const HeaderUserMenu = () => { const id = htmlIdGenerator()(); @@ -320,18 +338,10 @@ const HeaderUserMenu = () => { export default () => { const [position, setPosition] = useState('static'); const [showNotification, setShowNotification] = useState(true); - const [isAnimating, setIsAnimating] = useState(false); + const headerUpdatesRef = useRef(); const [theme, setTheme] = useState('light'); const [notificationsNumber, setNotificationsNumber] = useState(1); - const animate = () => { - setIsAnimating(false); - - setTimeout(() => { - setIsAnimating(true); - }, 100); - }; - const notify = () => { if (!showNotification) { setNotificationsNumber(1); @@ -367,7 +377,9 @@ export default () => { - + headerUpdatesRef.current?.animate()}> Animate @@ -383,14 +395,14 @@ export default () => { setShowNotification()} notificationsNumber={notificationsNumber} - isAnimating={isAnimating} /> - + diff --git a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap index 7c767907128..f90543d19f9 100644 --- a/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap +++ b/src/components/header/header_section/__snapshots__/header_section_item_button.test.tsx.snap @@ -8,23 +8,7 @@ exports[`EuiHeaderSectionItemButton is rendered 1`] = ` type="button" > - -`; - -exports[`EuiHeaderSectionItemButton renders animation 1`] = ` - `; @@ -35,7 +19,7 @@ exports[`EuiHeaderSectionItemButton renders children 1`] = ` type="button" > Ahoy! @@ -50,7 +34,7 @@ exports[`EuiHeaderSectionItemButton renders notification as a badge 1`] = ` type="button" > { }); }); - test('renders animation', () => { - const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); + // test('renders animation', () => { + // const component = render( + // + // ); + // + // expect(component).toMatchSnapshot(); + // }); describe('onClick', () => { test("isn't called upon instantiation", () => { diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 2048e2a988e..8517155ac06 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -21,6 +21,9 @@ import React, { ButtonHTMLAttributes, PropsWithChildren, forwardRef, + useImperativeHandle, + useRef, + useState, } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; @@ -42,13 +45,9 @@ export type EuiHeaderSectionItemButtonProps = CommonProps & * Changes the color of the notification background */ notificationColor?: EuiNotificationBadgeProps['color']; - /** - * Pass `true` to trigger an animation - */ - animation?: boolean; }; -export type EuiHeaderSectionItemButtonRef = HTMLButtonElement; +export type EuiHeaderSectionItemButtonRef = HTMLButtonElement | null; export const EuiHeaderSectionItemButton = forwardRef< EuiHeaderSectionItemButtonRef, @@ -61,15 +60,147 @@ export const EuiHeaderSectionItemButton = forwardRef< className, notification, notificationColor = 'accent', - animation = false, ...rest }, ref ) => { + const [buttonRef, setButtonRef] = useState(); + const animationTargetRef = useRef(null); + + useImperativeHandle< + EuiHeaderSectionItemButtonRef, + EuiHeaderSectionItemButtonRef + >( + ref, + () => { + if (buttonRef) { + (buttonRef as any).triggerAnimation = () => { + const keyframes: Keyframe[] = [ + { transform: 'rotate(0)', offset: 0, easing: 'ease-in-out' }, + { + transform: 'rotate(30deg)', + offset: 0.01, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-28deg)', + offset: 0.03, + easing: 'ease-in-out', + }, + { + transform: 'rotate(34deg)', + offset: 0.05, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-32deg)', + offset: 0.07, + easing: 'ease-in-out', + }, + { + transform: 'rotate(30deg)', + offset: 0.09, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-28deg)', + offset: 0.11, + easing: 'ease-in-out', + }, + { + transform: 'rotate(26deg)', + offset: 0.13, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-24deg)', + offset: 0.15, + easing: 'ease-in-out', + }, + { + transform: 'rotate(22deg)', + offset: 0.17, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-20deg)', + offset: 0.19, + easing: 'ease-in-out', + }, + { + transform: 'rotate(18deg)', + offset: 0.21, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-16deg)', + offset: 0.23, + easing: 'ease-in-out', + }, + { + transform: 'rotate(14deg)', + offset: 0.25, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-12deg)', + offset: 0.27, + easing: 'ease-in-out', + }, + { + transform: 'rotate(10deg)', + offset: 0.29, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-8deg)', + offset: 0.31, + easing: 'ease-in-out', + }, + { + transform: 'rotate(6deg)', + offset: 0.33, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-4deg)', + offset: 0.35, + easing: 'ease-in-out', + }, + { + transform: 'rotate(2deg)', + offset: 0.37, + easing: 'ease-in-out', + }, + { + transform: 'rotate(-1deg)', + offset: 0.39, + easing: 'ease-in-out', + }, + { + transform: 'rotate(1deg)', + offset: 0.41, + easing: 'ease-in-out', + }, + { transform: 'rotate(0)', offset: 0.43, easing: 'ease-in-out' }, + { transform: 'rotate(0)', offset: 1, easing: 'ease-in-out' }, + ]; + animationTargetRef.current?.animate(keyframes, { + duration: 5000, + }); + }; + return buttonRef; + } else { + return null; + } + }, + [buttonRef] + ); + const classes = classNames('euiHeaderSectionItemButton', className); - const animationClasses = classNames({ - 'euiHeaderSectionItemButton__content--isAnimating': animation, - }); + const animationClasses = classNames([ + 'euiHeaderSectionItemButton__content', + ]); const notificationDot = ( - {children} + + {children} + {buttonNotification} ); From d5923d320e472ba15974419ef6ebaf273f6c26cf Mon Sep 17 00:00:00 2001 From: miukimiu Date: Mon, 1 Feb 2021 12:48:37 +0000 Subject: [PATCH 08/22] Snapshots. Trigger animation comments. --- src-docs/src/views/header/header_alert.js | 5 +++++ .../header_links/__snapshots__/header_links.test.tsx.snap | 2 +- .../header/header_section/_header_section_item_button.scss | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index 21a3c544af1..46a8595fd0f 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -137,10 +137,15 @@ const HeaderUpdates = forwardRef( const bellRef = useRef(); const cheerRef = useRef(); + + // we're passing passing the `triggerAnimation` callbacks to the `headerUpdatesRef` child components that we want to animate const animate = useCallback(() => { bellRef.current?.triggerAnimation(); cheerRef.current?.triggerAnimation(); }, []); + + // we're using the `useImperativeHandle` which allows the child to expose a function to the parent + // this way we can trigger the animations on both child components: `bellRef` and `cheerRef` useImperativeHandle( ref, () => ({ diff --git a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap index 03152dcf9c8..f64ef0db596 100644 --- a/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap +++ b/src/components/header/header_links/__snapshots__/header_links.test.tsx.snap @@ -84,7 +84,7 @@ exports[`EuiHeaderLinks popover props is rendered 1`] = ` type="button" > Date: Mon, 1 Feb 2021 12:57:41 +0000 Subject: [PATCH 09/22] Deleting unused css animation --- .../header/header_section/_animations.scss | 26 ------------------- .../header/header_section/_index.scss | 1 - 2 files changed, 27 deletions(-) delete mode 100644 src/components/header/header_section/_animations.scss diff --git a/src/components/header/header_section/_animations.scss b/src/components/header/header_section/_animations.scss deleted file mode 100644 index ca7f87d8d30..00000000000 --- a/src/components/header/header_section/_animations.scss +++ /dev/null @@ -1,26 +0,0 @@ -@keyframes euiButtonRinging { - 0% { transform: rotate(0); } - 1% { transform: rotate(30deg); } - 3% { transform: rotate(-28deg); } - 5% { transform: rotate(34deg); } - 7% { transform: rotate(-32deg); } - 9% { transform: rotate(30deg); } - 11% { transform: rotate(-28deg); } - 13% { transform: rotate(26deg); } - 15% { transform: rotate(-24deg); } - 17% { transform: rotate(22deg); } - 19% { transform: rotate(-20deg); } - 21% { transform: rotate(18deg); } - 23% { transform: rotate(-16deg); } - 25% { transform: rotate(14deg); } - 27% { transform: rotate(-12deg); } - 29% { transform: rotate(10deg); } - 31% { transform: rotate(-8deg); } - 33% { transform: rotate(6deg); } - 35% { transform: rotate(-4deg); } - 37% { transform: rotate(2deg); } - 39% { transform: rotate(-1deg); } - 41% { transform: rotate(1deg); } - 43% { transform: rotate(0); } - 100% { transform: rotate(0); } -} \ No newline at end of file diff --git a/src/components/header/header_section/_index.scss b/src/components/header/header_section/_index.scss index 8e83b2f64dc..7008343a138 100644 --- a/src/components/header/header_section/_index.scss +++ b/src/components/header/header_section/_index.scss @@ -1,4 +1,3 @@ -@import 'animations'; @import 'header_section'; @import 'header_section_item'; @import 'header_section_item_button'; \ No newline at end of file From 8253863e18cddb1e92c7aca0c0fe15a9f43ca99a Mon Sep 17 00:00:00 2001 From: miukimiu Date: Mon, 1 Feb 2021 13:34:13 +0000 Subject: [PATCH 10/22] triggerAnimation explanation --- src-docs/src/views/header/header_example.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 385270c11b8..6004ef17d9a 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -343,8 +343,9 @@ export const HeaderExample = { node that will render inside a{' '} EuiBadgeNotification or pass{' '} true to render a simple dot. You can also animate - the button by passing true into the{' '} - animation prop. + the button by calling the triggerAnimation(){' '} + method on the EuiHeaderSectionItemButton{' '} + ref.

From 46a3d77955cca179f011e4e4054367d84dfbfe7a Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 8 Feb 2021 17:57:38 -0500 Subject: [PATCH 11/22] Cleaned up some logic and created a docs example specifically for the notifications --- src-docs/src/views/header/header_alert.js | 443 ++++++++---------- src-docs/src/views/header/header_animate.js | 134 ++++++ src-docs/src/views/header/header_example.js | 80 +++- .../header_section_item_button.tsx | 38 +- 4 files changed, 406 insertions(+), 289 deletions(-) create mode 100644 src-docs/src/views/header/header_animate.js diff --git a/src-docs/src/views/header/header_alert.js b/src-docs/src/views/header/header_alert.js index 46a8595fd0f..c1968b6716c 100644 --- a/src-docs/src/views/header/header_alert.js +++ b/src-docs/src/views/header/header_alert.js @@ -1,15 +1,8 @@ -import React, { - forwardRef, - useCallback, - useImperativeHandle, - useRef, - useState, -} from 'react'; +import React, { useState } from 'react'; import { EuiAvatar, EuiBadge, - EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, @@ -36,211 +29,139 @@ import { } from '../../../../src/components'; import { htmlIdGenerator } from '../../../../src/services'; -const HeaderUpdates = forwardRef( - ({ showNotification, setShowNotification, notificationsNumber }, ref) => { - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [isPopoverVisible, setIsPopoverVisible] = useState(false); +const HeaderUpdates = () => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [isPopoverVisible, setIsPopoverVisible] = useState(false); - const 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, - }, - ]; + const 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, + }, + ]; - const closeFlyout = () => { - setIsFlyoutVisible(false); - }; - - const closePopover = () => { - setIsPopoverVisible(false); - }; - - const showFlyout = () => { - setShowNotification(false); - setIsFlyoutVisible(!isFlyoutVisible); - }; - - const showPopover = () => { - setShowNotification(false); - setIsPopoverVisible(!isPopoverVisible); - }; - - const bellRef = useRef(); - const cheerRef = useRef(); + const closeFlyout = () => { + setIsFlyoutVisible(false); + }; - // we're passing passing the `triggerAnimation` callbacks to the `headerUpdatesRef` child components that we want to animate - const animate = useCallback(() => { - bellRef.current?.triggerAnimation(); - cheerRef.current?.triggerAnimation(); - }, []); + const closePopover = () => { + setIsPopoverVisible(false); + }; - // we're using the `useImperativeHandle` which allows the child to expose a function to the parent - // this way we can trigger the animations on both child components: `bellRef` and `cheerRef` - useImperativeHandle( - ref, - () => ({ - animate, - }), - [animate] - ); + const showFlyout = () => { + setIsFlyoutVisible(!isFlyoutVisible); + }; - const bellButton = ( - showFlyout()} - notification={showNotification}> - - - ); + const showPopover = () => { + setIsPopoverVisible(!isPopoverVisible); + }; - const cheerButton = ( - showPopover()} - notification={showNotification && notificationsNumber}> - - - ); + const bellButton = ( + showFlyout()} + notification={true}> + + + ); - const flyout = ( - - closeFlyout()} - size="s" - id="headerFlyoutNewsFeed" - aria-labelledby="flyoutSmallTitle"> - - -

What's new

- - - - {alerts.map((alert, i) => ( - - ))} - - - - - closeFlyout()} - flush="left"> - Close - - - - -

Version 7.0

-
-
-
-
-
-
- ); + const cheerButton = ( + showPopover()} + notification={6}> + + + ); - const popover = ( - closePopover()} - panelPaddingSize="none"> - What's new -
- + const flyout = ( + + closeFlyout()} + size="s" + id="headerFlyoutNewsFeed" + aria-labelledby="flyoutSmallTitle"> + + +

What's new

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

Version 7.0

-
-
-
- ); + + + + + closeFlyout()} + flush="left"> + Close + + + + +

Version 7.0

+
+
+
+
+ + + ); + + const popover = ( + closePopover()} + panelPaddingSize="none"> + What's new +
+ + {alerts.map((alert, i) => ( + + ))} +
+ + +

Version 7.0

+
+
+
+ ); - return ( - <> - {bellButton} - {popover} - {isFlyoutVisible && flyout} - - ); - } -); -HeaderUpdates.displayName = 'HeaderUpdates'; + return ( + <> + {bellButton} + {popover} + {isFlyoutVisible && flyout} + + ); +}; const HeaderUserMenu = () => { const id = htmlIdGenerator()(); @@ -342,19 +305,7 @@ const HeaderUserMenu = () => { export default () => { const [position, setPosition] = useState('static'); - const [showNotification, setShowNotification] = useState(true); - const headerUpdatesRef = useRef(); const [theme, setTheme] = useState('light'); - const [notificationsNumber, setNotificationsNumber] = useState(1); - - const notify = () => { - if (!showNotification) { - setNotificationsNumber(1); - setShowNotification(true); - } else { - setNotificationsNumber(notificationsNumber + 1); - } - }; return ( <> @@ -374,23 +325,10 @@ export default () => { onChange={(e) => setTheme(e.target.checked ? 'dark' : 'light')} /> - - - - Notify - - - - - headerUpdatesRef.current?.animate()}> - Animate - - + @@ -399,12 +337,7 @@ export default () => { - setShowNotification()} - notificationsNumber={notificationsNumber} - /> + diff --git a/src-docs/src/views/header/header_animate.js b/src-docs/src/views/header/header_animate.js new file mode 100644 index 00000000000..08c5bb584f4 --- /dev/null +++ b/src-docs/src/views/header/header_animate.js @@ -0,0 +1,134 @@ +import React, { + forwardRef, + useCallback, + useImperativeHandle, + useRef, + useState, +} from 'react'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiHeader, + EuiHeaderLogo, + EuiHeaderSectionItemButton, + EuiIcon, + EuiSpacer, +} from '../../../../src/components'; + +const HeaderUpdates = forwardRef( + ({ showNotification, notificationsNumber }, ref) => { + const bellRef = useRef(); + const cheerRef = useRef(); + + // we're passing passing the `triggerAnimation` callbacks to the `headerUpdatesRef` child components that we want to animate + const animate = useCallback(() => { + bellRef.current?.triggerAnimation(); + cheerRef.current?.triggerAnimation(); + }, []); + + // we're using the `useImperativeHandle` which allows the child to expose a function to the parent + // this way we can trigger the animations on both child components: `bellRef` and `cheerRef` + useImperativeHandle( + ref, + () => ({ + animate, + }), + [animate] + ); + + const bellButton = ( + + + + ); + + const cheerButton = ( + + + + ); + + return ( + <> + {bellButton} + {cheerButton} + + ); + } +); +HeaderUpdates.displayName = 'HeaderUpdates'; + +export default () => { + const [showNotification, setShowNotification] = useState(false); + const headerUpdatesRef = useRef(); + const [notificationsNumber, setNotificationsNumber] = useState(0); + + const notify = () => { + if (!showNotification) { + setNotificationsNumber(1); + setShowNotification(true); + } else { + setNotificationsNumber(notificationsNumber + 1); + } + + headerUpdatesRef.current?.animate(); + }; + + return ( + <> + + + + Notify & animate + + + + + { + setShowNotification(false); + setNotificationsNumber(0); + }}> + Reset + + + + + + + Elastic], + borders: 'none', + }, + { + items: [ + setShowNotification()} + notificationsNumber={notificationsNumber} + />, + ], + borders: 'none', + }, + ]} + /> + + ); +}; diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 6004ef17d9a..243decfc1a0 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -37,6 +37,10 @@ import HeaderAlert from './header_alert'; const headerAlertSource = require('!!raw-loader!./header_alert'); const headerAlertHtml = renderToHtml(HeaderAlert); +import HeaderAnimate from './header_animate'; +const headerAnimateSource = require('!!raw-loader!./header_animate'); +const headerAnimateHtml = renderToHtml(HeaderAnimate); + import HeaderLinks from './header_links'; const headerLinksSource = require('!!raw-loader!./header_links'); const headerLinksHtml = renderToHtml(HeaderLinks); @@ -122,7 +126,6 @@ const headerAlertSnippet = ` aria-label="Open portal content" onClick={() => showPortalConten()} notification={showNotification} - animation={isAnimating} >
@@ -130,6 +133,31 @@ const headerAlertSnippet = ` `; +const headerAnimateSnippet = `const bellRef = useRef(); + +// we're passing passing the triggerAnimation callbacks to the EuiHeaderSectionItemButton that we want to animate +const animate = useCallback(() => { + bellRef.current?.triggerAnimation(); +}, []); + +// we're using the useImperativeHandle which allows the child to expose a function to the parent +useImperativeHandle( + ref, + () => ({ + animate, + }), + [animate] +); + +const bellButton = ( + + + +);`; + export const HeaderExample = { title: 'Header', sections: [ @@ -304,7 +332,7 @@ export const HeaderExample = { demo: , }, { - title: 'Portal content and buttons in the header', + title: 'Portal content in the header', source: [ { type: GuideSectionTypes.JS, @@ -336,18 +364,6 @@ export const HeaderExample = { in conjunction with a fixed header, be sure to add the repositionOnScroll prop to the popover.

-

- To alert or notify users about the additional information they are - receiving, use the EuiHeaderSectionItemButton{' '} - notification prop. You can pass a{' '} - node that will render inside a{' '} - EuiBadgeNotification or pass{' '} - true to render a simple dot. You can also animate - the button by calling the triggerAnimation(){' '} - method on the EuiHeaderSectionItemButton{' '} - ref. -

-

The example below shows how to incorporate{' '} EuiHeaderAlert components to show a list of updates @@ -359,8 +375,7 @@ export const HeaderExample = { EuiPopover {' '} - . It also shows how to animate and add different types of - notifications. + .

), @@ -371,6 +386,39 @@ export const HeaderExample = { snippet: headerAlertSnippet, demo: , }, + { + title: 'Header notifications', + source: [ + { + type: GuideSectionTypes.JS, + code: headerAnimateSource, + }, + { + type: GuideSectionTypes.HTML, + code: headerAnimateHtml, + }, + ], + text: ( + <> +

+ To alert or notify users about the additional information they are + receiving, use the EuiHeaderSectionItemButton{' '} + notification prop. You can pass a{' '} + node that will render inside a{' '} + EuiBadgeNotification or pass{' '} + true to render a simple dot. You can also animate + the button by calling the triggerAnimation(){' '} + method on the EuiHeaderSectionItemButton{' '} + ref. +

+ + ), + props: { + EuiHeaderSectionItemButton, + }, + snippet: headerAnimateSnippet, + demo: , + }, { title: 'Stacked headers', source: [ diff --git a/src/components/header/header_section/header_section_item_button.tsx b/src/components/header/header_section/header_section_item_button.tsx index 8517155ac06..526c719ebae 100644 --- a/src/components/header/header_section/header_section_item_button.tsx +++ b/src/components/header/header_section/header_section_item_button.tsx @@ -62,6 +62,9 @@ export const EuiHeaderSectionItemButton = forwardRef< notificationColor = 'accent', ...rest }, + /** + * Allows for animating with .triggerAnimation() + */ ref ) => { const [buttonRef, setButtonRef] = useState(); @@ -211,24 +214,23 @@ export const EuiHeaderSectionItemButton = forwardRef< /> ); - const buttonNotification = ( - <> - {notification && typeof notification === 'boolean' && notificationDot} - - {notification && typeof notification !== 'boolean' && ( - <> - - - {notification} - - - {notificationDot} - - )} - - ); + let buttonNotification; + if (notification === true) { + buttonNotification = notificationDot; + } else if (notification) { + buttonNotification = ( + <> + + + {notification} + + + {notificationDot} + + ); + } return (