From 41a54c82a0c22b24630a090cba0d0cfec5f4d7ce Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 29 Jan 2021 14:39:13 -0700 Subject: [PATCH] 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} );