From 95da581f48abe3b5e231ac3563c6269ccd83bb09 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 29 Oct 2024 08:56:12 +0100 Subject: [PATCH] Improve animations --- .../Errors/GlobalErrorsList/index.tsx | 58 ++++++++----------- .../Errors/GlobalErrorsList/styles.ts | 3 +- src/components/Errors/NewErrorCard/index.tsx | 29 ++++++---- .../common/AutoAnimatedContainer/index.tsx | 46 +++++++++++++++ .../common/AutoAnimatedContainer/types.ts | 9 +++ 5 files changed, 98 insertions(+), 47 deletions(-) create mode 100644 src/components/common/AutoAnimatedContainer/index.tsx create mode 100644 src/components/common/AutoAnimatedContainer/types.ts diff --git a/src/components/Errors/GlobalErrorsList/index.tsx b/src/components/Errors/GlobalErrorsList/index.tsx index 86265820f..962c61681 100644 --- a/src/components/Errors/GlobalErrorsList/index.tsx +++ b/src/components/Errors/GlobalErrorsList/index.tsx @@ -1,5 +1,4 @@ -import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { dispatcher } from "../../../dispatcher"; import { getFeatureFlagValue } from "../../../featureFlags"; import { @@ -50,20 +49,8 @@ export const GlobalErrorsList = () => { const { goTo } = useHistory(); const [isSortingMenuOpen, setIsSortingMenuOpen] = useState(false); const listContainerRef = useRef(null); - const [parent, toggleAnimations] = useAutoAnimate({ - duration: PIN_UNPIN_ANIMATION_DURATION - }); const [latestPinChangedId, setLatestPinChangedId] = useState(); - - // useAutoAnimate requires to memoize callback - const getListContainerRef = useCallback( - (el: HTMLDivElement | null) => { - listContainerRef.current = el; - parent(el); - }, - [parent, listContainerRef] - ); - + const [areAnimationsEnabled, setAreAnimationsEnabled] = useState(false); const { environment, backendInfo } = useConfigSelector(); const isDismissEnabled = getFeatureFlagValue( backendInfo, @@ -191,9 +178,8 @@ export const GlobalErrorsList = () => { }; }, [getData]); + // Cleanup errors store slice on unmount useMount(() => { - toggleAnimations(false); - return () => { resetGlobalErrors(); }; @@ -219,14 +205,14 @@ export const GlobalErrorsList = () => { if (isLatestChangedIdInList) { setTimeout(() => { - toggleAnimations(false); + setAreAnimationsEnabled(false); }, PIN_UNPIN_ANIMATION_DURATION); } else { - toggleAnimations(false); + setAreAnimationsEnabled(false); } setLatestPinChangedId(undefined); } - }, [previousList, list, latestPinChangedId, toggleAnimations]); + }, [previousList, list, latestPinChangedId]); // Reset page on filters change useEffect(() => { @@ -305,7 +291,7 @@ export const GlobalErrorsList = () => { }; const handlePinStatusToggle = () => { - toggleAnimations(true); + setAreAnimationsEnabled(true); }; const handlePinStatusChange = (errorId: string) => { @@ -389,20 +375,22 @@ export const GlobalErrorsList = () => { {list.length > 0 ? ( - <> - - {list.map((x) => ( - - ))} - - + + {list.map((x) => ( + + ))} + ) : areAnyFiltersApplied ? ( (null); const { backendInfo } = useConfigSelector(); + const [isPinned, setIsPinned] = useState(Boolean(data.pinnedAt)); const isOccurrenceChartEnabled = getFeatureFlagValue( backendInfo, @@ -54,15 +55,6 @@ export const NewErrorCard = ({ FeatureFlag.IS_GLOBAL_ERROR_DISMISS_ENABLED ); - const { - pin, - unpin, - data: pinUnpinResponse, - isInProgress: isPinUnpinInProgress - } = usePinning(data.id); - - const previousPinUnpinResponse = usePrevious(pinUnpinResponse); - const { id, affectedEndpoints, @@ -73,8 +65,19 @@ export const NewErrorCard = ({ status, firstDetected, lastDetected, - isDismissed + isDismissed, + pinnedAt } = data; + + const { + pin, + unpin, + data: pinUnpinResponse, + isInProgress: isPinUnpinInProgress + } = usePinning(id); + + const previousPinUnpinResponse = usePrevious(pinUnpinResponse); + const statusTagType = getTagType(score.score); const { isDismissalChangeInProgress, @@ -125,6 +128,10 @@ export const NewErrorCard = ({ } }, [onPinStatusChange, pinUnpinResponse, previousPinUnpinResponse]); + useEffect(() => { + setIsPinned(Boolean(pinnedAt)); + }, [pinnedAt]); + const handleLinkClick = () => { sendUserActionTrackingEvent(trackingEvents.ERROR_CARD_SOURCE_LINK_CLICKED); onSourceLinkClick(id); @@ -180,6 +187,7 @@ export const NewErrorCard = ({ unpin(); } + setIsPinned(value); onPinStatusToggle(); }; @@ -198,7 +206,6 @@ export const NewErrorCard = ({ }; const isCritical = score.score > HIGH_SEVERITY_SCORE_THRESHOLD; - const isPinned = Boolean(data.pinnedAt); const selectorValue = selectedEndpoint ? getEndpointKey(selectedEndpoint) diff --git a/src/components/common/AutoAnimatedContainer/index.tsx b/src/components/common/AutoAnimatedContainer/index.tsx new file mode 100644 index 000000000..6c10463bf --- /dev/null +++ b/src/components/common/AutoAnimatedContainer/index.tsx @@ -0,0 +1,46 @@ +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { ForwardedRef, forwardRef, useCallback, useEffect } from "react"; +import { useMount } from "../../../hooks/useMount"; +import { AutoAnimatedContainerProps } from "./types"; + +export const AutoAnimatedContainerComponent = ( + { + children, + isAnimationEnabled, + animationOptions, + className + }: AutoAnimatedContainerProps, + ref: ForwardedRef +) => { + const [parent, enable] = useAutoAnimate(animationOptions); + + // Memoization is required to avoid React "Maximum update depth exceeded" error + // More info: https://github.com/formkit/auto-animate/issues/166 + const getRef = useCallback( + (element: HTMLDivElement | null) => { + if (typeof ref === "function") { + ref(element); + } else if (ref) { + ref.current = element; + } + parent(element); + }, + [parent, ref] + ); + + useMount(() => { + enable(isAnimationEnabled); + }); + + useEffect(() => { + enable(isAnimationEnabled); + }, [enable, isAnimationEnabled]); + + return ( +
+ {children} +
+ ); +}; + +export const AutoAnimatedContainer = forwardRef(AutoAnimatedContainerComponent); diff --git a/src/components/common/AutoAnimatedContainer/types.ts b/src/components/common/AutoAnimatedContainer/types.ts new file mode 100644 index 000000000..131e87919 --- /dev/null +++ b/src/components/common/AutoAnimatedContainer/types.ts @@ -0,0 +1,9 @@ +import { AutoAnimateOptions } from "@formkit/auto-animate"; +import { ReactNode } from "react"; + +export interface AutoAnimatedContainerProps { + isAnimationEnabled: boolean; + children: ReactNode; + animationOptions?: Partial; + className?: string; +}