From d0d7526558b51454036f1f4a8fdfc1e3cb06e047 Mon Sep 17 00:00:00 2001 From: ZivSa1 Date: Wed, 29 Apr 2026 16:45:19 +0300 Subject: [PATCH] FloatingButton and ScreenFooter - enhance visibility handling and animations --- .../src/components/floatingButton/index.tsx | 42 +++++++++++++++- .../src/components/screenFooter/index.tsx | 48 +++++++++++++++++-- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/floatingButton/index.tsx b/packages/react-native-ui-lib/src/components/floatingButton/index.tsx index 7951bda202..fd475383bd 100644 --- a/packages/react-native-ui-lib/src/components/floatingButton/index.tsx +++ b/packages/react-native-ui-lib/src/components/floatingButton/index.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, useEffect, useMemo} from 'react'; +import React, {PropsWithChildren, useEffect, useMemo, useState} from 'react'; import {StyleSheet} from 'react-native'; import {asBaseComponent, Constants} from '../../commons/new'; import {LogService} from '../../services'; @@ -84,6 +84,41 @@ const FloatingButton = (props: FloatingButtonProps) => { isAndroidEdgeToEdge, testID } = props; + const [isMounted, setIsMounted] = useState(visible); + const [shouldShowFooter, setShouldShowFooter] = useState(visible); + + useEffect(() => { + let showFrame: number | undefined; + let secondShowFrame: number | undefined; + let hideTimeout: ReturnType | undefined; + + if (visible) { + setIsMounted(true); + setShouldShowFooter(false); + showFrame = requestAnimationFrame(() => { + secondShowFrame = requestAnimationFrame(() => { + setShouldShowFooter(true); + }); + }); + } else { + setShouldShowFooter(false); + hideTimeout = setTimeout(() => { + setIsMounted(false); + }, withoutAnimation ? 0 : duration); + } + + return () => { + if (showFrame !== undefined) { + cancelAnimationFrame(showFrame); + } + if (secondShowFrame !== undefined) { + cancelAnimationFrame(secondShowFrame); + } + if (hideTimeout !== undefined) { + clearTimeout(hideTimeout); + } + }; + }, [visible, duration, withoutAnimation]); useEffect(() => { // eslint-disable-next-line max-len @@ -106,6 +141,9 @@ const FloatingButton = (props: FloatingButtonProps) => { if (!button && !secondaryButton) { return null; } + if (!isMounted) { + return null; + } const renderPrimaryButton = () => { if (!button) { @@ -156,7 +194,7 @@ const FloatingButton = (props: FloatingButtonProps) => { return ( { isStatusBarTranslucentAndroid: isAndroidEdgeToEdge }); const [height, setHeight] = useState(0); - const visibilityTranslateY = useSharedValue(0); + const initialHiddenOffset = 9999; + const visibilityTranslateY = useSharedValue(visible ? 0 : initialHiddenOffset); + const pendingEntranceAnimation = useRef(!visible); // Update visibility translation when visible or height changes useEffect(() => { - visibilityTranslateY.value = withTiming(visible ? 0 : height, {duration: animationDuration}); + let animationFrame: number | undefined; + const hiddenOffset = height > 0 ? height : initialHiddenOffset; + + if (visible) { + if (height === 0) { + pendingEntranceAnimation.current = true; + visibilityTranslateY.value = initialHiddenOffset; + return; + } + + if (pendingEntranceAnimation.current) { + pendingEntranceAnimation.current = false; + visibilityTranslateY.value = height; + animationFrame = requestAnimationFrame(() => { + visibilityTranslateY.value = withTiming(0, {duration: animationDuration}); + }); + } else { + visibilityTranslateY.value = withTiming(0, {duration: animationDuration}); + } + } else { + pendingEntranceAnimation.current = true; + visibilityTranslateY.value = withTiming(hiddenOffset, {duration: animationDuration}); + } + + return () => { + if (animationFrame !== undefined) { + cancelAnimationFrame(animationFrame); + } + }; }, [visible, height, animationDuration, visibilityTranslateY]); // Animated style for STICKY behavior (counters Android system offset + visibility) @@ -237,11 +267,19 @@ const ScreenFooter = (props: ScreenFooterProps) => { ); }, [renderBackground, testID, contentContainerStyle, childrenArray]); + const renderHoistedFooterContent = useCallback(() => { + return ( + + {renderFooterContent()} + + ); + }, [testID, hoistedAnimatedStyle, visible, renderFooterContent]); + if (keyboardBehavior === KeyboardBehavior.HOISTED) { return ( - +