Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,15 @@ function BaseSelectionListItemRenderer<TItem extends ListItem>({
return onCheckboxPress ? () => onCheckboxPress(item) : undefined;
};

// We add this render condition to allow customizing how an item is rendered in the list,
// without requiring that item to be a ListItem.
if (item.shouldOnlyRenderHeaderContent) {
return item.headerContent;
}

return (
<>
{item.headerContent && item.headerContent}
<ListItem
item={item}
isFocused={isFocused}
Expand Down
6 changes: 6 additions & 0 deletions src/components/SelectionListWithSections/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ type ListItem<K extends string | number = string> = {
/** Whether the brick road indicator should be shown */
brickRoadIndicator?: BrickRoad | '' | null;

/** Allows us to render only the header content, making it easier to embed items other than ListItem into the list */
shouldOnlyRenderHeaderContent?: boolean;

/** Element to render above the ListItem */
headerContent?: ReactNode;

/** Element to render below the ListItem */
footerContent?: ReactNode;

Expand Down
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
*/
let currentUserLogin: string | undefined;
let currentUserAccountID: number | undefined;
Onyx.connect({

Check warning on line 196 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserLogin = value?.email;
Expand All @@ -202,13 +202,13 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 205 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => (allPersonalDetails = isEmptyObject(value) ? {} : value),
});

const policies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 211 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (policy, key) => {
if (!policy || !key || !policy.name) {
Expand All @@ -220,14 +220,14 @@
});

let allPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 223 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (val) => (allPolicies = val),
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 230 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -236,7 +236,7 @@
});

let allReportNameValuePairs: OnyxCollection<ReportNameValuePairs>;
Onyx.connect({

Check warning on line 239 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -248,7 +248,7 @@
const allSortedReportActions: Record<string, ReportAction[]> = {};
let allReportActions: OnyxCollection<ReportActions>;
const lastVisibleReportActions: ReportActions = {};
Onyx.connect({

Check warning on line 251 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand Down Expand Up @@ -310,7 +310,7 @@
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 313 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (value) => (activePolicyID = value),
});
Expand Down Expand Up @@ -1461,7 +1461,7 @@
if (!peekedValue) {
throw new Error('Heap is empty, cannot peek value');
}
if (comparator(option) > comparator(peekedValue)) {
if (reversed ? comparator(option) < comparator(peekedValue) : comparator(option) > comparator(peekedValue)) {
heap.pop();
heap.push(option);
}
Expand Down
3 changes: 3 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import isEmpty from 'lodash/isEmpty';
import isNumber from 'lodash/isNumber';
import mapValues from 'lodash/mapValues';
import lodashMaxBy from 'lodash/maxBy';
import type {ReactNode} from 'react';
import type {ColorValue} from 'react-native';
import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
Expand Down Expand Up @@ -899,6 +900,8 @@ type OptionData = {
lastName?: string;
avatar?: AvatarSource;
timezone?: Timezone;
shouldOnlyRenderHeaderContent?: boolean;
headerContent?: ReactNode;
} & Report &
ReportNameValuePairs;

Expand Down
43 changes: 26 additions & 17 deletions src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,21 @@ function MoneyRequestParticipantsSelector({
!(contactPermissionState === RESULTS.GRANTED || contactPermissionState === RESULTS.LIMITED) &&
inputHelperText === translate('common.noResultsFound');

const importContactsButtonComponent = useMemo(() => {
const shouldShowImportContactsButton = contactState?.showImportUI ?? showImportContacts;
if (!shouldShowImportContactsButton) {
return null;
}
return (
<MenuItem
title={translate('contact.importContacts')}
icon={icons.UserPlus}
onPress={goToSettings}
shouldShowRightIcon
/>
);
}, [icons.UserPlus, contactState?.showImportUI, showImportContacts, translate]);

/**
* Returns the sections needed for the OptionsSelector
* @returns {Array}
Expand Down Expand Up @@ -349,6 +364,16 @@ function MoneyRequestParticipantsSelector({
headerMessage = inputHelperText;
}

const sectionsLength = newSections.reduce((value, section) => value + section.data.length, 0);
if (sectionsLength > 0 && importContactsButtonComponent) {
// Always render the "Import contacts" button at the top of the list
newSections.unshift({
title: undefined,
data: [{shouldOnlyRenderHeaderContent: true, headerContent: importContactsButtonComponent}],
shouldShow: true,
});
}

return [newSections, headerMessage];
}, [
areOptionsInitialized,
Expand All @@ -368,6 +393,7 @@ function MoneyRequestParticipantsSelector({
isPerDiemRequest,
showImportContacts,
inputHelperText,
importContactsButtonComponent,
]);

/**
Expand Down Expand Up @@ -504,22 +530,6 @@ function MoneyRequestParticipantsSelector({
[isIOUSplit, addParticipantToSelection, addSingleParticipant],
);

const footerContentAbovePaginationComponent = useMemo(() => {
const shouldShowImportContactsButton = contactState?.showImportUI ?? showImportContacts;
if (!shouldShowImportContactsButton) {
return null;
}
return (
<MenuItem
title={translate('contact.importContacts')}
icon={icons.UserPlus}
onPress={goToSettings}
shouldShowRightIcon
style={styles.mb3}
/>
);
}, [icons.UserPlus, contactState?.showImportUI, showImportContacts, styles.mb3, translate]);

const ClickableImportContactTextComponent = useMemo(() => {
if (searchTerm.length || isSearchingForReports) {
return;
Expand Down Expand Up @@ -580,7 +590,6 @@ function MoneyRequestParticipantsSelector({
}
footerContent={footerContent}
listEmptyContent={EmptySelectionListContentWithPermission}
footerContentAbovePagination={footerContentAbovePaginationComponent}
headerMessage={header}
showLoadingPlaceholder={showLoadingPlaceholder}
canSelectMultiple={isIOUSplit && isAllowedToSplit}
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/OptionsListUtilsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2448,6 +2448,23 @@ describe('OptionsListUtils', () => {
const result = optionsOrderBy(options, comparator, 0);
expect(result).toEqual([]);
});

it('returns the older options up to the specified limit', () => {
const options: OptionData[] = [
{reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData,
{reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData,
{reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData,
{reportID: '4', lastVisibleActionCreated: '2022-01-01T13:00:00Z'} as OptionData,
];
const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? '';
// We will pass reversed === true to sort the list in ascending order
const result = optionsOrderBy(options, comparator, 2, undefined, true);
expect(result.length).toBe(2);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(result.at(0)!.reportID).toBe('3');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(result.at(1)!.reportID).toBe('1');
});
});

describe('sortAlphabetically', () => {
Expand Down
Loading