From 8dcd126cc63f64d91cb29cb1aadda7ade7247f5d Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Mon, 18 Nov 2024 16:00:03 +0100 Subject: [PATCH] fix(activity): tab indicator animation --- src/components/Tabs.tsx | 62 +++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx index 26d061dad..bccccc618 100644 --- a/src/components/Tabs.tsx +++ b/src/components/Tabs.tsx @@ -1,6 +1,7 @@ -import React, { ReactElement, memo, useState } from 'react'; +import React, { ReactElement, memo, useEffect, useState } from 'react'; import Animated, { useAnimatedStyle, + useSharedValue, withTiming, } from 'react-native-reanimated'; import { @@ -18,12 +19,10 @@ import colors from '../styles/colors'; import { CaptionB } from '../styles/text'; import { TActivityFilter } from '../utils/activity'; -const tabsGap = 4; - -export type TTab = { - id: string; - filter: TActivityFilter; -}; +export type TTab = { id: string; filter: TActivityFilter }; +type TTabLayout = { width: number; height: number; x: number; y: number }; +type TTabLayouts = Record; +const initialTabLayout: TTabLayout = { width: 0, height: 0, x: 0, y: 0 }; const Tab = ({ text, @@ -62,28 +61,50 @@ const Tabs = ({ onPress: (index: number) => void; }): ReactElement => { const { t } = useTranslation('wallet'); - const [tabWidth, setTabWidth] = useState(0); + const activeTabLayout = useSharedValue(initialTabLayout); + const [layouts, setLayouts] = useState({}); + + useEffect(() => { + // Set the active tab layout when the active tab changes + const layout = layouts[tabs[activeTab].id]; + if (layout) { + activeTabLayout.value = withTiming(layout); + } + }, [activeTab, layouts, tabs, activeTabLayout]); - const animatedTabStyle = useAnimatedStyle(() => { - return { left: withTiming((tabWidth + tabsGap) * activeTab) }; - }, [tabWidth, activeTab]); + const animatedStyle = useAnimatedStyle(() => { + return { + height: activeTabLayout.value.height, + top: activeTabLayout.value.y, + width: activeTabLayout.value.width, + left: activeTabLayout.value.x, + }; + }); - const onLayout = (event: LayoutChangeEvent): void => { - setTabWidth(event.nativeEvent.layout.width); + const handleLayout = ( + id: string, + event: LayoutChangeEvent, + index: number, + ): void => { + const { layout } = event.nativeEvent; + setLayouts((prevLayouts) => ({ ...prevLayouts, [id]: layout })); + + if (index === activeTab) { + // Set the active tab layout on initial render + activeTabLayout.value = layout; + } }; return ( - + {tabs.map((tab, index) => ( handleLayout(tab.id, event, index)} onPress={(): void => onPress(index)} /> ))} @@ -94,7 +115,7 @@ const Tabs = ({ const styles = StyleSheet.create({ root: { flexDirection: 'row', - gap: tabsGap, + gap: 4, }, tab: { flex: 1, @@ -105,10 +126,9 @@ const styles = StyleSheet.create({ paddingVertical: 10, }, activeTab: { - backgroundColor: colors.brand, - height: 2, + borderBottomWidth: 2, + borderColor: colors.brand, position: 'absolute', - top: '95%', zIndex: 1, }, });