-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: fix the janky request money tab swipe on web #26022
Changes from all commits
83d8efb
97a8300
152d967
fe59f6d
85e7035
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<View> | ||
<Animated.View style={{opacity: inactiveOpacity}}> | ||
<Icon | ||
src={icon} | ||
fill={themeColors.icon} | ||
/> | ||
</Animated.View> | ||
<Animated.View style={[StyleSheet.absoluteFill, {opacity: activeOpacity}]}> | ||
<Icon | ||
src={icon} | ||
fill={themeColors.iconMenu} | ||
/> | ||
</Animated.View> | ||
</View> | ||
); | ||
} | ||
|
||
TabIcon.propTypes = propTypes; | ||
TabIcon.defaultProps = defaultProps; | ||
TabIcon.displayName = 'TabIcon'; | ||
|
||
export default TabIcon; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<View> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same reply. |
||
<Animated.View style={[{opacity: activeOpacity}]}> | ||
<Text style={styles.tabText(true)}>{title}</Text> | ||
</Animated.View> | ||
<Animated.View style={[StyleSheet.absoluteFill, {opacity: inactiveOpacity}]}> | ||
<Text style={styles.tabText(false)}>{title}</Text> | ||
</Animated.View> | ||
</View> | ||
); | ||
} | ||
|
||
TabLabel.propTypes = propTypes; | ||
TabLabel.defaultProps = defaultProps; | ||
TabLabel.displayName = 'TabLabel'; | ||
|
||
export default TabLabel; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prop description is missing here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
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 ( | ||
<View style={styles.tabSelector}> | ||
{_.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 ( | ||
<TabSelectorItem | ||
isSelected={isFocused} | ||
key={route.name} | ||
title={getTitle(route.name, translate)} | ||
icon={getIcon(route.name)} | ||
onPress={onPress} | ||
activeOpacity={activeOpacity} | ||
inactiveOpacity={inactiveOpacity} | ||
backgroundColor={backgroundColor} | ||
/> | ||
); | ||
})} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this wrapping
<View>
necessary? wouldn't be fragment (<>
) enough?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Without it, the layout breaks.