Repository navigation
[CFI] Attendee anchored dropdown #105345
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
Merged
puneetlath
merged 4 commits into
Expensify:main
from
DylanDylann:dylann/101573-attendees-anchored-dropdown
Oct 30, 2026
Merged
[CFI] Attendee anchored dropdown #105345
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /** | ||
| * The attendee list in the container anchored to a field's row, with an Apply button in place of the full page's Save. | ||
| */ | ||
| import AnchoredSelectionListPopover from '@components/AnchoredSelectionListPopover'; | ||
| import type {AnchoredSelectionListPopoverProps} from '@components/AnchoredSelectionListPopover'; | ||
| import ScreenWrapperStatusContext from '@components/ScreenWrapper/ScreenWrapperStatusContext'; | ||
|
|
||
| import useLocalize from '@hooks/useLocalize'; | ||
|
|
||
| import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; | ||
|
|
||
| import CONST from '@src/CONST'; | ||
| import type {IOUType} from '@src/CONST'; | ||
| import type {Attendee} from '@src/types/onyx/IOU'; | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| /** | ||
| * The list builds its options once the screen it is on has finished its transition, which it reads off the screen | ||
| * wrapper. A container anchored to a row has no screen transition, so the list is told the screen has settled, and that | ||
| * no safe area padding was applied around it, which is what it would read outside any screen wrapper. | ||
| */ | ||
| const SETTLED_SCREEN_STATUS = {didScreenTransitionEnd: true, isSafeAreaTopPaddingApplied: false, isSafeAreaBottomPaddingApplied: false}; | ||
|
|
||
| type AttendeePickerModalProps = { | ||
| /** The expense's attendees when the list opens. The list keeps its own selection from there */ | ||
| attendees: Attendee[]; | ||
|
|
||
| /** Called with the whole selection each time an attendee is toggled */ | ||
| onAttendeesChanged: (attendees: Attendee[]) => void; | ||
|
|
||
| /** Called when the user applies the selection, with the Apply button or the Enter key */ | ||
| onApply: () => void; | ||
|
|
||
| /** Type of the expense, which the list's empty state is worded for */ | ||
| iouType: IOUType; | ||
| } & Omit<AnchoredSelectionListPopoverProps, 'optionCount' | 'isSearchable' | 'title' | 'backButtonSentryLabel' | 'headerActions' | 'children'>; | ||
|
|
||
| /** | ||
| * The attendee list in a container anchored to a row: a pop-over on a wide layout, a bottom sheet on a narrow one. | ||
| * Unlike the single-select lists, toggling an attendee hands the new selection to `onAttendeesChanged` and keeps the | ||
| * container open, so the user can keep picking. The selection is applied with the Apply button at the bottom, as on | ||
| * the full page, where the button reads Save. | ||
| */ | ||
| function AttendeePickerModal({attendees, onAttendeesChanged, onApply, iouType, ...popoverProps}: AttendeePickerModalProps) { | ||
| const {translate} = useLocalize(); | ||
|
|
||
| return ( | ||
| <AnchoredSelectionListPopover | ||
| {...popoverProps} | ||
| // The list always has a search input and, between recents and contacts, more rows than the container shows at | ||
| // once, so the container opens at its full height rather than fitting a count. | ||
| optionCount={CONST.STANDARD_LIST_ITEM_LIMIT} | ||
| isSearchable | ||
| title={translate('iou.attendees')} | ||
| backButtonSentryLabel={CONST.SENTRY_LABEL.ATTENDEE_PICKER_MODAL.BACK_BUTTON} | ||
| > | ||
| <ScreenWrapperStatusContext.Provider value={SETTLED_SCREEN_STATUS}> | ||
| <MoneyRequestAttendeeSelector | ||
| attendees={attendees} | ||
| onAttendeesAdded={onAttendeesChanged} | ||
| onFinish={onApply} | ||
| iouType={iouType} | ||
| searchInputLabel={translate('selectionList.searchForSomeone')} | ||
| confirmButtonText={translate('common.apply')} | ||
| confirmButtonSize={CONST.BUTTON_SIZE.MEDIUM} | ||
| /> | ||
| </ScreenWrapperStatusContext.Provider> | ||
| </AnchoredSelectionListPopover> | ||
| ); | ||
| } | ||
|
|
||
| export default AttendeePickerModal; | ||
| export type {AttendeePickerModalProps}; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/components/MoneyRequestConfirmationList/sections/AttendeeFieldDropdown.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /** | ||
| * The attendees row's dropdown on the expense confirmation form: the attendee list anchored to the row, holding the | ||
| * selection until the user applies it to the draft expense. | ||
| */ | ||
| import AttendeePickerModal from '@components/AttendeePicker/AttendeePickerModal'; | ||
|
|
||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useReportOwnerAsAttendee from '@hooks/useReportOwnerAsAttendee'; | ||
|
|
||
| import {setMoneyRequestAttendees} from '@libs/actions/IOU/MoneyRequest'; | ||
| import {getOriginalAttendees} from '@libs/TransactionUtils'; | ||
|
|
||
| import type {IOUType} from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import type {Attendee} from '@src/types/onyx/IOU'; | ||
|
|
||
| import {deepEqual} from 'fast-equals'; | ||
| import React, {useState} from 'react'; | ||
|
|
||
| import type {ExpenseFieldDropdownRenderProps} from './ExpenseFieldDropdown'; | ||
|
|
||
| type AttendeeFieldDropdownProps = ExpenseFieldDropdownRenderProps & { | ||
|
DylanDylann marked this conversation as resolved.
Resolved
|
||
| /** ID of the draft expense the attendees are written to */ | ||
| transactionID: string; | ||
|
|
||
| /** Type of the expense, which the list's empty state is worded for */ | ||
| iouType: IOUType; | ||
| }; | ||
|
|
||
| /** | ||
| * The attendee list, in the container anchored to the expense form's attendees row. Mounted only once the row is | ||
| * opened. The selection is kept here while the user picks and written to the draft when applied, as the full page | ||
| * writes it on save; dismissing the container drops the unapplied selection. | ||
| */ | ||
| function AttendeeFieldDropdown({transactionID, iouType, isVisible, onClose, ...popoverProps}: AttendeeFieldDropdownProps) { | ||
| const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`); | ||
|
DylanDylann marked this conversation as resolved.
Resolved
|
||
| const reportOwnerAsAttendee = useReportOwnerAsAttendee(draftTransaction); | ||
| const originalAttendees = getOriginalAttendees(draftTransaction, reportOwnerAsAttendee); | ||
|
|
||
| // The list starts from the draft's attendees, the creator when it has none, as the full page does. The row keeps this | ||
| // dropdown mounted once it has been opened, so the selection is dropped each time it opens or closes: a selection | ||
| // dismissed without Apply must not come back on the next open. | ||
| const [pickedAttendees, setPickedAttendees] = useState<Attendee[]>(); | ||
| const [wasVisible, setWasVisible] = useState(isVisible); | ||
| if (wasVisible !== isVisible) { | ||
| setWasVisible(isVisible); | ||
| setPickedAttendees(undefined); | ||
| } | ||
| const attendees = pickedAttendees ?? originalAttendees; | ||
|
|
||
| const applyAttendees = () => { | ||
| // An expense needs at least one attendee, which the full page enforces at save. The list shows the same error, and | ||
| // the Apply button is disabled, so this only guards the Enter key. | ||
| if (attendees.length === 0) { | ||
| return; | ||
| } | ||
| if (!deepEqual(originalAttendees, attendees)) { | ||
| setMoneyRequestAttendees(transactionID, attendees, true); | ||
| } | ||
| onClose(); | ||
| }; | ||
|
|
||
| return ( | ||
| <AttendeePickerModal | ||
| {...popoverProps} | ||
| isVisible={isVisible} | ||
| onClose={onClose} | ||
| shouldShowHeader | ||
| attendees={attendees} | ||
| onAttendeesChanged={setPickedAttendees} | ||
| onApply={applyAttendees} | ||
| iouType={iouType} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default AttendeeFieldDropdown; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.