From afd7cf9d453bd9bca7086cb3a910bc89d1a82965 Mon Sep 17 00:00:00 2001 From: leon-morival Date: Fri, 3 Oct 2025 10:43:08 +0200 Subject: [PATCH] animated tab bar --- src/components/PressableScale.js | 68 ++++++++++++++++++++++++++++++++ src/navigation/BottomTab.js | 1 + src/navigation/TabBar.js | 36 ++++++++++++----- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 src/components/PressableScale.js diff --git a/src/components/PressableScale.js b/src/components/PressableScale.js new file mode 100644 index 0000000..5819ee4 --- /dev/null +++ b/src/components/PressableScale.js @@ -0,0 +1,68 @@ +import React, { useCallback, useEffect } from "react"; +import { Pressable } from "react-native"; +import Animated, { + cancelAnimation, + useAnimatedStyle, + useSharedValue, + withSpring, +} from "react-native-reanimated"; + +export default function PressableScale({ + children, + onPress, + style, + contentStyle, + hitSlop = 8, + disabled = false, + scaleTo = 1.5, + spring = { damping: 18, stiffness: 220, mass: 0.8 }, + ...rest +}) { + const sv = useSharedValue(1); + + const rStyle = useAnimatedStyle(() => ({ + transform: [{ scale: sv.value }], + })); + + const pressIn = useCallback(() => { + if (disabled) return; + cancelAnimation(sv); + sv.value = 1; + sv.value = withSpring(scaleTo, spring); + }, [disabled, scaleTo, spring, sv]); + + const reset = useCallback(() => { + cancelAnimation(sv); + sv.value = withSpring(1, spring); + }, [spring, sv]); + + useEffect( + () => () => { + cancelAnimation(sv); + sv.value = 1; + }, + [sv] + ); + + return ( + { + onPress?.(e); + reset(); + }} + hitSlop={hitSlop} + disabled={disabled} + style={style} + {...rest} + > + {children} + + ); +} diff --git a/src/navigation/BottomTab.js b/src/navigation/BottomTab.js index 920a466..a8c7ed2 100644 --- a/src/navigation/BottomTab.js +++ b/src/navigation/BottomTab.js @@ -25,6 +25,7 @@ export const BottomTabScreen = () => { animate={{ tintColor: focused ? Palette.white : "#8B8883", }} + transition={{ type: "spring", damping: 18, mass: 0.7 }} style={[ { width: Platform.OS === "web" ? 30 : 24, diff --git a/src/navigation/TabBar.js b/src/navigation/TabBar.js index e1926f1..127754d 100644 --- a/src/navigation/TabBar.js +++ b/src/navigation/TabBar.js @@ -1,5 +1,6 @@ +import { Motion } from "@legendapp/motion"; import * as Haptics from "expo-haptics"; -import React from "react"; +import React, { useState } from "react"; import { Text, TouchableOpacity, View } from "react-native"; import ItemContainer from "../components/ItemContainer/ItemContainer"; @@ -9,6 +10,7 @@ import { FONT_FAMILY } from "../styles/Fonts"; const TabBar = ({ state = {}, descriptors = {}, navigation = {} }) => { const { isWeb } = useLayoutType(); + const [pressedIndex, setPressedIndex] = useState(null); return ( { testID={options.tabBarTestID} onPress={_onPressTabBar} onLongPress={onLongPress} + onPressIn={() => setPressedIndex(index)} + onPressOut={() => setPressedIndex(null)} > - {options?.tabBarIcon({ focused: isFocused })} - - {label} - + {options?.tabBarIcon({ focused: isFocused })} + + {label} + + ) );