-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
[No QA] Create Money Request Header component #18148
Changes from all commits
9a02fcc
6832775
2d52d92
48e99fd
8873246
b31c2d1
f6d9b4b
e7ff900
1867b7e
c59467c
9caca42
04e5b45
854de26
a4a9226
240871a
e58f154
b6f5732
938d002
413a867
036d9e9
89c919a
ead5b8c
acf1257
7d53670
eadd538
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,117 @@ | ||
import React from 'react'; | ||
import {View} from 'react-native'; | ||
import _ from 'underscore'; | ||
import PropTypes from 'prop-types'; | ||
import CONST from '../CONST'; | ||
import reportPropTypes from '../pages/reportPropTypes'; | ||
import participantPropTypes from './participantPropTypes'; | ||
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; | ||
import withLocalize, {withLocalizePropTypes} from './withLocalize'; | ||
import styles from '../styles/styles'; | ||
import SubscriptAvatar from './SubscriptAvatar'; | ||
import * as ReportUtils from '../libs/ReportUtils'; | ||
import Avatar from './Avatar'; | ||
import DisplayNames from './DisplayNames'; | ||
import compose from '../libs/compose'; | ||
import * as OptionsListUtils from '../libs/OptionsListUtils'; | ||
import Text from './Text'; | ||
|
||
const propTypes = { | ||
/** The report currently being looked at */ | ||
report: reportPropTypes, | ||
|
||
/** The policies which the user has access to and which the report could be tied to */ | ||
policies: PropTypes.shape({ | ||
/** Name of the policy */ | ||
name: PropTypes.string, | ||
}), | ||
|
||
/** The size of the avatar */ | ||
size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), | ||
|
||
/** Personal details of all the users */ | ||
personalDetails: PropTypes.objectOf(participantPropTypes), | ||
|
||
...windowDimensionsPropTypes, | ||
...withLocalizePropTypes, | ||
}; | ||
|
||
const defaultProps = { | ||
personalDetails: {}, | ||
policies: {}, | ||
report: null, | ||
size: CONST.AVATAR_SIZE.DEFAULT, | ||
}; | ||
|
||
const AvatarWithDisplayName = (props) => { | ||
const title = ReportUtils.getDisplayNameForParticipant(props.report.ownerEmail, true); | ||
const subtitle = ReportUtils.getChatRoomSubtitle(props.report, props.policies); | ||
const isExpenseReport = ReportUtils.isExpenseReport(props.report); | ||
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies); | ||
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForLogins([props.report.ownerEmail], props.personalDetails); | ||
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(ownerPersonalDetails, false); | ||
return ( | ||
<View> | ||
<View style={[styles.appContentHeaderTitle]}> | ||
{Boolean(props.report && title) && ( | ||
<View | ||
style={[ | ||
styles.flexRow, | ||
styles.alignItemsCenter, | ||
styles.justifyContentBetween, | ||
]} | ||
> | ||
{isExpenseReport ? ( | ||
<SubscriptAvatar | ||
mainAvatar={icons[0]} | ||
secondaryAvatar={icons[1]} | ||
mainTooltip={props.report.ownerEmail} | ||
secondaryTooltip={subtitle} | ||
size={props.size} | ||
/> | ||
) : ( | ||
<Avatar | ||
size={props.size} | ||
source={icons[0].source} | ||
type={icons[0].type} | ||
name={icons[0].name} | ||
containerStyles={props.size === CONST.AVATAR_SIZE.SMALL ? styles.emptyAvatarSmall : styles.emptyAvatar} | ||
/> | ||
)} | ||
<View style={[styles.flex1, styles.flexColumn]}> | ||
<DisplayNames | ||
fullTitle={title} | ||
displayNamesWithTooltips={displayNamesWithTooltips} | ||
tooltipEnabled | ||
numberOfLines={1} | ||
textStyles={[styles.headerText, styles.pre]} | ||
shouldUseFullTitle={isExpenseReport} | ||
/> | ||
{!_.isEmpty(subtitle) && ( | ||
<Text | ||
style={[ | ||
styles.sidebarLinkText, | ||
styles.optionAlternateText, | ||
styles.textLabelSupporting, | ||
styles.pre, | ||
]} | ||
numberOfLines={1} | ||
> | ||
{subtitle} | ||
</Text> | ||
)} | ||
</View> | ||
</View> | ||
)} | ||
</View> | ||
</View> | ||
); | ||
}; | ||
AvatarWithDisplayName.propTypes = propTypes; | ||
AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; | ||
AvatarWithDisplayName.defaultProps = defaultProps; | ||
|
||
export default compose( | ||
withWindowDimensions, | ||
withLocalize, | ||
)(AvatarWithDisplayName); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,9 @@ import ThreeDotsMenu, {ThreeDotsMenuItemPropTypes} from './ThreeDotsMenu'; | |
import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState'; | ||
import withLocalize, {withLocalizePropTypes} from './withLocalize'; | ||
import withKeyboardState, {keyboardStatePropTypes} from './withKeyboardState'; | ||
import AvatarWithDisplayName from './AvatarWithDisplayName'; | ||
import iouReportPropTypes from '../pages/iouReportPropTypes'; | ||
import participantPropTypes from './participantPropTypes'; | ||
|
||
const propTypes = { | ||
/** Title of the Header */ | ||
|
@@ -78,6 +81,25 @@ const propTypes = { | |
total: PropTypes.number, | ||
}), | ||
|
||
/** Whether we should show an avatar */ | ||
shouldShowAvatarWithDisplay: PropTypes.bool, | ||
|
||
/** Report, if we're showing the details for one and using AvatarWithDisplay */ | ||
report: iouReportPropTypes, | ||
|
||
/** Policies, if we're showing the details for a report and need info about it for AvatarWithDisplay */ | ||
policies: PropTypes.shape({ | ||
/** Name of the policy */ | ||
name: PropTypes.string, | ||
}), | ||
|
||
/** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */ | ||
personalDetails: PropTypes.objectOf(participantPropTypes), | ||
|
||
/** Additional styles to render on the container of this component */ | ||
// eslint-disable-next-line react/forbid-prop-types | ||
containerStyles: PropTypes.arrayOf(PropTypes.object), | ||
|
||
...withLocalizePropTypes, | ||
...withDelayToggleButtonStatePropTypes, | ||
...keyboardStatePropTypes, | ||
|
@@ -97,13 +119,18 @@ const defaultProps = { | |
shouldShowThreeDotsButton: false, | ||
shouldShowCloseButton: true, | ||
shouldShowStepCounter: true, | ||
shouldShowAvatarWithDisplay: false, | ||
report: null, | ||
policies: {}, | ||
personalDetails: {}, | ||
guidesCallTaskID: '', | ||
stepCounter: null, | ||
threeDotsMenuItems: [], | ||
threeDotsAnchorPosition: { | ||
top: 0, | ||
left: 0, | ||
}, | ||
containerStyles: [], | ||
}; | ||
|
||
class HeaderWithCloseButton extends Component { | ||
|
@@ -128,7 +155,7 @@ class HeaderWithCloseButton extends Component { | |
|
||
render() { | ||
return ( | ||
<View style={[styles.headerBar, this.props.shouldShowBorderBottom && styles.borderBottom, this.props.shouldShowBackButton && styles.pl2]}> | ||
<View style={[styles.headerBar, this.props.shouldShowBorderBottom && styles.borderBottom, this.props.shouldShowBackButton && styles.pl2, ...this.props.containerStyles]}> | ||
<View style={[ | ||
styles.dFlex, | ||
styles.flexRow, | ||
|
@@ -138,26 +165,37 @@ class HeaderWithCloseButton extends Component { | |
styles.overflowHidden, | ||
]} | ||
> | ||
{this.props.shouldShowBackButton && ( | ||
<Tooltip text={this.props.translate('common.back')}> | ||
<Pressable | ||
onPress={() => { | ||
if (this.props.isKeyboardShown) { | ||
Keyboard.dismiss(); | ||
} | ||
this.props.onBackButtonPress(); | ||
}} | ||
style={[styles.touchableButtonImage]} | ||
> | ||
<Icon src={Expensicons.BackArrow} /> | ||
</Pressable> | ||
</Tooltip> | ||
<View style={[styles.flexRow, styles.flex1]}> | ||
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. Why do we need this wrapper? 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. Explained below down here: https://github.com/Expensify/App/pull/18148/files/7d536701547969e16f47104bc55539703e9ad9ac#r1182854883
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. |
||
{this.props.shouldShowBackButton && ( | ||
<Tooltip text={this.props.translate('common.back')}> | ||
<Pressable | ||
onPress={() => { | ||
if (this.props.isKeyboardShown) { | ||
Keyboard.dismiss(); | ||
} | ||
this.props.onBackButtonPress(); | ||
}} | ||
bondydaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
style={[styles.touchableButtonImage]} | ||
> | ||
<Icon src={Expensicons.BackArrow} /> | ||
</Pressable> | ||
</Tooltip> | ||
)} | ||
{this.props.shouldShowAvatarWithDisplay && ( | ||
<AvatarWithDisplayName | ||
luacmartins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
report={this.props.report} | ||
policies={this.props.policies} | ||
personalDetails={this.props.personalDetails} | ||
/> | ||
)} | ||
</View> | ||
{!this.props.shouldShowAvatarWithDisplay && ( | ||
mountiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Header | ||
title={this.props.title} | ||
subtitle={this.props.stepCounter && this.props.shouldShowStepCounter ? this.props.translate('stepCounter', this.props.stepCounter) : this.props.subtitle} | ||
/> | ||
)} | ||
<Header | ||
title={this.props.title} | ||
subtitle={this.props.stepCounter && this.props.shouldShowStepCounter ? this.props.translate('stepCounter', this.props.stepCounter) : this.props.subtitle} | ||
/> | ||
<View style={[styles.reportOptions, styles.flexRow, styles.pr5]}> | ||
<View style={[styles.reportOptions, styles.flexRow]}> | ||
{ | ||
this.props.shouldShowDownloadButton && ( | ||
<Tooltip text={this.props.translate('common.download')}> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import React from 'react'; | ||
import {View} from 'react-native'; | ||
import PropTypes from 'prop-types'; | ||
import lodashGet from 'lodash/get'; | ||
import HeaderWithCloseButton from './HeaderWithCloseButton'; | ||
import iouReportPropTypes from '../pages/iouReportPropTypes'; | ||
import withLocalize, {withLocalizePropTypes} from './withLocalize'; | ||
import * as ReportUtils from '../libs/ReportUtils'; | ||
import * as Expensicons from './Icon/Expensicons'; | ||
import Text from './Text'; | ||
import participantPropTypes from './participantPropTypes'; | ||
import Avatar from './Avatar'; | ||
import styles from '../styles/styles'; | ||
import themeColors from '../styles/themes/default'; | ||
import CONST from '../CONST'; | ||
import withWindowDimensions from './withWindowDimensions'; | ||
import compose from '../libs/compose'; | ||
import Navigation from '../libs/Navigation/Navigation'; | ||
import ROUTES from '../ROUTES'; | ||
import Icon from './Icon'; | ||
|
||
const propTypes = { | ||
/** The report currently being looked at */ | ||
report: iouReportPropTypes.isRequired, | ||
|
||
/** The policies which the user has access to and which the report could be tied to */ | ||
policies: PropTypes.shape({ | ||
/** Name of the policy */ | ||
name: PropTypes.string, | ||
}).isRequired, | ||
|
||
/** Personal details so we can get the ones for the report participants */ | ||
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, | ||
|
||
/** Whether we're viewing a report with a single transaction in it */ | ||
isSingleTransactionView: PropTypes.bool, | ||
|
||
...withLocalizePropTypes, | ||
}; | ||
|
||
const defaultProps = { | ||
isSingleTransactionView: false, | ||
}; | ||
|
||
const MoneyRequestHeader = (props) => { | ||
const formattedAmount = props.numberFormat(props.report.total / 100, { | ||
style: 'currency', | ||
currency: props.report.currency, | ||
}); | ||
const isSettled = false; // TODO: use ReportUtils.isSettled(props.report.reportID) once that method is added | ||
const isExpenseReport = ReportUtils.isExpenseReport(props.report); | ||
const payeeName = isExpenseReport | ||
? ReportUtils.getPolicyName(props.report, props.policies) | ||
: ReportUtils.getDisplayNameForParticipant(props.report.managerEmail); | ||
const payeeAvatar = isExpenseReport | ||
? ReportUtils.getWorkspaceAvatar(props.report) | ||
: ReportUtils.getAvatar(lodashGet(props.personalDetails, [props.report.managerEmail, 'avatar']), props.personalDetails); | ||
return ( | ||
<View style={[ | ||
{backgroundColor: themeColors.highlightBG}, | ||
styles.pl0, | ||
]} | ||
> | ||
<HeaderWithCloseButton | ||
shouldShowAvatarWithDisplay | ||
shouldShowThreeDotsButton={!isSettled} | ||
luacmartins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
threeDotsMenuItems={[{ | ||
icon: Expensicons.Trashcan, | ||
text: props.translate('common.delete'), | ||
onSelected: () => {}, | ||
}]} | ||
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton} | ||
report={props.report} | ||
policies={props.policies} | ||
personalDetails={props.personalDetails} | ||
containerStyles={[styles.pt5, styles.pb3, styles.pr1]} | ||
shouldShowCloseButton={false} | ||
shouldShowBackButton={props.isSmallScreenWidth} | ||
onBackButtonPress={() => Navigation.navigate(ROUTES.HOME)} | ||
/> | ||
<View style={[styles.ph5, styles.pb5]}> | ||
<Text style={[styles.textLabelSupporting, styles.lh16]}>{props.translate('common.to')}</Text> | ||
<View style={[ | ||
styles.flexRow, | ||
styles.alignItemsCenter, | ||
styles.justifyContentBetween, | ||
styles.pv3, | ||
]} | ||
> | ||
<View style={[ | ||
styles.flexRow, | ||
styles.alignItemsCenter, | ||
styles.justifyContentBetween, | ||
]} | ||
> | ||
<Avatar | ||
luacmartins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
source={payeeAvatar} | ||
type={isExpenseReport ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR} | ||
name={payeeName} | ||
size={CONST.AVATAR_SIZE.HEADER} | ||
/> | ||
<View style={[styles.flexColumn, styles.ml3]}> | ||
<Text | ||
style={[styles.headerText, styles.pre]} | ||
numberOfLines={1} | ||
> | ||
{payeeName} | ||
</Text> | ||
{isExpenseReport && ( | ||
<Text | ||
style={[styles.textLabelSupporting, styles.lh16, styles.pre]} | ||
numberOfLines={1} | ||
> | ||
{props.translate('workspace.common.workspace')} | ||
</Text> | ||
)} | ||
</View> | ||
</View> | ||
<View style={[styles.flexRow]}> | ||
{!props.isSingleTransactionView && ( | ||
<Text style={[styles.newKansasLarge]}>{formattedAmount}</Text> | ||
)} | ||
{isSettled && ( | ||
<View style={styles.moneyRequestHeaderCheckmark}> | ||
<Icon src={Expensicons.Checkmark} fill={themeColors.iconSuccessFill} /> | ||
</View> | ||
)} | ||
</View> | ||
</View> | ||
</View> | ||
</View> | ||
); | ||
}; | ||
|
||
MoneyRequestHeader.displayName = 'MoneyRequestHeader'; | ||
MoneyRequestHeader.propTypes = propTypes; | ||
MoneyRequestHeader.defaultProps = defaultProps; | ||
|
||
export default compose( | ||
withWindowDimensions, | ||
withLocalize, | ||
)(MoneyRequestHeader); |
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.
NAB but if these are only used when
shouldShowAvatarWithDisplay
istrue
then maybe that's a case for having a completely separate componentHeaderWithCloseButtonAndAvavtar
or something like that 🤷.