diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index a65bf806471a3..d597194525df6 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -110,6 +110,7 @@ import { rejectMoneyRequestReason, shouldBlockSubmitDueToStrictPolicyRules, } from '@libs/ReportUtils'; +import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import { @@ -2177,6 +2178,7 @@ function MoneyReportHeader({ ]); const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length && !transactionThreadReportID; + const popoverUseScrollView = shouldPopoverUseScrollView(selectedTransactionsOptions); const hasPayInSelectionMode = allExpensesSelected && hasPayAction; @@ -2258,6 +2260,7 @@ function MoneyReportHeader({ customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} isSplitButton={false} shouldAlwaysShowDropdownMenu + shouldPopoverUseScrollView={popoverUseScrollView} wrapperStyle={wrapperStyle} /> ), @@ -2272,6 +2275,7 @@ function MoneyReportHeader({ translate, selectedTransactionIDs.length, kycWallRef, + popoverUseScrollView, ], ); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 9d36a095ee32b..aa645e24839ae 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -59,6 +59,7 @@ import { wasMessageReceivedWhileOffline, } from '@libs/ReportActionsUtils'; import {canUserPerformWriteAction, chatIncludesChronosWithID, getOriginalReportID, getReportLastVisibleActionCreated, isHarvestCreatedExpenseReport, isUnread} from '@libs/ReportUtils'; +import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView'; import markOpenReportEnd from '@libs/telemetry/markOpenReportEnd'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import {isTransactionPendingDelete} from '@libs/TransactionUtils'; @@ -306,6 +307,8 @@ function MoneyRequestReportActionsList({ }); }, [originalSelectedTransactionsOptions, dismissedRejectUseExplanation, isDelegateAccessRestricted, showDelegateNoAccessModal]); + const popoverUseScrollView = shouldPopoverUseScrollView(selectedTransactionsOptions); + const dismissRejectModalBasedOnAction = useCallback(() => { if (rejectModalAction === CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT_BULK) { dismissRejectUseExplanation(); @@ -800,6 +803,7 @@ function MoneyRequestReportActionsList({ customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} isSplitButton={false} shouldAlwaysShowDropdownMenu + shouldPopoverUseScrollView={popoverUseScrollView} wrapperStyle={[styles.w100, styles.ph5]} /> diff --git a/src/components/Search/SearchBulkActionsButton.tsx b/src/components/Search/SearchBulkActionsButton.tsx index ce3905b9d49e8..f418270be5e68 100644 --- a/src/components/Search/SearchBulkActionsButton.tsx +++ b/src/components/Search/SearchBulkActionsButton.tsx @@ -20,6 +20,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {handleBulkPayItemSelected} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import {isExpenseReport} from '@libs/ReportUtils'; +import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -76,8 +77,7 @@ function SearchBulkActionsButton({queryJSON}: SearchBulkActionsButtonProps) { const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); const isExpenseReportType = queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT; - const shouldPopoverUseScrollView = - headerButtonsOptions.length >= CONST.DROPDOWN_SCROLL_THRESHOLD || headerButtonsOptions.some((option) => (option.subMenuItems?.length ?? 0) >= CONST.DROPDOWN_SCROLL_THRESHOLD); + const popoverUseScrollView = shouldPopoverUseScrollView(headerButtonsOptions); const selectedItemsCount = useMemo(() => { if (!selectedTransactions) { @@ -123,7 +123,7 @@ function SearchBulkActionsButton({queryJSON}: SearchBulkActionsButtonProps) { shouldAlwaysShowDropdownMenu isDisabled={headerButtonsOptions.length === 0} onPress={() => null} - shouldPopoverUseScrollView={shouldPopoverUseScrollView} + shouldPopoverUseScrollView={popoverUseScrollView} onSubItemSelected={(subItem) => handleBulkPayItemSelected({ item: subItem, @@ -163,7 +163,7 @@ function SearchBulkActionsButton({queryJSON}: SearchBulkActionsButtonProps) { buttonSize={CONST.DROPDOWN_BUTTON_SIZE.SMALL} customText={selectionButtonText} options={headerButtonsOptions} - shouldPopoverUseScrollView={shouldPopoverUseScrollView} + shouldPopoverUseScrollView={popoverUseScrollView} onSubItemSelected={(subItem) => handleBulkPayItemSelected({ item: subItem, diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 0c01b08c9a0ba..42cfa976eac8b 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -38,6 +38,7 @@ import { isIOUReport, } from '@libs/ReportUtils'; import {handleUnvalidatedUserNavigation, useSettlementButtonPaymentMethods} from '@libs/SettlementButtonUtils'; +import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {setPersonalBankAccountContinueKYCOnSuccess} from '@userActions/BankAccounts'; import {approveMoneyRequest} from '@userActions/IOU'; @@ -577,8 +578,7 @@ function SettlementButton({ const shouldUseSplitButton = hasPreferredPaymentMethod || !!lastPaymentPolicy || ((isExpenseReport || isInvoiceReport) && hasIntentToPay); const shouldLimitWidth = shouldUseShortForm && shouldUseSplitButton && !paymentButtonOptions.length; - const shouldPopoverUseScrollView = - paymentButtonOptions.length >= CONST.DROPDOWN_SCROLL_THRESHOLD || paymentButtonOptions.some((option) => (option.subMenuItems?.length ?? 0) >= CONST.DROPDOWN_SCROLL_THRESHOLD); + const popoverUseScrollView = shouldPopoverUseScrollView(paymentButtonOptions); return ( 5 ? styles.settlementButtonListContainer : {}} wrapperStyle={[wrapperStyle, shouldLimitWidth ? styles.settlementButtonShortFormWidth : {}]} disabledStyle={disabledStyle} diff --git a/src/libs/shouldPopoverUseScrollView.ts b/src/libs/shouldPopoverUseScrollView.ts new file mode 100644 index 0000000000000..ccce7e66ec26e --- /dev/null +++ b/src/libs/shouldPopoverUseScrollView.ts @@ -0,0 +1,8 @@ +import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import CONST from '@src/CONST'; + +function shouldPopoverUseScrollView(options: Array>): boolean { + return options.length >= CONST.DROPDOWN_SCROLL_THRESHOLD || options.some((option) => (option.subMenuItems?.length ?? 0) >= CONST.DROPDOWN_SCROLL_THRESHOLD); +} + +export default shouldPopoverUseScrollView; diff --git a/tests/unit/shouldPopoverUseScrollViewTest.ts b/tests/unit/shouldPopoverUseScrollViewTest.ts new file mode 100644 index 0000000000000..af091a4711a19 --- /dev/null +++ b/tests/unit/shouldPopoverUseScrollViewTest.ts @@ -0,0 +1,39 @@ +import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView'; +import CONST from '@src/CONST'; + +describe('shouldPopoverUseScrollView', () => { + const createOption = (value: string, subMenuItems?: Array<{value: string; text: string}>): DropdownOption => ({ + value, + text: value, + ...(subMenuItems && {subMenuItems: subMenuItems.map((item) => ({...item, key: item.value}))}), + }); + + it('returns false when there are few top-level options and no large submenus', () => { + const options = [createOption('a'), createOption('b'), createOption('c')]; + expect(shouldPopoverUseScrollView(options)).toBe(false); + }); + + it('returns true when there are 5 or more top-level options', () => { + const options = Array.from({length: CONST.DROPDOWN_SCROLL_THRESHOLD}, (_, i) => createOption(`option-${i}`)); + expect(shouldPopoverUseScrollView(options)).toBe(true); + }); + + it('returns true when any option has 5 or more submenu items', () => { + const subMenuItems = Array.from({length: CONST.DROPDOWN_SCROLL_THRESHOLD}, (_, i) => ({ + value: `sub-${i}`, + text: `Sub ${i}`, + })); + const options = [createOption('parent', subMenuItems)]; + expect(shouldPopoverUseScrollView(options)).toBe(true); + }); + + it('returns false when submenu has fewer than threshold items', () => { + const subMenuItems = [ + {value: 'sub-1', text: 'Sub 1'}, + {value: 'sub-2', text: 'Sub 2'}, + ]; + const options = [createOption('parent', subMenuItems)]; + expect(shouldPopoverUseScrollView(options)).toBe(false); + }); +});