From fba2945c9794f62cac08fcd437f9fcc498f509ec Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 17 Oct 2024 15:47:02 +0200 Subject: [PATCH 1/2] fix: remove interpolation from web platoform as animations are disabled --- src/components/TabSelector/TabSelector.tsx | 41 +++---------------- .../TabSelector/getBackground/index.native.ts | 17 ++++++++ .../TabSelector/getBackground/index.ts | 9 ++++ .../TabSelector/getBackground/types.ts | 15 +++++++ .../TabSelector/getOpacity/index.native.ts | 18 ++++++++ .../TabSelector/getOpacity/index.ts | 13 ++++++ .../TabSelector/getOpacity/types.ts | 14 +++++++ 7 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 src/components/TabSelector/getBackground/index.native.ts create mode 100644 src/components/TabSelector/getBackground/index.ts create mode 100644 src/components/TabSelector/getBackground/types.ts create mode 100644 src/components/TabSelector/getOpacity/index.native.ts create mode 100644 src/components/TabSelector/getOpacity/index.ts create mode 100644 src/components/TabSelector/getOpacity/types.ts diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index 1bf753cd4aa4..df109763b647 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -1,6 +1,5 @@ import type {MaterialTopTabBarProps} from '@react-navigation/material-top-tabs/lib/typescript/src/types'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import type {Animated} from 'react-native'; +import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -10,6 +9,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; +import getBackgroundColor from './getBackground'; +import getOpacity from './getOpacity'; import TabSelectorItem from './TabSelectorItem'; type TabSelectorProps = MaterialTopTabBarProps & { @@ -50,21 +51,6 @@ function getIconAndTitle(route: string, translate: LocaleContextProps['translate } } -function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) { - const activeValue = active ? 1 : 0; - const inactiveValue = active ? 0 : 1; - - if (routesLength > 1) { - const inputRange = Array.from({length: routesLength}, (v, i) => i); - - return position.interpolate({ - inputRange, - outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)), - }); - } - return activeValue; -} - function TabSelector({state, navigation, onTabPress = () => {}, position, onFocusTrapContainerElementChanged}: TabSelectorProps) { const {translate} = useLocalize(); const theme = useTheme(); @@ -72,21 +58,6 @@ function TabSelector({state, navigation, onTabPress = () => {}, position, onFocu const defaultAffectedAnimatedTabs = useMemo(() => Array.from({length: state.routes.length}, (v, i) => i), [state.routes.length]); const [affectedAnimatedTabs, setAffectedAnimatedTabs] = useState(defaultAffectedAnimatedTabs); - const getBackgroundColor = useCallback( - (routesLength: number, tabIndex: number, affectedTabs: number[]) => { - if (routesLength > 1) { - const inputRange = Array.from({length: routesLength}, (v, i) => i); - - return position.interpolate({ - inputRange, - outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG)), - }) as unknown as Animated.AnimatedInterpolation; - } - return theme.border; - }, - [theme, position], - ); - useEffect(() => { // It is required to wait transition end to reset affectedAnimatedTabs because tabs style is still animating during transition. setTimeout(() => { @@ -98,10 +69,10 @@ function TabSelector({state, navigation, onTabPress = () => {}, position, onFocu {state.routes.map((route, index) => { - const activeOpacity = getOpacity(position, state.routes.length, index, true, affectedAnimatedTabs); - const inactiveOpacity = getOpacity(position, state.routes.length, index, false, affectedAnimatedTabs); - const backgroundColor = getBackgroundColor(state.routes.length, index, affectedAnimatedTabs); const isActive = index === state.index; + const activeOpacity = getOpacity({routesLength: state.routes.length, tabIndex: index, active: true, affectedTabs: affectedAnimatedTabs, position, isActive}); + const inactiveOpacity = getOpacity({routesLength: state.routes.length, tabIndex: index, active: false, affectedTabs: affectedAnimatedTabs, position, isActive}); + const backgroundColor = getBackgroundColor({routesLength: state.routes.length, tabIndex: index, affectedTabs: affectedAnimatedTabs, theme, position, isActive}); const {icon, title} = getIconAndTitle(route.name, translate); const onPress = () => { diff --git a/src/components/TabSelector/getBackground/index.native.ts b/src/components/TabSelector/getBackground/index.native.ts new file mode 100644 index 000000000000..09a9b3f347e6 --- /dev/null +++ b/src/components/TabSelector/getBackground/index.native.ts @@ -0,0 +1,17 @@ +import type {Animated} from 'react-native'; +import type GetBackgroudColor from './types'; + +const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, position}) => { + if (routesLength > 1) { + const inputRange = Array.from({length: routesLength}, (v, i) => i); + return position?.interpolate({ + inputRange, + outputRange: inputRange.map((i) => { + return affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG; + }), + }) as unknown as Animated.AnimatedInterpolation; + } + return theme.border; +}; + +export default getBackgroundColor; diff --git a/src/components/TabSelector/getBackground/index.ts b/src/components/TabSelector/getBackground/index.ts new file mode 100644 index 000000000000..2eb60a5115a1 --- /dev/null +++ b/src/components/TabSelector/getBackground/index.ts @@ -0,0 +1,9 @@ +import type GetBackgroudColor from './types'; + +const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, isActive}) => { + if (routesLength > 1) { + return affectedTabs.includes(tabIndex) && isActive ? theme.border : theme.appBG; + } + return theme.border; +}; +export default getBackgroundColor; diff --git a/src/components/TabSelector/getBackground/types.ts b/src/components/TabSelector/getBackground/types.ts new file mode 100644 index 000000000000..5fc368eda253 --- /dev/null +++ b/src/components/TabSelector/getBackground/types.ts @@ -0,0 +1,15 @@ +import type {Animated} from 'react-native'; +import type {ThemeColors} from '@styles/theme/types'; + +type GetBackgroudColorConfig = { + routesLength: number; + tabIndex: number; + affectedTabs: number[]; + theme: ThemeColors; + position?: Animated.AnimatedInterpolation; + isActive?: boolean; +}; + +type GetBackgroudColor = (args: GetBackgroudColorConfig) => Animated.AnimatedInterpolation | string; + +export default GetBackgroudColor; diff --git a/src/components/TabSelector/getOpacity/index.native.ts b/src/components/TabSelector/getOpacity/index.native.ts new file mode 100644 index 000000000000..0da5455214c9 --- /dev/null +++ b/src/components/TabSelector/getOpacity/index.native.ts @@ -0,0 +1,18 @@ +import type GetOpacity from './types'; + +const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, position, isActive}) => { + const activeValue = active ? 1 : 0; + const inactiveValue = active ? 0 : 1; + + if (routesLength > 1) { + const inputRange = Array.from({length: routesLength}, (v, i) => i); + + return position?.interpolate({ + inputRange, + outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex && isActive ? activeValue : inactiveValue)), + }); + } + return activeValue; +}; + +export default getOpacity; diff --git a/src/components/TabSelector/getOpacity/index.ts b/src/components/TabSelector/getOpacity/index.ts new file mode 100644 index 000000000000..d9f3a2eb6167 --- /dev/null +++ b/src/components/TabSelector/getOpacity/index.ts @@ -0,0 +1,13 @@ +import type GetOpacity from './types'; + +const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, isActive}) => { + const activeValue = active ? 1 : 0; + const inactiveValue = active ? 0 : 1; + + if (routesLength > 1) { + return affectedTabs.includes(tabIndex) && isActive ? activeValue : inactiveValue; + } + return activeValue; +}; + +export default getOpacity; diff --git a/src/components/TabSelector/getOpacity/types.ts b/src/components/TabSelector/getOpacity/types.ts new file mode 100644 index 000000000000..5f4a881c3ef5 --- /dev/null +++ b/src/components/TabSelector/getOpacity/types.ts @@ -0,0 +1,14 @@ +import type {Animated} from 'react-native'; + +type GetOpacityConfig = { + routesLength: number; + tabIndex: number; + active: boolean; + affectedTabs: number[]; + position?: Animated.AnimatedInterpolation; + isActive?: boolean; +}; + +type GetOpacity = (args: GetOpacityConfig) => 1 | 0 | Animated.AnimatedInterpolation | undefined; + +export default GetOpacity; From 53b76c9f4ef7073c0707eb5b0cae60b0fccd81f1 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 18 Oct 2024 10:36:46 +0200 Subject: [PATCH 2/2] fix comments --- .../TabSelector/getBackground/types.ts | 35 +++++++++++++++++- .../TabSelector/getOpacity/types.ts | 37 +++++++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/components/TabSelector/getBackground/types.ts b/src/components/TabSelector/getBackground/types.ts index 5fc368eda253..f66ee37e9b73 100644 --- a/src/components/TabSelector/getBackground/types.ts +++ b/src/components/TabSelector/getBackground/types.ts @@ -1,15 +1,46 @@ import type {Animated} from 'react-native'; import type {ThemeColors} from '@styles/theme/types'; +/** + * Configuration for the getBackgroundColor function. + */ type GetBackgroudColorConfig = { + /** + * The number of routes. + */ routesLength: number; + + /** + * The index of the current tab. + */ tabIndex: number; + + /** + * The indices of the affected tabs. + */ affectedTabs: number[]; + + /** + * The theme colors. + */ theme: ThemeColors; - position?: Animated.AnimatedInterpolation; - isActive?: boolean; + + /** + * The animated position interpolation. + */ + position: Animated.AnimatedInterpolation; + + /** + * Whether the tab is active. + */ + isActive: boolean; }; +/** + * Function to get the background color. + * @param args - The configuration for the background color. + * @returns The interpolated background color or a string. + */ type GetBackgroudColor = (args: GetBackgroudColorConfig) => Animated.AnimatedInterpolation | string; export default GetBackgroudColor; diff --git a/src/components/TabSelector/getOpacity/types.ts b/src/components/TabSelector/getOpacity/types.ts index 5f4a881c3ef5..46e4568b2783 100644 --- a/src/components/TabSelector/getOpacity/types.ts +++ b/src/components/TabSelector/getOpacity/types.ts @@ -1,14 +1,45 @@ import type {Animated} from 'react-native'; +/** + * Configuration for the getOpacity function. + */ type GetOpacityConfig = { + /** + * The number of routes in the tab bar. + */ routesLength: number; + + /** + * The index of the tab. + */ tabIndex: number; + + /** + * Whether we are calculating the opacity for the active tab. + */ active: boolean; + + /** + * The indexes of the tabs that are affected by the animation. + */ affectedTabs: number[]; - position?: Animated.AnimatedInterpolation; - isActive?: boolean; + + /** + * Scene's position, value which we would like to interpolate. + */ + position: Animated.AnimatedInterpolation; + + /** + * Whether the tab is active. + */ + isActive: boolean; }; -type GetOpacity = (args: GetOpacityConfig) => 1 | 0 | Animated.AnimatedInterpolation | undefined; +/** + * Function to get the opacity. + * @param args - The configuration for the opacity. + * @returns The interpolated opacity or a fixed value (1 or 0). + */ +type GetOpacity = (args: GetOpacityConfig) => 1 | 0 | Animated.AnimatedInterpolation; export default GetOpacity;