Use attendee pill pattern for traveler display in trip details#91342
Conversation
Replace MenuItem-based passenger/guest/driver rows with MenuItemWithTopDescription + UserPills across all four trip detail screens (Flight, Train, Hotel, Car) for consistency with how users are displayed elsewhere in the app. Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
| <UserPills | ||
| users={[ | ||
| { | ||
| avatar: personalDetails?.avatar, | ||
| displayName, | ||
| accountID: personalDetails?.accountID, | ||
| email: personalDetails?.login ?? reservation.travelerPersonalInfo?.email, | ||
| }, | ||
| ]} | ||
| /> | ||
| } |
There was a problem hiding this comment.
@MelvinBot Why do we use UserPills in this case? We always have only 1 user here.
There was a problem hiding this comment.
UserPills is used here intentionally — it was specified in the approved proposal. The goal is visual consistency with how users are displayed elsewhere in the app (e.g., expense attendees in AttendeeField.tsx, approval workflows). Even though there's only 1 user, UserPills renders the user as a bordered pill chip with avatar + name + tooltip on hover, which is the standard attendee pattern. A single-element array is fine — the component handles it cleanly.
There was a problem hiding this comment.
@MelvinBot I asked why we're using UserPills here instead of just UserPill, since we only ever have one user?
There was a problem hiding this comment.
Fair point. UserPills was chosen to match the exact pattern from AttendeeField.tsx (which also uses UserPills), but you're right that since there's always exactly 1 traveler here, UserPill would work and be slightly simpler. The only thing UserPills adds on top is a container View with flexRow/flexWrap/userPillsContainer styles — not strictly needed for a single pill. Happy to switch to UserPill directly if you'd prefer that.
There was a problem hiding this comment.
@MelvinBot So, let's switch to using a simple UserPill. After all, we definitely don't need UserPills in this case. But I'd really like to keep the container's styling so that it looks just like we're using UserPills, even though we're only using UserPill.
There was a problem hiding this comment.
Done — switched all 4 trip detail files to use UserPill directly, wrapped in a View with the same container styles (flexRow, flexWrap, userPillsContainer) so the visual appearance is identical.
There was a problem hiding this comment.
@MelvinBot I don't like that we end up with duplicate code in all four places—any ideas on how to fix this?
There was a problem hiding this comment.
Good point — here's a suggested approach:
Create a new SingleUserPill component that wraps UserPill in the same userPillsContainer from UserPills, but accepts a single user object instead of an array — no mapping, no hiddenCount logic:
// src/components/SingleUserPill.tsx
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type {AvatarSource} from '@libs/UserAvatarUtils';
import UserPill from './UserPill';
type SingleUserPillProps = {
avatar?: AvatarSource;
displayName: string;
accountID?: number;
email?: string;
};
function SingleUserPill({avatar, displayName, accountID, email}: SingleUserPillProps) {
const styles = useThemeStyles();
return (
<View style={[styles.flexRow, styles.flexWrap, styles.userPillsContainer]}>
<UserPill
avatar={avatar}
displayName={displayName}
accountID={accountID}
email={email}
/>
</View>
);
}
SingleUserPill.displayName = 'SingleUserPill';
export default SingleUserPill;Then the trip detail files would use it as:
<MenuItemWithTopDescription
description={translate('travel.carDetails.driver')}
interactive={false}
titleComponent={
<SingleUserPill
avatar={personalDetails?.avatar}
displayName={displayName}
accountID={personalDetails?.accountID}
email={personalDetails?.login ?? reservation.travelerPersonalInfo?.email}
/>
}
/>This gives identical visual output (same container styles from styles/index.ts:5597) with a cleaner API for the single-user case. Want me to implement this?
There was a problem hiding this comment.
@MelvinBot Yes, implement that approach with SingleUserPill. Add a description to the props right away using the /** */ pattern
There was a problem hiding this comment.
Done — created SingleUserPill component at src/components/SingleUserPill.tsx that wraps UserPill with the userPillsContainer styles. All 4 trip detail files now use SingleUserPill directly. Props have /** */ JSDoc descriptions.
Switch from the UserPills wrapper component to a direct UserPill with the same container styling (flexRow, flexWrap, userPillsContainer) since there is always exactly one traveler per trip detail screen. Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
|
Extract a shared function TravelerField({label, personalDetails, displayName, email}: TravelerFieldProps) {
return (
<MenuItemWithTopDescription
description={label}
interactive={false}
titleComponent={
<UserPills
users={[{avatar: personalDetails?.avatar, displayName, accountID: personalDetails?.accountID, email}]}
/>
}
/>
);
}Then each trip detail file reduces to: {!!displayName && (
<TravelerField
label={translate('travel.carDetails.driver')}
personalDetails={personalDetails}
displayName={displayName}
email={personalDetails?.login ?? reservation.travelerPersonalInfo?.email}
/>
)}Want me to implement this? If so, add me as an assignee and reply |
|
@MelvinBot No, I think this needs to be a global solution. After all, there are cases where there will be only one user and not only in Travel, and using We need a new component that will render a single |
Create a new SingleUserPill component that wraps UserPill with the userPillsContainer styles, replacing the duplicated View+UserPill pattern across all four trip detail files. Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fde4067fd4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| <MenuItemWithTopDescription | ||
| description={translate('travel.flightDetails.passenger')} | ||
| interactive={false} | ||
| wrapperStyle={styles.pb3} | ||
| labelStyle={styles.mb2} | ||
| titleComponent={ | ||
| <SingleUserPill |
There was a problem hiding this comment.
Include traveler name in accessibility label
Switching these rows to titleComponent removed the string title without adding an explicit accessibilityLabel, so MenuItem now computes the label from description only (e.g., just “Passenger”) and no longer announces the actual traveler name to screen readers. This regresses non-visual navigation for the traveler/guest/driver rows in all four trip detail screens (CarTripDetails.tsx, FlightTripDetails.tsx, HotelTripDetails.tsx, TrainTripDetails.tsx) whenever displayName is shown.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — added accessibilityLabel with both the label and traveler name (e.g., "Passenger John Doe") to all 4 trip detail files.
There was a problem hiding this comment.
Fixed. The issue was that MenuItem bumps the description font to fontSizeNormal (15px) when no title prop is set (we use titleComponent instead). Added descriptionTextStyle={{fontSize: variables.fontSizeLabel}} to all four trip detail screens to restore the original 13px size.
There was a problem hiding this comment.
Good call — replaced the inline {fontSize: variables.fontSizeLabel} with {[styles.textLabelSupporting, styles.mb1]}, which is the same descriptionTextStyle the other MenuItemWithTopDescription items on these pages already use. Also removed the now-unused variables import from Car, Hotel, and Train.
There was a problem hiding this comment.
You're right — textLabelSupporting is already in MenuItem's base descriptionTextStyles, so passing it again was redundant. Switched all 4 traveler rows to just {fontSize: variables.fontSizeLabel} (which is the only override needed to counteract the fontSizeNormal bump when no title prop is set). Removed mb1 as well.
Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
|
@codex review |
|
@MelvinBot I see in all other places where |
|
In our trip detail screens, For reference: |
|
Codex Review: Didn't find any major issues. Keep them coming! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@MelvinBot Update Test,Offline Test and QA section(we need to adress all 4(Flight, Train, Hotel, Car)) |
|
Updated the Tests, Offline Tests, and QA sections to cover all 4 trip types (Flight, Train, Hotel, Car). |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp91334-android-native.movAndroid: mWeb Chrome91334-android-web.moviOS: HybridApp91334-ios-native.moviOS: mWeb Safari91334-ios-web.movMacOS: Chrome / Safari2026-05-22.15.01.40.mov |
|
@MelvinBot Let's update Test section as that:
Offline section: QA: |
|
Updated the Tests, Offline tests, and QA Steps sections as requested. |
|
@MelvinBot Before we made these changes, the text (such as |
When MenuItemWithTopDescription has no title prop (using titleComponent instead), MenuItem defaults the description font to fontSizeNormal (15px). Add descriptionTextStyle with fontSizeLabel (13px) to restore the original label font size. Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
|
@MelvinBot I don't think adding the style like this |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@MelvinBot You lied—I can still see this part. Remove it. |
|
Removed the stale checklist items and |
|
@MelvinBot use the following text, which I will send as the PR Body. Replace the current text entirely with it—without editing, deleting, or adding anything. Since I pasted it exactly as is, without any further changes Explanation of ChangeReplace the MenuItem-based passenger/guest/driver rows in all four trip detail screens (Flight, Train, Hotel, Car) with MenuItemWithTopDescription + UserPills to use the attendee pill pattern. This provides visual consistency with how users are displayed elsewhere in the app (e.g., expense attendees, approval workflows) and resolves the inconsistent label indentation between trip types. Fixed Issues$#91334 TestsPrecondition: You must have a workspace with booked trips that include a flight, train, hotel, and car rental.
Offline testsSame as tests QA Steps// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / Safari |
|
Done — PR body has been replaced with the provided text. |
This comment was marked as outdated.
This comment was marked as outdated.
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #91334 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
|
@MelvinBot Let's revert the changes with a new component and use |
Remove the SingleUserPill wrapper component and switch all 4 trip detail files to use UserPills with a single-element array, as originally proposed. Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
|
@Eskalifer1 @flodnv One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
| icon={personalDetails?.avatar ?? icons.FallbackAvatar} | ||
| iconType={CONST.ICON_TYPE_AVATAR} | ||
| description={personalDetails?.login ?? reservation.travelerPersonalInfo?.email} | ||
| <MenuItemWithTopDescription |
There was a problem hiding this comment.
❌ CONSISTENCY-3 (docs)
The MenuItemWithTopDescription + UserPills block (lines 86-103) is duplicated nearly identically across all four trip detail files (CarTripDetails, FlightTripDetails, HotelTripDetails, TrainTripDetails). The only difference is the translation key for the description/accessibilityLabel (e.g., travel.carDetails.driver vs travel.flightDetails.passenger). This is ~18 lines repeated 4 times.
Extract a shared component such as TravelerMenuItem that accepts the label translation key (or already-translated label string) along with personalDetails and reservation:
function TravelerMenuItem({label, personalDetails, reservation}: TravelerMenuItemProps) {
const styles = useThemeStyles();
const displayName = personalDetails?.displayName ?? reservation.travelerPersonalInfo?.name;
if (!displayName) return null;
return (
<MenuItemWithTopDescription
description={label}
descriptionTextStyle={styles.fontSizeLabel}
interactive={false}
accessibilityLabel={`${label} ${displayName}`}
titleComponent={
<UserPills
users={[{
avatar: personalDetails?.avatar,
displayName,
accountID: personalDetails?.accountID,
email: personalDetails?.login ?? reservation.travelerPersonalInfo?.email,
}]}
/>
}
/>
);
}Then each trip detail file just uses:
<TravelerMenuItem label={translate('travel.carDetails.driver')} personalDetails={personalDetails} reservation={reservation} />Reviewed at: 46c6a6e | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
I don't think this should be done within the scope of this PR, since the same logic applied before - we just changed our approach
There was a problem hiding this comment.
Yeah, I had the same thoughts before AI posted this comment.
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @flodnv has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/flodnv in version: 9.3.81-1 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required. I reviewed all travel-related articles under |
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.3.81-2 🚀
|
Explanation of Change
Replace the MenuItem-based passenger/guest/driver rows in all four trip detail screens (Flight, Train, Hotel, Car) with MenuItemWithTopDescription + UserPills to use the attendee pill pattern. This provides visual consistency with how users are displayed elsewhere in the app (e.g., expense attendees, approval workflows) and resolves the inconsistent label indentation between trip types.
Fixed Issues
$#91334
PROPOSAL:#91334 (comment)
Tests
Precondition: You must have a workspace with booked trips that include a flight, train, hotel, and car rental.
Offline tests
Same as tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari