Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/components/ApprovalWorkflowSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import Icon from './Icon';
import MenuItem from './MenuItem';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Text from './Text';
import UserPill from './UserPill';
import UserPills from './UserPills';

type ApprovalWorkflowSectionProps = {
/** Single workflow displayed in this component */
Expand All @@ -38,11 +40,15 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CU
const approverTitle = (index: number) =>
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`;

const members = approvalWorkflow.isDefault
? translate('workspace.common.everyone')
: sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare)
.map((m) => Str.removeSMSDomain(m.displayName))
.join(', ');
const sortedMembers = approvalWorkflow.isDefault ? [] : sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare);

const members = approvalWorkflow.isDefault ? translate('workspace.common.everyone') : sortedMembers.map((m) => Str.removeSMSDomain(m.displayName)).join(', ');

const memberPills = sortedMembers.map((m) => ({
avatar: m.avatar,
displayName: m.displayName,
email: m.email,
}));
return (
<PressableWithoutFeedback
accessibilityRole="button"
Expand Down Expand Up @@ -76,7 +82,7 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CU
style={styles.p0}
titleStyle={styles.textLabelSupportingNormal}
descriptionTextStyle={[styles.textNormalThemeText, styles.lineHeightXLarge]}
description={members}
description={approvalWorkflow.isDefault ? members : undefined}
numberOfLinesDescription={4}
shouldBeAccessible={false}
tabIndex={-1}
Expand All @@ -86,6 +92,7 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CU
iconFill={theme.icon}
onPress={onPress}
shouldRemoveBackground
titleComponent={!approvalWorkflow.isDefault ? <UserPills users={memberPills} /> : undefined}
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.WORKFLOWS.APPROVAL_SECTION_EXPENSES_FROM}
/>

Expand All @@ -98,7 +105,6 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CU
style={styles.p0}
titleStyle={styles.textLabelSupportingNormal}
descriptionTextStyle={[styles.textNormalThemeText, styles.lineHeightXLarge]}
description={Str.removeSMSDomain(approver.displayName)}
icon={icons.UserCheck}
shouldBeAccessible={false}
tabIndex={-1}
Expand All @@ -108,6 +114,13 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CU
iconFill={theme.icon}
onPress={onPress}
shouldRemoveBackground
titleComponent={
<UserPill
avatar={approver.avatar}
displayName={approver.displayName}
email={approver.email}
/>
}
helperText={getApprovalLimitDescription({approver, currency, translate, personalDetailsByEmail})}
helperTextStyle={styles.workflowApprovalLimitText}
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.WORKFLOWS.APPROVAL_SECTION_APPROVER}
Expand Down
16 changes: 14 additions & 2 deletions src/components/MoneyRequestConfirmationListFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import ReceiptImage from './ReceiptImage';
import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from './ShowContextMenuContext';
import Text from './Text';
import TextInput from './TextInput';
import UserPills from './UserPills';

type MoneyRequestConfirmationListFooterProps = {
/** The action to perform */
Expand Down Expand Up @@ -1172,10 +1173,22 @@ function MoneyRequestConfirmationListFooter({
<MenuItemWithTopDescription
key="attendees"
shouldShowRightIcon={!isReadOnly}
title={iouAttendees?.map((item) => item?.displayName ?? item?.login).join(', ')}
accessibilityLabel={iouAttendees?.map((a) => a?.displayName ?? a?.login).join(', ')}
description={`${translate('iou.attendees')} ${
iouAttendees?.length && iouAttendees.length > 1 && formattedAmountPerAttendee ? `\u00B7 ${formattedAmountPerAttendee} ${translate('common.perPerson')}` : ''
}`}
titleComponent={
Array.isArray(iouAttendees) ? (
<UserPills
users={iouAttendees.map((a) => ({
avatar: a?.avatarUrl,
displayName: a?.displayName ?? a?.login ?? a?.email ?? '',
accountID: a?.accountID,
email: a?.email ?? a?.login,
}))}
/>
) : undefined
}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
Expand All @@ -1186,7 +1199,6 @@ function MoneyRequestConfirmationListFooter({
Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute()));
}}
interactive={!isReadOnly}
shouldRenderAsHTML
brickRoadIndicator={shouldDisplayAttendeesError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={shouldDisplayAttendeesError ? translate(formError) : ''}
sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.ATTENDEES_FIELD}
Expand Down
16 changes: 14 additions & 2 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import {useSearchStateContext} from '@components/Search/SearchContext';
import Switch from '@components/Switch';
import Text from '@components/Text';
import UserPills from '@components/UserPills';
import ViolationMessages from '@components/ViolationMessages';
import {useWideRHPState} from '@components/WideRHPContextProvider';
import useActiveRoute from '@hooks/useActiveRoute';
Expand Down Expand Up @@ -1169,12 +1170,24 @@ function MoneyRequestView({
<OfflineWithFeedback pendingAction={getPendingFieldAction('attendees')}>
<MenuItemWithTopDescription
key="attendees"
title={getAttendeesTitle}
accessibilityLabel={getAttendeesTitle}
description={`${translate('iou.attendees')} ${
Array.isArray(actualAttendees) && actualAttendees.length > 1 && formattedPerAttendeeAmount
? `${CONST.DOT_SEPARATOR} ${formattedPerAttendeeAmount} ${translate('common.perPerson')}`
: ''
}`}
titleComponent={
Array.isArray(actualAttendees) ? (
<UserPills
users={actualAttendees.map((a) => ({
avatar: a?.avatarUrl,
displayName: a?.displayName ?? a?.login ?? a?.email ?? '',
accountID: a?.accountID,
email: a?.email ?? a?.login,
}))}
/>
) : undefined
}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
Expand All @@ -1184,7 +1197,6 @@ function MoneyRequestView({
errorText={getErrorForField('attendees')}
interactive={canEdit}
shouldShowRightIcon={canEdit}
shouldRenderAsHTML
copyValue={attendeesCopyValue}
copyable={!!attendeesCopyValue}
/>
Expand Down
59 changes: 59 additions & 0 deletions src/components/UserPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Str} from 'expensify-common';
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type {AvatarSource} from '@libs/UserAvatarUtils';
import CONST from '@src/CONST';
import Avatar from './Avatar';
import Text from './Text';
import UserDetailsTooltip from './UserDetailsTooltip';

type UserPillProps = {
/** Avatar source (URL or icon) */
avatar?: AvatarSource;

/** Display name of the user */
displayName: string;

/** Account ID for proper avatar rendering */
accountID?: number;

/** Email/login for tooltip subtitle */
email?: string;
};

function UserPill({avatar, displayName, accountID, email}: UserPillProps) {
const styles = useThemeStyles();

return (
<UserDetailsTooltip
accountID={accountID ?? CONST.DEFAULT_NUMBER_ID}
fallbackUserDetails={{
avatar,
displayName: Str.removeSMSDomain(displayName),
login: email ?? displayName,
}}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.alignSelfStart, styles.userPill]}>
<Avatar
source={avatar}
size={CONST.AVATAR_SIZE.MENTION_ICON}
type={CONST.ICON_TYPE_AVATAR}
avatarID={accountID}
name={displayName}
/>
<Text
style={styles.userPillText}
numberOfLines={1}
>
{Str.removeSMSDomain(displayName)}
</Text>
</View>
</UserDetailsTooltip>
);
}

UserPill.displayName = 'UserPill';

export default UserPill;
export type {UserPillProps};
74 changes: 74 additions & 0 deletions src/components/UserPills.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import type {AvatarSource} from '@libs/UserAvatarUtils';
import Text from './Text';
import Tooltip from './Tooltip';
import UserPill from './UserPill';

type UserPillData = {
/** Avatar source (URL or icon) */
avatar?: AvatarSource;

/** Display name of the user */
displayName: string;

/** Account ID for proper avatar rendering */
accountID?: number;

/** Email/login for tooltip subtitle */
email?: string;
};

type UserPillsProps = {
/** Array of user data to render as pills */
users: UserPillData[];

/** Maximum number of pills to display before showing "+X more" */
maxVisible?: number;
};

const DEFAULT_MAX_VISIBLE = 9;

function UserPills({users, maxVisible = DEFAULT_MAX_VISIBLE}: UserPillsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

// "+1 more" edge case: if only 1 user would be hidden, show it instead of "+1 more"
const visibleUsers = users.length <= maxVisible + 1 ? users : users.slice(0, maxVisible);
const hiddenCount = users.length - visibleUsers.length;
const hiddenNames =
hiddenCount > 0
? users
.slice(visibleUsers.length)
.map((u) => u.displayName)
.join(', ')
: '';

return (
<View style={[styles.flexRow, styles.flexWrap, styles.userPillsContainer]}>
{visibleUsers.map((user) => (
<UserPill
key={user.accountID ?? user.displayName}
avatar={user.avatar}
displayName={user.displayName}
accountID={user.accountID}
email={user.email}
/>
))}
{hiddenCount > 0 && (
<Tooltip text={hiddenNames}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text style={styles.userPillMoreText}>{translate('common.plusMore', {count: hiddenCount})}</Text>
</View>
</Tooltip>
)}
</View>
);
}

UserPills.displayName = 'UserPills';

export default UserPills;
export type {UserPillData, UserPillsProps};
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Lass uns anfangen`,
showMore: 'Mehr anzeigen',
showLess: 'Weniger anzeigen',
plusMore: ({count}: {count: number}) => `+${count} weitere`,
merchant: 'Händler',
change: 'Ändern',
category: 'Kategorie',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ const translations = {
letsStart: `Let's start`,
showMore: 'Show more',
showLess: 'Show less',
plusMore: ({count}: {count: number}) => `+${count} more`,
merchant: 'Merchant',
change: 'Change',
category: 'Category',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: 'Empecemos',
showMore: 'Mostrar más',
showLess: 'Mostrar menos',
plusMore: ({count}: {count: number}) => `+${count} más`,
merchant: 'Comerciante',
change: 'Cambio',
category: 'Categoría',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Commençons`,
showMore: 'Afficher plus',
showLess: 'Afficher moins',
plusMore: ({count}: {count: number}) => `+${count} de plus`,
merchant: 'Commerçant',
change: 'Modifier',
category: 'Catégorie',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Iniziamo`,
showMore: 'Mostra di più',
showLess: 'Mostra meno',
plusMore: ({count}: {count: number}) => `+${count} altri`,
merchant: 'Esercente',
change: 'Modifica',
category: 'Categoria',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `はじめましょう`,
showMore: 'さらに表示',
showLess: '表示を減らす',
plusMore: ({count}: {count: number}) => `+${count}件`,
merchant: '加盟店',
change: '変更',
category: 'カテゴリ',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Laten we beginnen`,
showMore: 'Meer weergeven',
showLess: 'Toon minder',
plusMore: ({count}: {count: number}) => `+${count} meer`,
merchant: 'Handelaar',
change: 'Wijzigen',
category: 'Categorie',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Zacznijmy`,
showMore: 'Pokaż więcej',
showLess: 'Pokaż mniej',
plusMore: ({count}: {count: number}) => `+${count} więcej`,
merchant: 'Sprzedawca',
change: 'Zmień',
category: 'Kategoria',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `Vamos começar`,
showMore: 'Mostrar mais',
showLess: 'Mostrar menos',
plusMore: ({count}: {count: number}) => `+${count} mais`,
merchant: 'Estabelecimento',
change: 'Alterar',
category: 'Categoria',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ const translations: TranslationDeepObject<typeof en> = {
letsStart: `开始吧`,
showMore: '显示更多',
showLess: '收起',
plusMore: ({count}: {count: number}) => `+${count}个`,
merchant: '商户',
change: '更改',
category: '类别',
Expand Down
Loading
Loading