diff --git a/src/components/TabSelector/TabIcon.js b/src/components/TabSelector/TabIcon.js
deleted file mode 100644
index d96ae19897f4..000000000000
--- a/src/components/TabSelector/TabIcon.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Animated, StyleSheet, View} from 'react-native';
-import Icon from '@components/Icon';
-import useTheme from '@hooks/useTheme';
-
-const propTypes = {
- /** Icon to display on tab */
- icon: PropTypes.func,
-
- /** Animated opacity value while the label is inactive state */
- // eslint-disable-next-line
- inactiveOpacity: PropTypes.any,
-
- /** Animated opacity value while the label is in active state */
- // eslint-disable-next-line
- activeOpacity: PropTypes.any,
-};
-
-const defaultProps = {
- icon: '',
- inactiveOpacity: 1,
- activeOpacity: 0,
-};
-
-function TabIcon({icon, activeOpacity, inactiveOpacity}) {
- const theme = useTheme();
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-TabIcon.propTypes = propTypes;
-TabIcon.defaultProps = defaultProps;
-TabIcon.displayName = 'TabIcon';
-
-export default TabIcon;
diff --git a/src/components/TabSelector/TabIcon.tsx b/src/components/TabSelector/TabIcon.tsx
new file mode 100644
index 000000000000..f61001145029
--- /dev/null
+++ b/src/components/TabSelector/TabIcon.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import {Animated, StyleSheet, View} from 'react-native';
+import Icon, {SrcProps} from '@components/Icon';
+import useTheme from '@hooks/useTheme';
+
+type TabIconProps = {
+ /** Icon to display on tab */
+ icon?: (props: SrcProps) => React.ReactNode;
+
+ /** Animated opacity value while the icon is in inactive state */
+ inactiveOpacity?: number | Animated.AnimatedInterpolation;
+
+ /** Animated opacity value while the icon is in active state */
+ activeOpacity?: number | Animated.AnimatedInterpolation;
+};
+
+function TabIcon({icon, activeOpacity = 0, inactiveOpacity = 1}: TabIconProps) {
+ const theme = useTheme();
+ return (
+
+ {icon && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+ );
+}
+
+TabIcon.displayName = 'TabIcon';
+
+export default TabIcon;
diff --git a/src/components/TabSelector/TabLabel.js b/src/components/TabSelector/TabLabel.tsx
similarity index 59%
rename from src/components/TabSelector/TabLabel.js
rename to src/components/TabSelector/TabLabel.tsx
index fdf204011152..40f4dc30bb97 100644
--- a/src/components/TabSelector/TabLabel.js
+++ b/src/components/TabSelector/TabLabel.tsx
@@ -1,28 +1,19 @@
-import PropTypes from 'prop-types';
import React from 'react';
import {Animated, StyleSheet, Text, View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
-const propTypes = {
+type TabLabelProps = {
/** Title of the tab */
- title: PropTypes.string,
+ title?: string;
- /** Animated opacity value while the label is inactive state */
- // eslint-disable-next-line
- inactiveOpacity: PropTypes.any,
+ /** Animated opacity value while the label is in inactive state */
+ inactiveOpacity?: number | Animated.AnimatedInterpolation;
/** Animated opacity value while the label is in active state */
- // eslint-disable-next-line
- activeOpacity: PropTypes.any,
+ activeOpacity?: number | Animated.AnimatedInterpolation;
};
-const defaultProps = {
- title: '',
- inactiveOpacity: 1,
- activeOpacity: 0,
-};
-
-function TabLabel({title, activeOpacity, inactiveOpacity}) {
+function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1}: TabLabelProps) {
const styles = useThemeStyles();
return (
@@ -36,8 +27,6 @@ function TabLabel({title, activeOpacity, inactiveOpacity}) {
);
}
-TabLabel.propTypes = propTypes;
-TabLabel.defaultProps = defaultProps;
TabLabel.displayName = 'TabLabel';
export default TabLabel;
diff --git a/src/components/TabSelector/TabSelector.js b/src/components/TabSelector/TabSelector.tsx
similarity index 70%
rename from src/components/TabSelector/TabSelector.js
rename to src/components/TabSelector/TabSelector.tsx
index 444bb62263d9..28ab15c04e9b 100644
--- a/src/components/TabSelector/TabSelector.js
+++ b/src/components/TabSelector/TabSelector.tsx
@@ -1,42 +1,38 @@
-import PropTypes from 'prop-types';
-import React, {useCallback, useMemo, useState} from 'react';
+import {MaterialTopTabNavigationHelpers} from '@react-navigation/material-top-tabs/lib/typescript/src/types';
+import {TabNavigationState} from '@react-navigation/native';
+import React, {FunctionComponent, useCallback, useEffect, useMemo, useState} from 'react';
+import type {Animated} from 'react-native';
import {View} from 'react-native';
-import _ from 'underscore';
+import {SvgProps} from 'react-native-svg';
import * as Expensicons from '@components/Icon/Expensicons';
+import {LocaleContextProps} from '@components/LocaleContextProvider';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import {RootStackParamList} from '@libs/Navigation/types';
import CONST from '@src/CONST';
import TabSelectorItem from './TabSelectorItem';
-const propTypes = {
+type TabSelectorProps = {
/* Navigation state provided by React Navigation */
- // eslint-disable-next-line react/forbid-prop-types
- state: PropTypes.object.isRequired,
+ state: TabNavigationState;
/* Navigation functions provided by React Navigation */
- navigation: PropTypes.shape({
- navigate: PropTypes.func.isRequired,
- emit: PropTypes.func.isRequired,
- }).isRequired,
+ navigation: MaterialTopTabNavigationHelpers;
/* Callback fired when tab is pressed */
- onTabPress: PropTypes.func,
+ onTabPress?: (name: string) => void;
/* AnimatedValue for the position of the screen while swiping */
- position: PropTypes.shape({
- interpolate: PropTypes.func.isRequired,
- }),
+ position: Animated.AnimatedInterpolation;
};
-const defaultProps = {
- onTabPress: () => {},
- position: {
- interpolate: () => {},
- },
+type IconAndTitle = {
+ icon: FunctionComponent;
+ title: string;
};
-const getIconAndTitle = (route, translate) => {
+function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle {
switch (route) {
case CONST.TAB_REQUEST.MANUAL:
return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')};
@@ -51,9 +47,9 @@ const getIconAndTitle = (route, translate) => {
default:
throw new Error(`Route ${route} has no icon nor title set.`);
}
-};
+}
-const getOpacity = (position, routesLength, tabIndex, active, affectedTabs) => {
+function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) {
const activeValue = active ? 1 : 0;
const inactiveValue = active ? 0 : 1;
@@ -62,13 +58,13 @@ const getOpacity = (position, routesLength, tabIndex, active, affectedTabs) => {
return position.interpolate({
inputRange,
- outputRange: _.map(inputRange, (i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)),
+ outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)),
});
}
return activeValue;
-};
+}
-function TabSelector({state, navigation, onTabPress, position}) {
+function TabSelector({state, navigation, onTabPress = () => {}, position}: TabSelectorProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
@@ -76,13 +72,13 @@ function TabSelector({state, navigation, onTabPress, position}) {
const [affectedAnimatedTabs, setAffectedAnimatedTabs] = useState(defaultAffectedAnimatedTabs);
const getBackgroundColor = useCallback(
- (routesLength, tabIndex, affectedTabs) => {
+ (routesLength: number, tabIndex: number, affectedTabs: number[]) => {
if (routesLength > 1) {
const inputRange = Array.from({length: routesLength}, (v, i) => i);
return position.interpolate({
inputRange,
- outputRange: _.map(inputRange, (i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG)),
+ outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG)),
});
}
return theme.border;
@@ -90,7 +86,7 @@ function TabSelector({state, navigation, onTabPress, position}) {
[theme, position],
);
- React.useEffect(() => {
+ useEffect(() => {
// It is required to wait transition end to reset affectedAnimatedTabs because tabs style is still animating during transition.
setTimeout(() => {
setAffectedAnimatedTabs(defaultAffectedAnimatedTabs);
@@ -99,15 +95,15 @@ function TabSelector({state, navigation, onTabPress, position}) {
return (
- {_.map(state.routes, (route, index) => {
+ {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 isFocused = index === state.index;
+ const isActive = index === state.index;
const {icon, title} = getIconAndTitle(route.name, translate);
const onPress = () => {
- if (isFocused) {
+ if (isActive) {
return;
}
@@ -121,7 +117,7 @@ function TabSelector({state, navigation, onTabPress, position}) {
if (!event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
- navigation.navigate({name: route.name, merge: true});
+ navigation.navigate({key: route.key, merge: true});
}
onTabPress(route.name);
@@ -136,7 +132,7 @@ function TabSelector({state, navigation, onTabPress, position}) {
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
backgroundColor={backgroundColor}
- isFocused={isFocused}
+ isActive={isActive}
/>
);
})}
@@ -144,8 +140,6 @@ function TabSelector({state, navigation, onTabPress, position}) {
);
}
-TabSelector.propTypes = propTypes;
-TabSelector.defaultProps = defaultProps;
TabSelector.displayName = 'TabSelector';
export default TabSelector;
diff --git a/src/components/TabSelector/TabSelectorItem.js b/src/components/TabSelector/TabSelectorItem.tsx
similarity index 55%
rename from src/components/TabSelector/TabSelectorItem.js
rename to src/components/TabSelector/TabSelectorItem.tsx
index 88aa98766fae..c10a7475504f 100644
--- a/src/components/TabSelector/TabSelectorItem.js
+++ b/src/components/TabSelector/TabSelectorItem.tsx
@@ -1,48 +1,35 @@
-import PropTypes from 'prop-types';
import React from 'react';
import {Animated, StyleSheet} from 'react-native';
+import {SrcProps} from '@components/Icon';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useThemeStyles from '@hooks/useThemeStyles';
import TabIcon from './TabIcon';
import TabLabel from './TabLabel';
-const propTypes = {
+type TabSelectorItemProps = {
/** Function to call when onPress */
- onPress: PropTypes.func,
+ onPress?: () => void;
/** Icon to display on tab */
- icon: PropTypes.func,
+ icon?: (props: SrcProps) => React.ReactNode;
/** Title of the tab */
- title: PropTypes.string,
+ title?: string;
/** Animated background color value for the tab button */
- // eslint-disable-next-line
- backgroundColor: PropTypes.any,
+ backgroundColor?: string | Animated.AnimatedInterpolation;
- /** Animated opacity value while the label is inactive state */
- // eslint-disable-next-line
- inactiveOpacity: PropTypes.any,
+ /** Animated opacity value while the tab is in inactive state */
+ inactiveOpacity?: number | Animated.AnimatedInterpolation;
- /** Animated opacity value while the label is in active state */
- // eslint-disable-next-line
- activeOpacity: PropTypes.any,
+ /** Animated opacity value while the tab is in active state */
+ activeOpacity?: number | Animated.AnimatedInterpolation;
/** Whether this tab is active */
- isFocused: PropTypes.bool,
+ isActive?: boolean;
};
-const defaultProps = {
- onPress: () => {},
- icon: () => {},
- title: '',
- backgroundColor: '',
- inactiveOpacity: 1,
- activeOpacity: 0,
- isFocused: false,
-};
-
-function TabSelectorItem({icon, title, onPress, backgroundColor, activeOpacity, inactiveOpacity, isFocused}) {
+function TabSelectorItem({icon, title = '', onPress = () => {}, backgroundColor = '', activeOpacity = 0, inactiveOpacity = 1, isActive = false}: TabSelectorItemProps) {
const styles = useThemeStyles();
return (
{({hovered}) => (
-
+
)}
@@ -69,8 +56,6 @@ function TabSelectorItem({icon, title, onPress, backgroundColor, activeOpacity,
);
}
-TabSelectorItem.propTypes = propTypes;
-TabSelectorItem.defaultProps = defaultProps;
TabSelectorItem.displayName = 'TabSelectorItem';
export default TabSelectorItem;
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 78d724fc4bc5..71f77689042d 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -3686,11 +3686,16 @@ const styles = (theme: ThemeColors) =>
color: isSelected ? theme.text : theme.textSupporting,
} satisfies TextStyle),
- tabBackground: (hovered: boolean, isFocused: boolean, background: string) => ({
+ tabBackground: (hovered: boolean, isFocused: boolean, background: string | Animated.AnimatedInterpolation) => ({
backgroundColor: hovered && !isFocused ? theme.highlightBG : background,
}),
- tabOpacity: (hovered: boolean, isFocused: boolean, activeOpacityValue: number, inactiveOpacityValue: number) => ({
+ tabOpacity: (
+ hovered: boolean,
+ isFocused: boolean,
+ activeOpacityValue: number | Animated.AnimatedInterpolation,
+ inactiveOpacityValue: number | Animated.AnimatedInterpolation,
+ ) => ({
opacity: hovered && !isFocused ? inactiveOpacityValue : activeOpacityValue,
}),