From 3fa7f24bbb5f7ae644f8d2b6c32b8d7193f9b353 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 Apr 2026 18:17:50 +0800 Subject: [PATCH 1/8] fix popup content is not fully shown --- src/CONST/index.ts | 1 + .../MoneyRequestReportTransactionList.tsx | 4 +- .../Search/FilterDropdowns/BasePopup.tsx | 2 +- .../FilterDropdowns/DateSelectPopup/index.tsx | 9 +- .../Search/FilterDropdowns/DisplayPopup.tsx | 59 ++++--------- .../Search/FilterDropdowns/DropdownButton.tsx | 1 + .../Search/FilterDropdowns/GroupByPopup.tsx | 34 ++++---- .../FilterDropdowns/GroupCurrencyPopup.tsx | 8 +- .../Search/FilterDropdowns/InSelectPopup.tsx | 7 +- .../FilterDropdowns/MultiSelectPopup.tsx | 44 +++++----- .../FilterDropdowns/ReportFieldPopup.tsx | 74 ++++++---------- .../FilterDropdowns/SingleSelectPopup.tsx | 47 ++++++---- .../Search/FilterDropdowns/SortByPopup.tsx | 86 +++++++++++++++---- .../Search/FilterDropdowns/SortOrderPopup.tsx | 8 +- .../Search/FilterDropdowns/TextInputPopup.tsx | 4 +- .../FilterDropdowns/UserSelectPopup.tsx | 17 ++-- .../DatePickerFilterPopup.tsx | 7 ++ src/styles/index.ts | 76 ++++++++-------- 18 files changed, 254 insertions(+), 234 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index b33731461863..aa14cc58280c 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -9614,6 +9614,7 @@ const CONST = { AUTH_IMAGES: 'auth-images', }, + MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO: 0.9, MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE: 0.75, MAP_VIEW_LAYERS: { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 49d3c3480f98..431d301a1bbb 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -490,8 +490,8 @@ function MoneyRequestReportTransactionList({ const groupByPopoverComponent = useCallback( (props: {closeOverlay: () => void}) => ( - - + + + {onBackButtonPress ? ( (null); const scrollViewRef = useRef>(null); const [selectedDateModifier, setSelectedDateModifier] = useState(null); @@ -130,8 +128,6 @@ function DateSelectPopup({label, value, presets, style, closeOverlay, onChange, closeOverlay(); }, [clearSelection, closeOverlay, onChange, selectedDateModifier]); - const maxPopupHeight = Math.round(windowHeight * 0.875); - // For non-Range modes, use original simple styles. For Range, use custom layout const useRangeLayout = selectedDateModifier === CONST.SEARCH.DATE_MODIFIERS.RANGE; @@ -186,14 +182,13 @@ function DateSelectPopup({label, value, presets, style, closeOverlay, onChange, ); } - const topPaddingStyle = selectedDateModifier ? styles.pt3 : undefined; const buttonRowSpacing = selectedDateModifier ? styles.mt4 : styles.mt2; - const mobileContainerStyle = useRangeLayout ? [topPaddingStyle, styles.flexGrow1, {maxHeight: maxPopupHeight}] : styles.gap2; + const mobileContainerStyle = useRangeLayout ? [styles.flexGrow1] : styles.gap2; const mobileLabelStyle = useRangeLayout ? [styles.textLabel, styles.ph5, styles.pb3] : [styles.textLabel, styles.textSupporting, styles.ph5, styles.pv1]; const mobileButtonRowStyle = useRangeLayout ? [styles.flexRow, styles.ph5, buttonRowSpacing, styles.alignItemsCenter, styles.gap2] : [styles.flexRow, styles.gap2, styles.ph5]; return ( - + {!selectedDateModifier && !!label && {label}} ()] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); const [selectedDisplayFilter, setSelectedDisplayFilter] = useState< @@ -72,7 +71,7 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP const viewValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]; return ( - + setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER)} + onBackButtonPress={goBack} closeOverlay={closeOverlay} /> ); - break; case CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER: - subPopup = ( + return ( ); - break; case CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY: - subPopup = ( + return ( { const newValue = item?.value; if (!newValue) { @@ -212,52 +200,37 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP }} /> ); - break; case CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY: - subPopup = ( + return ( updateFilterForm({groupCurrency: item?.value})} + onBackButtonPress={goBack} closeOverlay={closeOverlay} /> ); - break; case CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW: - subPopup = ( + return ( updateFilterForm({view: item?.value ?? CONST.SEARCH.VIEW.TABLE})} /> ); - break; case CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT: - subPopup = ( + return ( updateFilterForm({limit: value})} /> ); - break; - default: - break; } - - return ( - - - {subPopup} - - ); } export default DisplayPopup; diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index 4e835a205266..91d3e645fed5 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -191,6 +191,7 @@ function DropdownButton({ restoreFocusType={CONST.MODAL.RESTORE_FOCUS_TYPE.DELETE} shouldEnableNewFocusManagement shouldMeasureAnchorPositionFromTop={false} + shouldUseModalPaddingStyle={false} outerStyle={{...StyleUtils.getOuterModalStyle(windowHeight, viewportOffsetTop), ...containerStyles}} // This must be false because we dont want the modal to close if we open the RHP for selections // such as date years diff --git a/src/components/Search/FilterDropdowns/GroupByPopup.tsx b/src/components/Search/FilterDropdowns/GroupByPopup.tsx index a8f5eee33059..317449d10e6a 100644 --- a/src/components/Search/FilterDropdowns/GroupByPopup.tsx +++ b/src/components/Search/FilterDropdowns/GroupByPopup.tsx @@ -5,6 +5,7 @@ import type {SearchGroupBy} from '@components/Search/types'; import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; import type {ListItem} from '@components/SelectionList/types'; +import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -18,9 +19,6 @@ type GroupByPopupItem = { }; type GroupByPopupProps = { - /** The label to show when in an overlay on mobile */ - label?: string; - /** The grouped options to show in the list */ sections: GroupBySection[]; @@ -28,6 +26,7 @@ type GroupByPopupProps = { value: GroupByPopupItem | null; style?: StyleProp; + onBackButtonPress: () => void; /** Function to call to close the overlay when changes are applied */ closeOverlay: () => void; @@ -36,10 +35,10 @@ type GroupByPopupProps = { onChange: (item: GroupByPopupItem | null) => void; }; -function GroupByPopup({label, value, sections, style, closeOverlay, onChange}: GroupByPopupProps) { +function GroupByPopup({value, sections, style, onBackButtonPress, closeOverlay, onChange}: GroupByPopupProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth, isInLandscapeMode} = useResponsiveLayout(); + const {isInLandscapeMode} = useResponsiveLayout(); const {windowHeight} = useWindowDimensions(); const [selectedItem, setSelectedItem] = useState(value); @@ -59,7 +58,7 @@ function GroupByPopup({label, value, sections, style, closeOverlay, onChange}: G [sections, selectedItem?.value, styles.dividerLine], ); - const optionsCount = Math.max( + const itemCount = Math.max( 1, listSections.reduce((count, section) => count + section.data.length + (section.data.length > 0 && !!section.customHeader ? 1 : 0), 0), ); @@ -82,25 +81,22 @@ function GroupByPopup({label, value, sections, style, closeOverlay, onChange}: G closeOverlay(); }, [closeOverlay, onChange]); - const shouldShowLabel = isSmallScreenWidth && !!label; - return ( - - - + ); } diff --git a/src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx b/src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx index 0f3a4946ba33..d77db54a3777 100644 --- a/src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx +++ b/src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx @@ -3,7 +3,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import {useCurrencyListActions, useCurrencyListState} from '@components/CurrencyListContextProvider'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useThemeStyles from '@hooks/useThemeStyles'; import {getCurrencyOptions} from '@libs/SearchUIUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; @@ -11,6 +10,7 @@ import SingleSelectPopup from './SingleSelectPopup'; import type {SingleSelectItem} from './SingleSelectPopup'; type GroupCurrencyPopupProps = { + onBackButtonPress: () => void; closeOverlay: () => void; onChange: (item: SingleSelectItem | null) => void; }; @@ -19,8 +19,7 @@ function filterGroupCurrencySelector(searchAdvancedFiltersForm: OnyxEntry ({label, loading, value, items, close const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'MultiSelectPopupDataLoading'}; + const hasTitle = isSmallScreenWidth && !!label; + return ( ({label, loading, value, items, close onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_MULTI_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_MULTI_SELECT} + style={[styles.getSelectionListPopoverHeight({itemCount: listData.length || 1, windowHeight, isInLandscapeMode, hasTitle})]} > - - {!!loading && ( - - - - )} - - {!loading && ( - + - )} - + + )} + + {!loading && ( + + )} ); } diff --git a/src/components/Search/FilterDropdowns/ReportFieldPopup.tsx b/src/components/Search/FilterDropdowns/ReportFieldPopup.tsx index cee095c86bcf..fcb7a9097748 100644 --- a/src/components/Search/FilterDropdowns/ReportFieldPopup.tsx +++ b/src/components/Search/FilterDropdowns/ReportFieldPopup.tsx @@ -7,11 +7,11 @@ import MenuItem from '@components/MenuItem'; import ScrollView from '@components/ScrollView'; import type {SearchDateValues} from '@components/Search/FilterComponents/DatePresetFilterBase'; import type {ReportFieldDateKey, ReportFieldTextKey} from '@components/Search/types'; +import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import {getDateRangeDisplayValueFromFormValue, isSearchDatePreset} from '@libs/SearchQueryUtils'; import {getDatePresets} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; @@ -49,10 +49,6 @@ function getFieldNameAsKey(fieldName: string) { } function ReportFieldListPopup({value, field, onBackButtonPress, onChange}: ReportFieldSubPopupProps) { - const styles = useThemeStyles(); - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); - const items = field.values.map((fieldValue) => ({ value: fieldValue, text: fieldValue, @@ -60,32 +56,25 @@ function ReportFieldListPopup({value, field, onBackButtonPress, onChange}: Repor const selectedValue = {text: value, value}; return ( - - - onChange(item?.value ?? '')} - /> - + onChange(item?.value ?? '')} + /> ); } function ReportFieldDatePopup({value, field, onBackButtonPress, onChange}: ReportFieldDatePopupProps) { const styles = useThemeStyles(); - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); + const {windowHeight} = useWindowDimensions(); + const isInLandscapeMode = useIsInLandscapeMode(); const filterKey = `${CONST.SEARCH.REPORT_FIELD.DEFAULT_PREFIX}${getFieldNameAsKey(field.name)}` as const; return ( - + - - - + ); } function ReportFieldPopup({closeOverlay, updateFilterForm}: ReportFieldPopupProps) { const {translate, localeCompare} = useLocalize(); - const StyleUtils = useStyleUtils(); + const {windowHeight} = useWindowDimensions(); + const isInLandscapeMode = useIsInLandscapeMode(); + const styles = useThemeStyles(); const policyReportFieldsSelector = (policies: OnyxCollection) => createAllPolicyReportFieldsSelector(policies, localeCompare); const [fieldList] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { selector: policyReportFieldsSelector, @@ -275,8 +254,9 @@ function ReportFieldPopup({closeOverlay, updateFilterForm}: ReportFieldPopupProp onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_REPORT_FIELD} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_REPORT_FIELD} + style={[styles.getPopoverMaxHeight(windowHeight, isInLandscapeMode)]} > - + {listItems.map((item) => ( = { /** The currently selected item */ value: SingleSelectItem | null; + onBackButtonPress?: () => void; + /** Function to call to close the overlay when changes are applied */ closeOverlay: () => void; @@ -55,6 +56,7 @@ function SingleSelectPopup({ label, value, items, + onBackButtonPress, closeOverlay, onChange, isSearchable, @@ -131,32 +133,41 @@ function SingleSelectPopup({ [searchTerm, isSearchable, searchPlaceholder, translate, setSearchTerm, noResultsFound], ); - const shouldShowLabel = isSmallScreenWidth && !!label; + const hasTitle = isSmallScreenWidth && !!label && !onBackButtonPress; return ( - - - - - + + + ); } diff --git a/src/components/Search/FilterDropdowns/SortByPopup.tsx b/src/components/Search/FilterDropdowns/SortByPopup.tsx index 7979f4519b0b..5c893f5b412c 100644 --- a/src/components/Search/FilterDropdowns/SortByPopup.tsx +++ b/src/components/Search/FilterDropdowns/SortByPopup.tsx @@ -1,48 +1,68 @@ -import React from 'react'; +import React, {useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; import type {SearchColumnType, SearchGroupBy, SearchQueryJSON} from '@components/Search/types'; +import SelectionList from '@components/SelectionList'; +import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; +import type {ListItem} from '@components/SelectionList/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import {close} from '@libs/actions/Modal'; import Navigation from '@libs/Navigation/Navigation'; import {buildSearchQueryString} from '@libs/SearchQueryUtils'; -import {getColumnsToShow, getSearchColumnTranslationKey, getSortByOptions} from '@libs/SearchUIUtils'; +import {getColumnsToShow, getSortByOptions} from '@libs/SearchUIUtils'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {columnsSelector} from '@src/selectors/AdvancedSearchFiltersForm'; import type {SearchResults} from '@src/types/onyx'; -import SingleSelectPopup from './SingleSelectPopup'; +import BasePopup from './BasePopup'; import type {SingleSelectItem} from './SingleSelectPopup'; +const DIVIDER_HEIGHT = 25; + type SortByPopupProps = { searchResults: OnyxEntry; queryJSON: SearchQueryJSON; groupBy: SingleSelectItem | null; onSort: () => void; onSortOrderPress: () => void; + onBackButtonPress: () => void; closeOverlay: () => void; }; -function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPress, closeOverlay}: SortByPopupProps) { +function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPress, onBackButtonPress, closeOverlay}: SortByPopupProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const {windowHeight} = useWindowDimensions(); + const isInLandscapeMode = useIsInLandscapeMode(); const {accountID} = useCurrentUserPersonalDetails(); const {shouldUseLiveData} = useSearchStateContext(); const {clearSelectedTransactions} = useSearchActionsContext(); + const [visibleColumns] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {selector: columnsSelector}); + const searchDataType = shouldUseLiveData ? CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT : searchResults?.search?.type; const currentColumns = !searchResults?.data ? [] : getColumnsToShow({currentAccountID: accountID, data: searchResults.data, visibleColumns, type: searchDataType, groupBy: groupBy?.value}); const sortableColumns = getSortByOptions(currentColumns, translate); - const sortBy = {text: translate(getSearchColumnTranslationKey(queryJSON.sortBy)), value: queryJSON.sortBy}; const sortOrder = queryJSON.sortOrder; + const [selectedItem, setSelectedItem] = useState(queryJSON.sortBy); + + const options = sortableColumns.map((item) => ({ + text: item.text, + keyForList: item.value, + isSelected: item.value === selectedItem, + })); + const onSortChange = (column: SearchColumnType) => { clearSelectedTransactions(); const newQuery = buildSearchQueryString({...queryJSON, sortBy: column}); @@ -53,8 +73,42 @@ function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPres }); }; + const updateSelectedItem = (item: ListItem) => { + setSelectedItem(item.keyForList as SearchColumnType); + }; + + const applyChanges = () => { + onSortChange(selectedItem); + closeOverlay(); + }; + + const resetChanges = () => { + const defaultSortBy = sortableColumns.at(0)?.value; + if (!defaultSortBy) { + return; + } + onSortChange(defaultSortBy); + closeOverlay(); + }; + return ( - <> + - { - if (!item) { - return; - } - onSortChange(item.value); - }} + - + ); } diff --git a/src/components/Search/FilterDropdowns/SortOrderPopup.tsx b/src/components/Search/FilterDropdowns/SortOrderPopup.tsx index 83734a263380..d6187af4d22e 100644 --- a/src/components/Search/FilterDropdowns/SortOrderPopup.tsx +++ b/src/components/Search/FilterDropdowns/SortOrderPopup.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {useSearchActionsContext} from '@components/Search/SearchContext'; import type {SearchQueryJSON, SortOrder} from '@components/Search/types'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import {close} from '@libs/actions/Modal'; import Navigation from '@libs/Navigation/Navigation'; import {buildSearchQueryString} from '@libs/SearchQueryUtils'; @@ -12,12 +11,12 @@ import SingleSelectPopup from './SingleSelectPopup'; type SortOrderPopupProps = { queryJSON: SearchQueryJSON; onSort: () => void; + onBackButtonPress: () => void; closeOverlay: () => void; }; -function SortOrderPopup({queryJSON, onSort, closeOverlay}: SortOrderPopupProps) { +function SortOrderPopup({queryJSON, onSort, onBackButtonPress, closeOverlay}: SortOrderPopupProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const {clearSelectedTransactions} = useSearchActionsContext(); const onSortChange = (sortOrder: SortOrder) => { @@ -35,9 +34,10 @@ function SortOrderPopup({queryJSON, onSort, closeOverlay}: SortOrderPopupProps) return ( { diff --git a/src/components/Search/FilterDropdowns/TextInputPopup.tsx b/src/components/Search/FilterDropdowns/TextInputPopup.tsx index 588ed6ebb4b7..f46a27bbf52f 100644 --- a/src/components/Search/FilterDropdowns/TextInputPopup.tsx +++ b/src/components/Search/FilterDropdowns/TextInputPopup.tsx @@ -10,11 +10,12 @@ type TextInputPopupProps = { defaultValue: string; label?: string; placeholder?: string; + onBackButtonPress?: () => void; closeOverlay: () => void; onChange: (value: string) => void; }; -function TextInputPopup({style, defaultValue, label, placeholder, closeOverlay, onChange}: TextInputPopupProps) { +function TextInputPopup({style, defaultValue, label, placeholder, onBackButtonPress, closeOverlay, onChange}: TextInputPopupProps) { const styles = useThemeStyles(); const [value, setValue] = useState(defaultValue); @@ -33,6 +34,7 @@ function TextInputPopup({style, defaultValue, label, placeholder, closeOverlay, label={label} onReset={resetChanges} onApply={applyChanges} + onBackButtonPress={onBackButtonPress} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_TEXT_INPUT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_TEXT_INPUT} style={style} diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index e751d236cf06..7b1777eab6c5 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -14,7 +14,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {getParticipantsOption} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import BasePopup from './BasePopup'; @@ -24,7 +23,7 @@ type UserSelectPopupProps = { value: string[]; /** The popup label */ - label?: string; + label: string; /** Function to call to close the overlay when changes are applied */ closeOverlay: () => void; @@ -46,7 +45,8 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U const {translate} = useLocalize(); const personalDetails = usePersonalDetails(); const {windowHeight} = useWindowDimensions(); - const {shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout(); + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth, isInLandscapeMode} = useResponsiveLayout(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountID = currentUserPersonalDetails.accountID; const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); @@ -178,14 +178,13 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_USER} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_USER} style={[ - styles.getCommonSelectionListPopoverHeight( - listData.length || 1, - variables.optionRowHeightCompact, + styles.getSelectionListPopoverHeight({ + itemCount: listData.length || 1, windowHeight, - shouldUseNarrowLayout, isInLandscapeMode, - shouldShowSearchInput, - ), + hasTitle: isSmallScreenWidth, + isSearchable: shouldShowSearchInput, + }), ]} > { const dateFormValues: Record = {}; dateFormValues[`${filterKey}On`] = selectedDates[CONST.SEARCH.DATE_MODIFIERS.ON]; @@ -34,6 +40,7 @@ function DatePickerFilterPopup({closeOverlay, setPopoverWidth, filterKey, value, closeOverlay={closeOverlay} setPopoverWidth={setPopoverWidth} presets={getDatePresets(filterKey, true)} + style={[styles.getPopoverMaxHeight(windowHeight, isInLandscapeMode)]} /> ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 90866a7484c3..8075d6d36f4c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -62,6 +62,18 @@ type AnchorPosition = { vertical: number; }; +type SelectionListPopover = { + itemCount: number; + itemHeight?: number; + windowHeight: number; + isInLandscapeMode: boolean; + hasTitle?: boolean; + hasHeader?: boolean; + hasButton?: boolean; + isSearchable?: boolean; + extraHeight?: number; +}; + const getReceiptDropZoneViewStyle = (theme: ThemeColors, margin: number, paddingVertical: number): ViewStyle => ({ borderRadius: variables.componentBorderRadiusLarge, borderColor: theme.borderFocus, @@ -6273,49 +6285,34 @@ const dynamicStyles = (theme: ThemeColors) => width, }), - getSelectionListPopoverHeight: (itemCount: number, windowHeight: number, isSearchable: boolean, isInLandscapeMode: boolean, shouldShowLabel: boolean) => { - const SEARCHBAR_HEIGHT = isSearchable ? 52 : 0; - const SEARCHBAR_PADDING = isSearchable ? 12 : 0; - const PADDING = 32; - const GAP = 8; - const BUTTON_HEIGHT = 40; - const LABEL_HEIGHT = 26; - const ESTIMATED_LIST_HEIGHT = itemCount * variables.optionRowHeightCompact + SEARCHBAR_HEIGHT + SEARCHBAR_PADDING; - const popoverHeight = isInLandscapeMode ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE * windowHeight : CONST.POPOVER_DROPDOWN_MAX_HEIGHT; - const MAX_HEIGHT = popoverHeight - (PADDING + GAP + BUTTON_HEIGHT) - (shouldShowLabel ? LABEL_HEIGHT + GAP : 0); - - // Native platforms don't support maxHeight in the way thats expected, so lets manually set the height to either - // the listHeight, the max height of the popover, or 90% of the window height, such that we never overflow the screen - // and never expand over the max height - const height = Math.min(ESTIMATED_LIST_HEIGHT, MAX_HEIGHT, windowHeight * 0.9); - - return {height}; - }, - - getCommonSelectionListPopoverHeight: ( - itemCount: number, - itemHeight: number, - windowHeight: number, - shouldUseNarrowLayout: boolean, - isInLandscapeMode: boolean, - isSearchable = true, - ) => { - const MODAL_PADDING = 32; - const BUTTON_HEIGHT = 48; + getSelectionListPopoverHeight: ({ + itemCount, + itemHeight = variables.optionRowHeightCompact, + windowHeight, + isInLandscapeMode, + hasTitle, + hasHeader, + hasButton, + isSearchable, + extraHeight = 0, + }: SelectionListPopover) => { + const MODAL_VERTICAL_PADDING = 32; + const BUTTON_HEIGHT = hasButton ? 48 : 0; + const HEADER_HEIGHT = hasHeader ? 48 : 0; + const TITLE_HEIGHT = hasTitle ? 34 : 0; const SEARCHBAR_HEIGHT = isSearchable ? 64 : 0; - const TITLE_HEIGHT = shouldUseNarrowLayout ? 34 : 0; - const PADDING = shouldUseNarrowLayout ? 0 : MODAL_PADDING; - const ESTIMATED_LIST_HEIGHT = itemCount * itemHeight + SEARCHBAR_HEIGHT + BUTTON_HEIGHT + TITLE_HEIGHT + PADDING; + const ESTIMATED_LIST_HEIGHT = itemCount * itemHeight + SEARCHBAR_HEIGHT + BUTTON_HEIGHT + TITLE_HEIGHT + HEADER_HEIGHT + MODAL_VERTICAL_PADDING + extraHeight; - const popoverHeight = isInLandscapeMode ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE * windowHeight - MODAL_PADDING : CONST.POPOVER_DROPDOWN_MAX_HEIGHT; + const popoverHeight = isInLandscapeMode + ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE * windowHeight - MODAL_VERTICAL_PADDING + : CONST.POPOVER_DROPDOWN_MAX_HEIGHT; // Native platforms don't support maxHeight in the way thats expected, so lets manually set the height to either // the listHeight, the max height of the popover, or 90% of the window height, such that we never overflow the screen // and never expand over the max height - const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * 0.9); - const width = shouldUseNarrowLayout ? sizing.w100 : {width: CONST.POPOVER_DROPDOWN_WIDTH}; + const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO); - return {height, ...width}; + return {height}; }, getCardSelectionListPopoverHeight: ( @@ -6338,11 +6335,16 @@ const dynamicStyles = (theme: ThemeColors) => // Native platforms don't support maxHeight in the way thats expected, so lets manually set the height to either // the listHeight, the max height of the popover, or 90% of the window height, such that we never overflow the screen // and never expand over the max height - const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * 0.9); + const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO); return {height}; }, + getPopoverMaxHeight: (windowHeight: number, isInLandscapeMode: boolean) => { + const heightRatio = isInLandscapeMode ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE : CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO; + return {maxHeight: Math.min(CONST.POPOVER_DROPDOWN_MAX_HEIGHT, windowHeight * heightRatio)}; + }, + testDriveModalContainer: (shouldUseNarrowLayout: boolean) => ({ // On small/medium screens, we need to remove the top padding paddingTop: 0, From d828d4cd418e323b77da14e7f2b631490ad1e082 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 Apr 2026 18:24:35 +0800 Subject: [PATCH 2/8] lint --- .../MoneyRequestReportTransactionList.tsx | 2 +- src/components/Search/FilterDropdowns/DisplayPopup.tsx | 2 ++ src/components/Search/FilterDropdowns/MultiSelectPopup.tsx | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 431d301a1bbb..8ec4338e7fdd 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -508,7 +508,7 @@ function MoneyRequestReportTransactionList({ ), - [groupByOptions, reportLayoutGroupBy, styles, windowHeight, isSmallScreenWidth, isInLandscapeMode], + [groupByOptions, reportLayoutGroupBy, styles, windowHeight, isInLandscapeMode], ); const transactionListContent = ( diff --git a/src/components/Search/FilterDropdowns/DisplayPopup.tsx b/src/components/Search/FilterDropdowns/DisplayPopup.tsx index 2b52f91c136c..19f9abc51aff 100644 --- a/src/components/Search/FilterDropdowns/DisplayPopup.tsx +++ b/src/components/Search/FilterDropdowns/DisplayPopup.tsx @@ -230,6 +230,8 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP onChange={(value) => updateFilterForm({limit: value})} /> ); + default: + return null; } } diff --git a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx index 3ec013348219..77b2e25c8bc0 100644 --- a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx @@ -12,7 +12,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import BasePopup from './BasePopup'; From f5452b769dbb8deff858135779d04daf1d10aece Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 Apr 2026 18:35:38 +0800 Subject: [PATCH 3/8] fix button height not included --- .../MoneyRequestReportTransactionList.tsx | 2 +- src/styles/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 8ec4338e7fdd..0a91e26b15f4 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -491,7 +491,7 @@ function MoneyRequestReportTransactionList({ const groupByPopoverComponent = useCallback( (props: {closeOverlay: () => void}) => ( - + isInLandscapeMode, hasTitle, hasHeader, - hasButton, + hasButton = true, isSearchable, extraHeight = 0, }: SelectionListPopover) => { From 992d4fa8dc04959d5fef09a8fa711db0b7002efb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 18 Apr 2026 12:55:59 +0800 Subject: [PATCH 4/8] fix modal padding --- src/components/Search/FilterDropdowns/DropdownButton.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index 91d3e645fed5..f3417baa889e 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -191,12 +191,11 @@ function DropdownButton({ restoreFocusType={CONST.MODAL.RESTORE_FOCUS_TYPE.DELETE} shouldEnableNewFocusManagement shouldMeasureAnchorPositionFromTop={false} - shouldUseModalPaddingStyle={false} outerStyle={{...StyleUtils.getOuterModalStyle(windowHeight, viewportOffsetTop), ...containerStyles}} // This must be false because we dont want the modal to close if we open the RHP for selections // such as date years shouldCloseWhenBrowserNavigationChanged={false} - innerContainerStyle={containerStyles} + innerContainerStyle={{...containerStyles, ...styles.p0}} popoverDimensions={{ width: actualPopoverWidth, height: CONST.POPOVER_DROPDOWN_MIN_HEIGHT, From 5a890f32baeb425e7d58ae436acbe30a4dbaeda5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 22 Apr 2026 12:34:25 +0800 Subject: [PATCH 5/8] apply the height to the list instead of the whole popover --- .../FilterDropdowns/CardSelectPopup.tsx | 63 ++++++++++++------- .../Search/FilterDropdowns/GroupByPopup.tsx | 15 ++--- .../Search/FilterDropdowns/InSelectPopup.tsx | 39 +++++++----- .../FilterDropdowns/MultiSelectPopup.tsx | 41 ++++++------ .../FilterDropdowns/SingleSelectPopup.tsx | 52 ++++++++------- .../Search/FilterDropdowns/SortByPopup.tsx | 49 ++++++++------- .../FilterDropdowns/UserSelectPopup.tsx | 46 +++++++------- src/styles/index.ts | 34 ++-------- 8 files changed, 176 insertions(+), 163 deletions(-) diff --git a/src/components/Search/FilterDropdowns/CardSelectPopup.tsx b/src/components/Search/FilterDropdowns/CardSelectPopup.tsx index db87871c04b4..d00d1ca2afb7 100644 --- a/src/components/Search/FilterDropdowns/CardSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/CardSelectPopup.tsx @@ -19,6 +19,7 @@ import {openSearchCardFiltersPage} from '@libs/actions/Search'; import {buildCardFeedsData, buildCardsData, generateSelectedCards, getDomainFeedData, getSelectedCardsFromFeeds} from '@libs/CardFeedUtils'; import type {CardFilterItem} from '@libs/CardFeedUtils'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; @@ -39,7 +40,8 @@ function CardSelectPopup({isExpanded, updateFilterForm, closeOverlay}: CardSelec const illustrations = useThemeIllustrations(); const companyCardFeedIcons = useCompanyCardFeedIcons(); const {windowHeight} = useWindowDimensions(); - const {shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout(); + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth, isInLandscapeMode} = useResponsiveLayout(); const [areCardsLoaded] = useOnyx(ONYXKEYS.IS_SEARCH_FILTERS_CARD_DATA_LOADED); const [userCardList, userCardListMetadata] = useOnyx(ONYXKEYS.CARD_LIST); @@ -185,31 +187,44 @@ function CardSelectPopup({isExpanded, updateFilterForm, closeOverlay}: CardSelec onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_CARD} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_CARD} - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to fallback to 1 when it's 0 - style={styles.getCardSelectionListPopoverHeight(itemCount || 1, sectionHeaderCount, windowHeight, shouldUseNarrowLayout, isInLandscapeMode, shouldShowSearchInput)} > - {!!shouldShowLoadingState && ( - - + {!!shouldShowLoadingState && ( + + + + )} + {!shouldShowLoadingState && ( + + sections={sections} + ListItem={CardListItem} + onSelectRow={updateNewCards} + shouldPreventDefaultFocusOnSelectRow={false} + shouldShowTextInput={shouldShowSearchInput} + textInputOptions={textInputOptions} + shouldStopPropagation + canSelectMultiple /> - - )} - {!shouldShowLoadingState && ( - - sections={sections} - ListItem={CardListItem} - onSelectRow={updateNewCards} - shouldPreventDefaultFocusOnSelectRow={false} - shouldShowTextInput={shouldShowSearchInput} - textInputOptions={textInputOptions} - shouldStopPropagation - canSelectMultiple - /> - )} + )} + ); } diff --git a/src/components/Search/FilterDropdowns/GroupByPopup.tsx b/src/components/Search/FilterDropdowns/GroupByPopup.tsx index 317449d10e6a..7257ef459b4f 100644 --- a/src/components/Search/FilterDropdowns/GroupByPopup.tsx +++ b/src/components/Search/FilterDropdowns/GroupByPopup.tsx @@ -89,14 +89,15 @@ function GroupByPopup({value, sections, style, onBackButtonPress, closeOverlay, label={translate('search.display.groupBy')} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_SINGLE_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_SINGLE_SELECT} - style={[style, styles.getSelectionListPopoverHeight({itemCount, windowHeight, isInLandscapeMode, hasHeader: true})]} > - + + + ); } diff --git a/src/components/Search/FilterDropdowns/InSelectPopup.tsx b/src/components/Search/FilterDropdowns/InSelectPopup.tsx index cf5f14098092..90d028ecc4ed 100644 --- a/src/components/Search/FilterDropdowns/InSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/InSelectPopup.tsx @@ -1,4 +1,5 @@ import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMemberListItem'; @@ -192,21 +193,31 @@ function InSelectPopup({closeOverlay, updateFilterForm}: InSelectPopupProps) { onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_REPORT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_REPORT} - style={[ - styles.getSelectionListPopoverHeight({itemCount, itemHeight: variables.optionRowHeight, windowHeight, isInLandscapeMode, hasTitle: isSmallScreenWidth, isSearchable: true}), - ]} > - + + + ); } diff --git a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx index 77b2e25c8bc0..86cd0f995697 100644 --- a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx @@ -120,28 +120,29 @@ function MultiSelectPopup({label, loading, value, items, close onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_MULTI_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_MULTI_SELECT} - style={[styles.getSelectionListPopoverHeight({itemCount: listData.length || 1, windowHeight, isInLandscapeMode, hasTitle})]} > - {!!loading && ( - - + {!!loading && ( + + + + )} + + {!loading && ( + - - )} - - {!loading && ( - - )} + )} + ); } diff --git a/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx b/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx index 9a2927c35846..4996d4eec95a 100644 --- a/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx @@ -1,5 +1,6 @@ import React, {Activity, useCallback, useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import SelectionList from '@components/SelectionList'; import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; import type {ListItem, SelectionListStyle} from '@components/SelectionList/types'; @@ -143,31 +144,34 @@ function SingleSelectPopup({ onBackButtonPress={onBackButtonPress} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_SINGLE_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_SINGLE_SELECT} - style={[ - style, - styles.getSelectionListPopoverHeight({ - itemCount: options.length || 1, - windowHeight, - isInLandscapeMode, - hasTitle, - hasHeader: !!onBackButtonPress, - isSearchable: isSearchable ?? false, - }), - ]} + style={[style]} > - - - + + + + + ); } diff --git a/src/components/Search/FilterDropdowns/SortByPopup.tsx b/src/components/Search/FilterDropdowns/SortByPopup.tsx index 5c893f5b412c..4344d997551c 100644 --- a/src/components/Search/FilterDropdowns/SortByPopup.tsx +++ b/src/components/Search/FilterDropdowns/SortByPopup.tsx @@ -99,30 +99,33 @@ function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPres onBackButtonPress={onBackButtonPress} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_SINGLE_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_SINGLE_SELECT} - style={[ - styles.getSelectionListPopoverHeight({ - itemCount: sortableColumns.length, - windowHeight, - isInLandscapeMode, - hasHeader: true, - extraHeight: variables.optionRowHeight + DIVIDER_HEIGHT, - }), - ]} > - - - + + + + + ); } diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index 7b1777eab6c5..9c5e727b58d0 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -1,5 +1,6 @@ import isEmpty from 'lodash/isEmpty'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {View} from 'react-native'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import SelectionList from '@components/SelectionList'; import UserSelectionListItem from '@components/SelectionList/ListItem/UserSelectionListItem'; @@ -177,28 +178,31 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_USER} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_USER} - style={[ - styles.getSelectionListPopoverHeight({ - itemCount: listData.length || 1, - windowHeight, - isInLandscapeMode, - hasTitle: isSmallScreenWidth, - isSearchable: shouldShowSearchInput, - }), - ]} > - + + + ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 5c84dac96748..cfe214a6fdab 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -6374,42 +6374,16 @@ const dynamicStyles = (theme: ThemeColors) => const HEADER_HEIGHT = hasHeader ? 48 : 0; const TITLE_HEIGHT = hasTitle ? 34 : 0; const SEARCHBAR_HEIGHT = isSearchable ? 64 : 0; - const ESTIMATED_LIST_HEIGHT = itemCount * itemHeight + SEARCHBAR_HEIGHT + BUTTON_HEIGHT + TITLE_HEIGHT + HEADER_HEIGHT + MODAL_VERTICAL_PADDING + extraHeight; - const popoverHeight = isInLandscapeMode - ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE * windowHeight - MODAL_VERTICAL_PADDING - : CONST.POPOVER_DROPDOWN_MAX_HEIGHT; + const ESTIMATED_LIST_HEIGHT = itemCount * itemHeight + SEARCHBAR_HEIGHT + extraHeight; + const ESTIMATED_NON_LIST_HEIGHT = BUTTON_HEIGHT + HEADER_HEIGHT + TITLE_HEIGHT + MODAL_VERTICAL_PADDING; - // Native platforms don't support maxHeight in the way thats expected, so lets manually set the height to either - // the listHeight, the max height of the popover, or 90% of the window height, such that we never overflow the screen - // and never expand over the max height - const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO); - - return {height}; - }, - - getCardSelectionListPopoverHeight: ( - itemCount: number, - sectionHeaderCount: number, - windowHeight: number, - shouldUseNarrowLayout: boolean, - isInLandscapeMode: boolean, - isSearchable = true, - ) => { - const MODAL_PADDING = 32; - const BUTTON_HEIGHT = 48; - const SEARCHBAR_HEIGHT = isSearchable ? 64 : 0; - const TITLE_HEIGHT = shouldUseNarrowLayout ? 34 : 0; - const PADDING = shouldUseNarrowLayout ? 0 : MODAL_PADDING; - const ESTIMATED_LIST_HEIGHT = itemCount * variables.optionRowHeight + sectionHeaderCount * 28 + SEARCHBAR_HEIGHT + BUTTON_HEIGHT + TITLE_HEIGHT + PADDING; - - const popoverHeight = isInLandscapeMode ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE * windowHeight - MODAL_PADDING : CONST.POPOVER_DROPDOWN_MAX_HEIGHT; + const heightRatio = isInLandscapeMode ? CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE : CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO; // Native platforms don't support maxHeight in the way thats expected, so lets manually set the height to either // the listHeight, the max height of the popover, or 90% of the window height, such that we never overflow the screen // and never expand over the max height - const height = Math.min(ESTIMATED_LIST_HEIGHT, popoverHeight, windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO); - + const height = Math.min(ESTIMATED_LIST_HEIGHT, CONST.POPOVER_DROPDOWN_MAX_HEIGHT - ESTIMATED_NON_LIST_HEIGHT, windowHeight * heightRatio - ESTIMATED_NON_LIST_HEIGHT); return {height}; }, From b4ef4292eb54faa69dd8e9627880921bff678a43 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 22 Apr 2026 12:35:37 +0800 Subject: [PATCH 6/8] make it scrollable --- src/components/Search/FilterDropdowns/DisplayPopup.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Search/FilterDropdowns/DisplayPopup.tsx b/src/components/Search/FilterDropdowns/DisplayPopup.tsx index 19f9abc51aff..767b1f697421 100644 --- a/src/components/Search/FilterDropdowns/DisplayPopup.tsx +++ b/src/components/Search/FilterDropdowns/DisplayPopup.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; import type {SearchQueryJSON} from '@components/Search/types'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -71,7 +72,7 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP const viewValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]; return ( - + )} - + ); } From e8390291bbeb33bbd267f79766398662ebe686a0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 22 Apr 2026 12:35:45 +0800 Subject: [PATCH 7/8] pass missing isSearchable --- src/components/Search/FilterDropdowns/MultiSelectPopup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx index 86cd0f995697..5abba2ef3b17 100644 --- a/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/MultiSelectPopup.tsx @@ -121,7 +121,7 @@ function MultiSelectPopup({label, loading, value, items, close resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_MULTI_SELECT} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_MULTI_SELECT} > - + {!!loading && ( Date: Wed, 22 Apr 2026 12:44:39 +0800 Subject: [PATCH 8/8] remove unused var --- src/components/Search/FilterDropdowns/DisplayPopup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Search/FilterDropdowns/DisplayPopup.tsx b/src/components/Search/FilterDropdowns/DisplayPopup.tsx index 767b1f697421..addfabadf353 100644 --- a/src/components/Search/FilterDropdowns/DisplayPopup.tsx +++ b/src/components/Search/FilterDropdowns/DisplayPopup.tsx @@ -1,5 +1,4 @@ import React, {useState} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';