diff --git a/src/components/TabSelector/TabIcon.js b/src/components/TabSelector/TabIcon.js new file mode 100644 index 000000000000..dae8d6057d3f --- /dev/null +++ b/src/components/TabSelector/TabIcon.js @@ -0,0 +1,47 @@ +import {StyleSheet, View, Animated} from 'react-native'; +import React from 'react'; +import PropTypes from 'prop-types'; +import Icon from '../Icon'; +import themeColors from '../../styles/themes/default'; + +const propTypes = { + /** Icon to display on tab */ + icon: PropTypes.func, + + /** Animated opacity value while the label is inactive state */ + inactiveOpacity: PropTypes.number, + + /** Animated opacity value while the label is in active state */ + activeOpacity: PropTypes.number, +}; + +const defaultProps = { + icon: '', + inactiveOpacity: 1, + activeOpacity: 0, +}; + +function TabIcon({icon, activeOpacity, inactiveOpacity}) { + return ( + + + + + + + + + ); +} + +TabIcon.propTypes = propTypes; +TabIcon.defaultProps = defaultProps; +TabIcon.displayName = 'TabIcon'; + +export default TabIcon; diff --git a/src/components/TabSelector/TabLabel.js b/src/components/TabSelector/TabLabel.js new file mode 100644 index 000000000000..890286b2ee1d --- /dev/null +++ b/src/components/TabSelector/TabLabel.js @@ -0,0 +1,40 @@ +import {StyleSheet, View, Text, Animated} from 'react-native'; +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from '../../styles/styles'; + +const propTypes = { + /** Title of the tab */ + title: PropTypes.string, + + /** Animated opacity value while the label is inactive state */ + inactiveOpacity: PropTypes.number, + + /** Animated opacity value while the label is in active state */ + activeOpacity: PropTypes.number, +}; + +const defaultProps = { + title: '', + inactiveOpacity: 1, + activeOpacity: 0, +}; + +function TabLabel({title, activeOpacity, inactiveOpacity}) { + return ( + + + {title} + + + {title} + + + ); +} + +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.js index 5383253c417b..d50744682027 100644 --- a/src/components/TabSelector/TabSelector.js +++ b/src/components/TabSelector/TabSelector.js @@ -7,6 +7,7 @@ import TabSelectorItem from './TabSelectorItem'; import CONST from '../../CONST'; import useLocalize from '../../hooks/useLocalize'; import styles from '../../styles/styles'; +import themeColors from '../../styles/themes/default'; const propTypes = { /* Navigation state provided by React Navigation */ @@ -21,10 +22,18 @@ const propTypes = { /* Callback fired when tab is pressed */ onTabPress: PropTypes.func, + + /* AnimatedValue for the position of the screen while swiping */ + position: PropTypes.shape({ + interpolate: PropTypes.func.isRequired, + }), }; const defaultProps = { onTabPress: () => {}, + position: { + interpolate: () => {}, + }, }; const getIcon = (route) => { @@ -49,12 +58,44 @@ const getTitle = (route, translate) => { } }; -function TabSelector({state, navigation, onTabPress}) { +const getOpacity = (position, routesLength, tabIndex, active) => { + 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: _.map(inputRange, (i) => (i === tabIndex ? activeValue : inactiveValue)), + }); + } + return activeValue; +}; + +const getBackgroundColor = (position, routesLength, tabIndex) => { + if (routesLength > 1) { + const inputRange = Array.from({length: routesLength}, (v, i) => i); + + return position.interpolate({ + inputRange, + outputRange: _.map(inputRange, (i) => (i === tabIndex ? themeColors.midtone : themeColors.appBG)), + }); + } + return themeColors.midtone; +}; + +function TabSelector({state, navigation, onTabPress, position}) { const {translate} = useLocalize(); + return ( {_.map(state.routes, (route, index) => { - const isFocused = state.index === index; + const activeOpacity = getOpacity(position, state.routes.length, index, true); + const inactiveOpacity = getOpacity(position, state.routes.length, index, false); + const backgroundColor = getBackgroundColor(position, state.routes.length, index); + + const isFocused = index === state.index; const onPress = () => { const event = navigation.emit({ @@ -73,11 +114,13 @@ function TabSelector({state, navigation, onTabPress}) { return ( ); })} diff --git a/src/components/TabSelector/TabSelectorItem.js b/src/components/TabSelector/TabSelectorItem.js index cea59bc2ee65..6f96c5ef5d0a 100644 --- a/src/components/TabSelector/TabSelectorItem.js +++ b/src/components/TabSelector/TabSelectorItem.js @@ -1,10 +1,10 @@ -import {Text} from 'react-native'; +import {Animated} from 'react-native'; import React from 'react'; import PropTypes from 'prop-types'; -import Icon from '../Icon'; -import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; import PressableWithFeedback from '../Pressable/PressableWithFeedback'; +import TabIcon from './TabIcon'; +import TabLabel from './TabLabel'; const propTypes = { /** Function to call when onPress */ @@ -13,34 +13,50 @@ const propTypes = { /** Icon to display on tab */ icon: PropTypes.func, - /** True if tab is the selected item */ - isSelected: PropTypes.bool, - /** Title of the tab */ title: PropTypes.string, + + /** Animated background color value for the tab button */ + // eslint-disable-next-line + backgroundColor: PropTypes.any, + + /** Animated opacity value while the label is inactive state */ + inactiveOpacity: PropTypes.number, + + /** Animated opacity value while the label is in active state */ + activeOpacity: PropTypes.number, }; const defaultProps = { onPress: () => {}, icon: () => {}, - isSelected: false, title: '', + backgroundColor: '', + inactiveOpacity: 1, + activeOpacity: 0, }; -function TabSelectorItem(props) { +const AnimatedPressableWithFeedback = Animated.createAnimatedComponent(PressableWithFeedback); + +function TabSelectorItem({icon, title, onPress, backgroundColor, activeOpacity, inactiveOpacity}) { return ( - - + - {props.title} - + ); } diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 922db11ee702..03ea45b204fa 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -75,11 +75,12 @@ function MoneyRequestSelectorPage(props) { {(canUseScanReceipts || canUseDistanceRequests) && iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST ? ( ( + tabBar={({state, navigation, position}) => ( )} > diff --git a/src/styles/styles.js b/src/styles/styles.js index 6b8799e2065e..d4fc1073bc46 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3673,15 +3673,14 @@ const styles = { height: 450, }, - tabSelectorButton: (isSelected) => ({ + tabSelectorButton: { height: 40, padding: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderRadius: variables.buttonBorderRadius, - backgroundColor: isSelected ? themeColors.midtone : themeColors.appBG, - }), + }, tabSelector: { flexDirection: 'row',