From 6b66dad49344885b2e1ad62bf61fcd404dfb6cde Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 26 May 2022 12:18:17 -0700 Subject: [PATCH] When floating loading spinner isn't visible, don't render it --- .../components/TimelineFloatingHeader.scss | 9 --- .../TimelineFloatingHeader.stories.tsx | 41 +++++++++++ .../conversation/TimelineFloatingHeader.tsx | 70 ++++++++++++++----- 3 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 ts/components/conversation/TimelineFloatingHeader.stories.tsx diff --git a/stylesheets/components/TimelineFloatingHeader.scss b/stylesheets/components/TimelineFloatingHeader.scss index a43d57dc3b5..bc71f9c6554 100644 --- a/stylesheets/components/TimelineFloatingHeader.scss +++ b/stylesheets/components/TimelineFloatingHeader.scss @@ -25,14 +25,5 @@ @include timeline-floating-header-node; margin-top: 12px; padding: 6px; - - &--visible { - opacity: 1; - } - - &--hidden { - opacity: 0; - transition: opacity 0.25s ease-out 0.3s; - } } } diff --git a/ts/components/conversation/TimelineFloatingHeader.stories.tsx b/ts/components/conversation/TimelineFloatingHeader.stories.tsx new file mode 100644 index 00000000000..6d9fcc499fa --- /dev/null +++ b/ts/components/conversation/TimelineFloatingHeader.stories.tsx @@ -0,0 +1,41 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { isBoolean } from 'lodash'; +import { boolean } from '@storybook/addon-knobs'; + +import { setupI18n } from '../../util/setupI18n'; +import enMessages from '../../../_locales/en/messages.json'; + +import type { PropsType } from './TimelineFloatingHeader'; +import { TimelineFloatingHeader } from './TimelineFloatingHeader'; + +const i18n = setupI18n('en', enMessages); + +function booleanOr(value: boolean | undefined, defaultValue: boolean): boolean { + return isBoolean(value) ? value : defaultValue; +} + +const createProps = (overrideProps: Partial = {}): PropsType => ({ + isLoading: boolean('isLoading', booleanOr(overrideProps.isLoading, false)), + style: overrideProps.style, + visible: boolean('visible', booleanOr(overrideProps.visible, false)), + i18n, + timestamp: overrideProps.timestamp || Date.now(), +}); + +const stories = storiesOf('Components/TimelineFloatingHeader', module); + +stories.add('Visible', () => { + return ; +}); + +stories.add('Loading', () => { + return ( + + ); +}); diff --git a/ts/components/conversation/TimelineFloatingHeader.tsx b/ts/components/conversation/TimelineFloatingHeader.tsx index 8aee0e2a111..80e8ff62aab 100644 --- a/ts/components/conversation/TimelineFloatingHeader.tsx +++ b/ts/components/conversation/TimelineFloatingHeader.tsx @@ -4,29 +4,67 @@ import classNames from 'classnames'; import type { CSSProperties, ReactElement } from 'react'; import React, { useEffect, useState } from 'react'; +import { animated, useSpring } from '@react-spring/web'; + import type { LocalizerType } from '../../types/Util'; import { TimelineDateHeader } from './TimelineDateHeader'; import { Spinner } from '../Spinner'; +export type PropsType = Readonly<{ + i18n: LocalizerType; + isLoading: boolean; + style?: CSSProperties; + timestamp: number; + visible: boolean; +}>; + export const TimelineFloatingHeader = ({ i18n, isLoading, style, timestamp, visible, -}: Readonly<{ - i18n: LocalizerType; - isLoading: boolean; - style?: CSSProperties; - timestamp: number; - visible: boolean; -}>): ReactElement => { +}: PropsType): ReactElement => { const [hasRendered, setHasRendered] = useState(false); + const [showSpinner, setShowSpinner] = useState(isLoading); useEffect(() => { setHasRendered(true); }, []); + const [spinnerStyles, spinnerSpringRef] = useSpring( + () => ({ + delay: 300, + duration: 250, + from: { opacity: 1 }, + to: { opacity: 0 }, + onRest: { + opacity: ({ value }) => { + if (value === 0) { + setShowSpinner(false); + } + }, + }, + }), + [isLoading] + ); + + useEffect(() => { + if (isLoading) { + spinnerSpringRef.stop(); + spinnerSpringRef.set({ opacity: 1 }); + setShowSpinner(true); + } + + if (!isLoading && showSpinner) { + spinnerSpringRef.start(); + } + + if (!isLoading && !showSpinner) { + spinnerSpringRef.stop(); + } + }, [isLoading, showSpinner, spinnerSpringRef]); + return (
-
- -
+ {showSpinner && ( + + + + )}
); };