From 6a8dd584af8ab557caedde9d3bfe1401a996cc6a Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 4 May 2026 13:05:44 +0300 Subject: [PATCH 01/12] ScreenFooter - revert "local" withoutAnimation --- .../src/components/screenFooter/index.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index 32f51f1d47..5265dbd3fb 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -51,8 +51,6 @@ const ScreenFooter = (props: ScreenFooterProps) => { contentContainerStyle: contentContainerStyleOverride } = props; - const withoutAnimation = animationDuration === 0; - const keyboard = useAnimatedKeyboard({ isNavigationBarTranslucentAndroid: isAndroidEdgeToEdge, isStatusBarTranslucentAndroid: isAndroidEdgeToEdge @@ -239,18 +237,9 @@ const ScreenFooter = (props: ScreenFooterProps) => { ); }, [renderBackground, testID, contentContainerStyle, childrenArray]); - const Container = useMemo(() => { - return withoutAnimation ? View : Animated.View; - }, [withoutAnimation]); - - const containerStyle = useMemo(() => { - return withoutAnimation ? styles.container : [styles.container, hoistedAnimatedStyle]; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [withoutAnimation]); - if (keyboardBehavior === KeyboardBehavior.HOISTED) { return ( - + { revealKeyboardInteractive onHeightChanged={setHeight} /> - + ); } From 8e2b6319e78b528da214b41c6db16b64b24e974e Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 4 May 2026 15:02:17 +0300 Subject: [PATCH 02/12] Refactor keyboardBehavior --- .../src/components/screenFooter/index.tsx | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index 5265dbd3fb..538144b34d 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -237,9 +237,9 @@ const ScreenFooter = (props: ScreenFooterProps) => { ); }, [renderBackground, testID, contentContainerStyle, childrenArray]); - if (keyboardBehavior === KeyboardBehavior.HOISTED) { - return ( - + const renderKeyboardAwareFooter = useCallback(() => { + if (keyboardBehavior === 'hoisted') { + return ( { revealKeyboardInteractive onHeightChanged={setHeight} /> - - ); - } + ); + } else { + return renderFooterContent(); + } + }, [keyboardBehavior, renderFooterContent]); + + const containerStyle = useMemo(() => { + return keyboardBehavior === 'hoisted' + ? [styles.container, hoistedAnimatedStyle] + : [styles.container, stickyAnimatedStyle]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [keyboardBehavior]); return ( - - {renderFooterContent()} + + {renderKeyboardAwareFooter()} ); }; From 94729e4ebaeb4fe76a165cc0e6985ec638d77689 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 4 May 2026 15:02:28 +0300 Subject: [PATCH 03/12] Prettify --- .../src/components/screenFooter/index.tsx | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index 538144b34d..18dacf83e3 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -199,30 +199,28 @@ const ScreenFooter = (props: ScreenFooterProps) => { return null; }, [testID, isSolid, isFading, solidBackgroundStyle]); - const renderChild = useCallback( - (child: React.ReactNode, index: number) => { - if (itemsFit === ItemsFit.FIXED && itemWidth) { - const fixedStyle: ViewStyle = isHorizontal - ? {width: itemWidth, flexShrink: 1, overflow: 'hidden', flexDirection: 'row', justifyContent: 'center'} - : {width: itemWidth, maxWidth: '100%'}; - return ( - - {child} - - ); - } + const renderChild = useCallback((child: React.ReactNode, index: number) => { + if (itemsFit === ItemsFit.FIXED && itemWidth) { + const fixedStyle: ViewStyle = isHorizontal + ? {width: itemWidth, flexShrink: 1, overflow: 'hidden', flexDirection: 'row', justifyContent: 'center'} + : {width: itemWidth, maxWidth: '100%'}; + return ( + + {child} + + ); + } - if (isHorizontal && React.isValidElement(child) && itemsFit === ItemsFit.STRETCH) { - return ( - - {child} - - ); - } - return child; - }, - [itemsFit, itemWidth, isHorizontal] - ); + if (isHorizontal && React.isValidElement(child) && itemsFit === ItemsFit.STRETCH) { + return ( + + {child} + + ); + } + return child; + }, + [itemsFit, itemWidth, isHorizontal]); const childrenArray = React.Children.toArray(children).slice(0, 3).map(renderChild); From f00b787ce79e552e21bfd65f2b7c8fa3b8b446f4 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 08:22:19 +0300 Subject: [PATCH 04/12] Refactor animation into useAnimatedFooterStyle Co-authored-by: Copilot --- .../src/components/screenFooter/index.tsx | 53 +++------------- .../src/components/screenFooter/types.ts | 15 +++-- .../screenFooter/useAnimatedFooterStyle.ts | 62 +++++++++++++++++++ 3 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index 18dacf83e3..148a1e157c 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -1,6 +1,6 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {LayoutChangeEvent, StyleSheet, ViewStyle} from 'react-native'; -import Animated, {useAnimatedKeyboard, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import {Keyboard} from 'uilib-native'; import {SafeAreaContextPackage} from '../../optionalDependencies'; import View from '../view'; @@ -9,6 +9,7 @@ import Assets from '../../assets'; import {Colors, Shadows, Spacings} from '../../style'; import {asBaseComponent, Constants} from '../../commons/new'; import {useKeyboardHeight} from '../../hooks'; +import useAnimatedFooterStyle from './useAnimatedFooterStyle'; import { ScreenFooterProps, ScreenFooterLayouts, @@ -30,7 +31,6 @@ export { KeyboardBehavior, ScreenFooterShadow }; -const androidVersion = Constants.getAndroidVersion(); const ScreenFooter = (props: ScreenFooterProps) => { const { testID, @@ -44,38 +44,18 @@ const ScreenFooter = (props: ScreenFooterProps) => { itemWidth, horizontalItemsDistribution: distribution, visible = true, - animationDuration = 200, + animationDuration: animationDurationProp, shadow = ScreenFooterShadow.SH20, hideDivider = false, - isAndroidEdgeToEdge = !!androidVersion && androidVersion >= 35 ? true : undefined, + isAndroidEdgeToEdge, contentContainerStyle: contentContainerStyleOverride } = props; - const keyboard = useAnimatedKeyboard({ - isNavigationBarTranslucentAndroid: isAndroidEdgeToEdge, - isStatusBarTranslucentAndroid: isAndroidEdgeToEdge - }); - const [height, setHeight] = useState(0); - const visibilityTranslateY = useSharedValue(0); - - // Update visibility translation when visible or height changes - useEffect(() => { - visibilityTranslateY.value = withTiming(visible ? 0 : height, {duration: animationDuration}); - }, [visible, height, animationDuration, visibilityTranslateY]); - - // Animated style for STICKY behavior (counters Android system offset + visibility) - const stickyAnimatedStyle = useAnimatedStyle(() => { - const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; - return { - transform: [{translateY: counterSystemOffset + visibilityTranslateY.value}] - }; - }); - - // Animated style for HOISTED behavior (visibility only, keyboard handled by KeyboardAccessoryView) - const hoistedAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [{translateY: visibilityTranslateY.value}] - }; + const {containerStyle, setHeight} = useAnimatedFooterStyle({ + animationDuration: animationDurationProp, + keyboardBehavior, + visible, + isAndroidEdgeToEdge }); const onLayout = useCallback((event: LayoutChangeEvent) => { @@ -253,13 +233,6 @@ const ScreenFooter = (props: ScreenFooterProps) => { } }, [keyboardBehavior, renderFooterContent]); - const containerStyle = useMemo(() => { - return keyboardBehavior === 'hoisted' - ? [styles.container, hoistedAnimatedStyle] - : [styles.container, stickyAnimatedStyle]; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [keyboardBehavior]); - return ( { ScreenFooter.displayName = 'ScreenFooter'; const styles = StyleSheet.create({ - container: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0 - }, contentContainer: { paddingTop: Spacings.s4, paddingHorizontal: Spacings.s5, diff --git a/packages/react-native-ui-lib/src/components/screenFooter/types.ts b/packages/react-native-ui-lib/src/components/screenFooter/types.ts index 7e13f781ca..ab607ea1b3 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/types.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/types.ts @@ -40,7 +40,15 @@ export enum ScreenFooterShadow { SH30 = 'sh30' } -export interface ScreenFooterProps extends PropsWithChildren<{}> { +export interface AnimatedFooterStyleProps { + /** + * Duration of the show/hide animation in ms. + * @default 200 + */ + animationDuration?: number; +} + +export interface ScreenFooterProps extends AnimatedFooterStyleProps, PropsWithChildren<{}> { /** * Used as testing identifier */ @@ -86,11 +94,6 @@ export interface ScreenFooterProps extends PropsWithChildren<{}> { * If true, the footer is visible. If false, it slides down. */ visible?: boolean; - /** - * Duration of the show/hide animation in ms. - * @default 200 - */ - animationDuration?: number; /** * If true, the footer will respect the safe area (add bottom padding) */ diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts new file mode 100644 index 0000000000..78343d26ba --- /dev/null +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -0,0 +1,62 @@ +import {useAnimatedKeyboard, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {StyleSheet} from 'react-native'; +import {AnimatedFooterStyleProps, ScreenFooterProps} from './types'; +import {Constants} from '../../commons/new'; +import {useEffect, useMemo, useState} from 'react'; + +const androidVersion = Constants.getAndroidVersion(); +const useAnimatedFooterStyle = ( + props: AnimatedFooterStyleProps & Pick +) => { + const { + animationDuration = 200, + keyboardBehavior, + visible, + isAndroidEdgeToEdge = !!androidVersion && androidVersion >= 35 ? true : undefined + } = props; + + const keyboard = useAnimatedKeyboard({ + isNavigationBarTranslucentAndroid: isAndroidEdgeToEdge, + isStatusBarTranslucentAndroid: isAndroidEdgeToEdge + }); + const [height, setHeight] = useState(0); + + const visibilityTranslateY = useSharedValue(0); + + useEffect(() => { + visibilityTranslateY.value = withTiming(visible ? 0 : height, {duration: animationDuration}); + }, [visible, height, animationDuration, visibilityTranslateY]); + + const stickyAnimatedStyle = useAnimatedStyle(() => { + const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; + return { + transform: [{translateY: counterSystemOffset + visibilityTranslateY.value}] + }; + }); + + const hoistedAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [{translateY: visibilityTranslateY.value}] + }; + }); + + const containerStyle = useMemo(() => { + return keyboardBehavior === 'hoisted' + ? [styles.container, hoistedAnimatedStyle] + : [styles.container, stickyAnimatedStyle]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [keyboardBehavior]); + + return {containerStyle, setHeight}; +}; + +export default useAnimatedFooterStyle; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0 + } +}); From b8fe418b1802f92055f1171c87617bc6b9458d5d Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 09:44:50 +0300 Subject: [PATCH 05/12] Unify to a single useAnimatedStyle --- .../screenFooter/useAnimatedFooterStyle.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts index 78343d26ba..b4250c82a1 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -27,23 +27,21 @@ const useAnimatedFooterStyle = ( visibilityTranslateY.value = withTiming(visible ? 0 : height, {duration: animationDuration}); }, [visible, height, animationDuration, visibilityTranslateY]); - const stickyAnimatedStyle = useAnimatedStyle(() => { - const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; - return { - transform: [{translateY: counterSystemOffset + visibilityTranslateY.value}] - }; - }); - - const hoistedAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [{translateY: visibilityTranslateY.value}] - }; + const animatedStyle = useAnimatedStyle(() => { + if (keyboardBehavior === 'hoisted') { + return { + transform: [{translateY: visibilityTranslateY.value}] + }; + } else { + const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; + return { + transform: [{translateY: counterSystemOffset + visibilityTranslateY.value}] + }; + } }); const containerStyle = useMemo(() => { - return keyboardBehavior === 'hoisted' - ? [styles.container, hoistedAnimatedStyle] - : [styles.container, stickyAnimatedStyle]; + return [styles.container, animatedStyle]; // eslint-disable-next-line react-hooks/exhaustive-deps }, [keyboardBehavior]); From 0c64821d53413664c55663ab02c2d6252cde1863 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 09:47:16 +0300 Subject: [PATCH 06/12] Rename to animatedValue --- .../components/screenFooter/useAnimatedFooterStyle.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts index b4250c82a1..c0f153e481 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -21,21 +21,21 @@ const useAnimatedFooterStyle = ( }); const [height, setHeight] = useState(0); - const visibilityTranslateY = useSharedValue(0); + const animatedValue = useSharedValue(0); useEffect(() => { - visibilityTranslateY.value = withTiming(visible ? 0 : height, {duration: animationDuration}); - }, [visible, height, animationDuration, visibilityTranslateY]); + animatedValue.value = withTiming(visible ? 0 : height, {duration: animationDuration}); + }, [visible, height, animationDuration, animatedValue]); const animatedStyle = useAnimatedStyle(() => { if (keyboardBehavior === 'hoisted') { return { - transform: [{translateY: visibilityTranslateY.value}] + transform: [{translateY: animatedValue.value}] }; } else { const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; return { - transform: [{translateY: counterSystemOffset + visibilityTranslateY.value}] + transform: [{translateY: counterSystemOffset + animatedValue.value}] }; } }); From 914a7592c2bff4e1c11c8a5e0f27cd0ff5a43939 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 10:59:58 +0300 Subject: [PATCH 07/12] Add animationType Co-authored-by: Copilot --- .../componentScreens/ScreenFooterScreen.tsx | 24 +++++++++++ .../src/components/screenFooter/index.tsx | 8 +++- .../screenFooter/screenFooter.api.json | 8 +++- .../src/components/screenFooter/types.ts | 15 ++++++- .../screenFooter/useAnimatedFooterStyle.ts | 43 +++++++++++++------ packages/react-native-ui-lib/src/index.ts | 1 + 6 files changed, 81 insertions(+), 18 deletions(-) diff --git a/demo/src/screens/componentScreens/ScreenFooterScreen.tsx b/demo/src/screens/componentScreens/ScreenFooterScreen.tsx index a0e451b935..b703415de4 100644 --- a/demo/src/screens/componentScreens/ScreenFooterScreen.tsx +++ b/demo/src/screens/componentScreens/ScreenFooterScreen.tsx @@ -10,6 +10,7 @@ import { ScreenFooterLayouts, ScreenFooterBackgrounds, KeyboardBehavior, + ScreenFooterAnimationTypeProp, FooterAlignment, HorizontalItemsDistribution, ItemsFit, @@ -88,6 +89,12 @@ const KEYBOARD_BEHAVIOR_OPTIONS = [ {label: 'Hoisted', value: KeyboardBehavior.HOISTED} ]; +const ANIMATION_TYPE_OPTIONS = [ + {label: 'Slide', value: 'slide'}, + {label: 'Fade', value: 'fade'}, + {label: 'None', value: 'none'} +]; + const KEYBOARD_BEHAVIOR_OPTIONS_SPACED = [ {label: 'Sticky', value: KeyboardBehavior.STICKY}, {label: 'Hoisted', value: KeyboardBehavior.HOISTED}, @@ -115,6 +122,7 @@ const ScreenFooterContent = () => { const [layout, setLayout] = useState(ScreenFooterLayouts.HORIZONTAL); const [background, setBackground] = useState(ScreenFooterBackgrounds.SOLID); const [keyboardBehavior, setKeyboardBehavior] = useState(KeyboardBehavior.STICKY); + const [animationType, setAnimationType] = useState('slide'); const [alignment, setAlignment] = useState(FooterAlignment.CENTER); const [horizontalAlignment, setHorizontalAlignment] = useState(FooterAlignment.CENTER); const [distribution, setDistribution] = useState(HorizontalItemsDistribution.STACK); @@ -390,6 +398,21 @@ const ScreenFooterContent = () => { + {/* Animation type */} + + + Animation Type + + + opt.value === animationType)} + onChangeIndex={index => setAnimationType(ANIMATION_TYPE_OPTIONS[index].value)} + /> + + + + {/* Alignment (Cross Axis) */} @@ -478,6 +501,7 @@ const ScreenFooterContent = () => { layout={layout} backgroundType={background} keyboardBehavior={keyboardBehavior} + animationType={animationType} alignment={alignment} horizontalAlignment={horizontalAlignment} horizontalItemsDistribution={distribution} diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index 148a1e157c..8abb4bc19a 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -18,6 +18,7 @@ import { HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, + ScreenFooterAnimationTypeProp, ScreenFooterShadow } from './types'; @@ -29,6 +30,7 @@ export { HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, + ScreenFooterAnimationTypeProp, ScreenFooterShadow }; const ScreenFooter = (props: ScreenFooterProps) => { @@ -44,7 +46,8 @@ const ScreenFooter = (props: ScreenFooterProps) => { itemWidth, horizontalItemsDistribution: distribution, visible = true, - animationDuration: animationDurationProp, + animationDuration, + animationType, shadow = ScreenFooterShadow.SH20, hideDivider = false, isAndroidEdgeToEdge, @@ -52,7 +55,8 @@ const ScreenFooter = (props: ScreenFooterProps) => { } = props; const {containerStyle, setHeight} = useAnimatedFooterStyle({ - animationDuration: animationDurationProp, + animationDuration, + animationType, keyboardBehavior, visible, isAndroidEdgeToEdge diff --git a/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json b/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json index 87b4c7f117..039cc5cb45 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json +++ b/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json @@ -58,10 +58,16 @@ "description": "If true, the footer is visible. If false, it slides down", "default": "true" }, + { + "name": "animationType", + "type": "ScreenFooterAnimationTypeProp", + "description": "The animation type for showing/hiding the footer [slide, fade, none]", + "default": "'slide'" + }, { "name": "animationDuration", "type": "number", - "description": "Duration of the show/hide animation in ms", + "description": "Duration of the show/hide animation in ms (sending 0 will disable the animation)", "default": "200" }, { diff --git a/packages/react-native-ui-lib/src/components/screenFooter/types.ts b/packages/react-native-ui-lib/src/components/screenFooter/types.ts index ab607ea1b3..db30cde33b 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/types.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/types.ts @@ -40,9 +40,22 @@ export enum ScreenFooterShadow { SH30 = 'sh30' } +export enum ScreenFooterAnimation { + NONE = 'none', + SLIDE = 'slide', + FADE = 'fade' +} + +export type ScreenFooterAnimationTypeProp = ScreenFooterAnimation | `${ScreenFooterAnimation}`; + export interface AnimatedFooterStyleProps { /** - * Duration of the show/hide animation in ms. + * The type of animation to use when showing or hiding the footer. + * @default 'slide' + */ + animationType?: ScreenFooterAnimationTypeProp; + /** + * Duration of the show/hide animation in ms (sending 0 will disable the animation). * @default 200 */ animationDuration?: number; diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts index c0f153e481..4c92ee120f 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -1,5 +1,5 @@ import {useAnimatedKeyboard, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import {StyleSheet} from 'react-native'; +import {StyleSheet, ViewStyle} from 'react-native'; import {AnimatedFooterStyleProps, ScreenFooterProps} from './types'; import {Constants} from '../../commons/new'; import {useEffect, useMemo, useState} from 'react'; @@ -9,35 +9,50 @@ const useAnimatedFooterStyle = ( props: AnimatedFooterStyleProps & Pick ) => { const { - animationDuration = 200, + animationType: animationTypeProp = 'slide', + animationDuration: animationDurationProp = 200, keyboardBehavior, visible, isAndroidEdgeToEdge = !!androidVersion && androidVersion >= 35 ? true : undefined } = props; + const animationType = animationDurationProp === 0 ? 'none' : animationTypeProp; + const animationDuration = animationType === 'none' ? 0 : animationDurationProp; + const keyboard = useAnimatedKeyboard({ isNavigationBarTranslucentAndroid: isAndroidEdgeToEdge, isStatusBarTranslucentAndroid: isAndroidEdgeToEdge }); - const [height, setHeight] = useState(0); - const animatedValue = useSharedValue(0); + const [height, setHeight] = useState(0); + const animatedValue = useSharedValue(animationType === 'fade' && visible ? 1 : 0); useEffect(() => { - animatedValue.value = withTiming(visible ? 0 : height, {duration: animationDuration}); - }, [visible, height, animationDuration, animatedValue]); + if (animationType === 'slide') { + animatedValue.value = withTiming(visible ? 0 : height, {duration: animationDuration}); + } else { + animatedValue.value = withTiming(visible ? 1 : 0, {duration: animationDuration}); + } + }, [visible, height, animationDuration, animatedValue, animationType]); const animatedStyle = useAnimatedStyle(() => { - if (keyboardBehavior === 'hoisted') { - return { - transform: [{translateY: animatedValue.value}] - }; + let style: ViewStyle = {}; + let translateY = 0; + if (animationType === 'slide') { + translateY = animatedValue.value; } else { - const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0; - return { - transform: [{translateY: counterSystemOffset + animatedValue.value}] - }; + style = {opacity: animatedValue.value}; } + + if (keyboardBehavior === 'sticky' && Constants.isAndroid) { + translateY += keyboard.height.value; + } + + if (translateY !== 0) { + style.transform = [{translateY}]; + } + + return style; }); const containerStyle = useMemo(() => { diff --git a/packages/react-native-ui-lib/src/index.ts b/packages/react-native-ui-lib/src/index.ts index 23f0cf087b..7261ee5d42 100644 --- a/packages/react-native-ui-lib/src/index.ts +++ b/packages/react-native-ui-lib/src/index.ts @@ -79,6 +79,7 @@ export { HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, + ScreenFooterAnimationTypeProp, ScreenFooterShadow } from './components/screenFooter'; export {default as Gradient, GradientProps, GradientTypes} from './components/gradient'; From 677cbb25e4d99d2e8101023a74e52d09f5de507e Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 11:00:15 +0300 Subject: [PATCH 08/12] Remove keyboardBehavior from deps --- .../src/components/screenFooter/useAnimatedFooterStyle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts index 4c92ee120f..34b1828059 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -58,7 +58,7 @@ const useAnimatedFooterStyle = ( const containerStyle = useMemo(() => { return [styles.container, animatedStyle]; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [keyboardBehavior]); + }, []); return {containerStyle, setHeight}; }; From 6215e6e1eb78bb45eac1b5d2a86a764c5916539e Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 5 May 2026 11:07:59 +0300 Subject: [PATCH 09/12] Add animationType to FloatingButton --- .../src/components/floatingButton/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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..189092c6da 100644 --- a/packages/react-native-ui-lib/src/components/floatingButton/index.tsx +++ b/packages/react-native-ui-lib/src/components/floatingButton/index.tsx @@ -11,7 +11,7 @@ export enum FloatingButtonLayouts { HORIZONTAL = 'Horizontal' } -export interface FloatingButtonProps extends Pick { +export interface FloatingButtonProps extends Pick { /** * Whether the button is visible */ @@ -82,6 +82,7 @@ const FloatingButton = (props: FloatingButtonProps) => { hideBackgroundOverlay, hoisted = Constants.isAndroid, isAndroidEdgeToEdge, + animationType, testID } = props; @@ -162,6 +163,7 @@ const FloatingButton = (props: FloatingButtonProps) => { keyboardBehavior={hoisted ? KeyboardBehavior.HOISTED : KeyboardBehavior.STICKY} isAndroidEdgeToEdge={isAndroidEdgeToEdge} animationDuration={withoutAnimation ? 0 : duration} + animationType={animationType} itemsFit={fullWidth ? ItemsFit.STRETCH : undefined} contentContainerStyle={footerContentContainerStyle} testID={testID} From dc7f16eb6237168d3bbe9cce975bc08597287ea6 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 11 May 2026 12:30:10 +0300 Subject: [PATCH 10/12] Fix visible's doc --- .../src/components/screenFooter/screenFooter.api.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json b/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json index 6bff389bd5..a81d34d717 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json +++ b/packages/react-native-ui-lib/src/components/screenFooter/screenFooter.api.json @@ -55,7 +55,7 @@ { "name": "visible", "type": "boolean", - "description": "If true, the footer is visible. If false, it slides down", + "description": "If true, the footer is visible. If false, the footer is hidden using the configured animation type", "default": "true" }, { From 9bbe2c5c84208901e932401ecd52a8ddd7f4198a Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 11 May 2026 12:57:44 +0300 Subject: [PATCH 11/12] Fix fade\none receiving taps when not visible (Android) --- .../react-native-ui-lib/src/components/screenFooter/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx index b42e67372f..cd4ccf5178 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/index.tsx +++ b/packages/react-native-ui-lib/src/components/screenFooter/index.tsx @@ -244,7 +244,7 @@ const ScreenFooter = (props: ScreenFooterProps) => { testID={testID} style={containerStyle} onLayout={keyboardBehavior === 'hoisted' ? undefined : onLayout} - pointerEvents={keyboardBehavior === 'hoisted' ? (visible ? 'box-none' : 'none') : 'auto'} + pointerEvents={!visible ? 'none' : keyboardBehavior === 'hoisted' ? 'box-none' : 'auto'} > {renderKeyboardAwareFooter()} From fe436f7ec774d746e936e99daba7cf85b6bf27a9 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 11 May 2026 14:21:37 +0300 Subject: [PATCH 12/12] Fix footer no re-appearing on scroll up (Android + remove animation + slide) --- .../src/components/screenFooter/useAnimatedFooterStyle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts index 2e06a74689..cbdcecc1e2 100644 --- a/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts +++ b/packages/react-native-ui-lib/src/components/screenFooter/useAnimatedFooterStyle.ts @@ -50,7 +50,7 @@ const useAnimatedFooterStyle = ( translateY += keyboard.height.value; } - if (translateY !== 0) { + if (animationType === 'slide' || translateY !== 0) { style.transform = [{translateY}]; }