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..f66ee37e9b73 --- /dev/null +++ b/src/components/TabSelector/getBackground/types.ts @@ -0,0 +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; + + /** + * 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/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..46e4568b2783 --- /dev/null +++ b/src/components/TabSelector/getOpacity/types.ts @@ -0,0 +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[]; + + /** + * Scene's position, value which we would like to interpolate. + */ + position: Animated.AnimatedInterpolation; + + /** + * Whether the tab is active. + */ + isActive: boolean; +}; + +/** + * 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;