From e871e30620b2070701f6d981832e3df538b3f505 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 8 May 2026 23:56:32 +0800 Subject: [PATCH 01/86] update advanced filters button to open filters popover --- assets/images/coins.svg | 11 +- .../multifactorAuthentication/fingerprint.svg | 8 +- assets/images/paycheck.svg | 7 + assets/images/user-arrow-left.svg | 7 + src/CONST/index.ts | 1 + .../Icon/chunks/expensify-icons.chunk.ts | 4 + .../PopoverWithMeasuredContent/index.tsx | 4 +- .../PopoverWithMeasuredContent/types.ts | 3 + src/components/SafeTriangle/index.native.tsx | 11 + src/components/SafeTriangle/index.tsx | 188 +++++++++++++++ src/components/SafeTriangle/type.ts | 8 + .../Search/FilterComponents/index.tsx | 4 +- .../AdvancedFiltersFullscreen.tsx | 99 ++++++++ .../AdvancedFilters/AdvancedFiltersPopup.tsx | 49 ++++ .../AdvancedFilters/AmountFilterComponent.tsx | 221 ++++++++++++++++++ .../AdvancedFilters/DateFilterComponent.tsx | 96 ++++++++ .../AdvancedFilters/FilterItem.tsx | 60 +++++ .../AdvancedFilters/FilterList.tsx | 47 ++++ .../ReportFieldFilterComponent.tsx | 67 ++++++ .../AdvancedFilters/SelectedFilterContent.tsx | 177 ++++++++++++++ .../FilterDropdowns/AdvancedFilters/index.tsx | 27 +++ .../useFullscreenAdvancedFilters.tsx | 9 + .../Search/FilterDropdowns/CommonPopup.tsx | 2 +- .../Search/FilterDropdowns/DropdownButton.tsx | 204 +++------------- .../FilterDropdowns/FilterPopupButton.tsx | 146 ++++++++++++ .../DatePickerFilterPopup.tsx | 2 +- .../SearchAdvancedFiltersButton.tsx | 60 +++-- .../SearchDisplayDropdownButton.tsx | 44 ++-- .../SearchFiltersBarNarrow.tsx | 5 +- .../SearchPageHeader/SearchFiltersBarWide.tsx | 19 +- .../SearchFiltersClearButton.tsx | 36 +++ .../SearchPageHeader/useSearchFiltersBar.tsx | 14 +- .../TableFilterButtons/buildFilterItems.tsx | 2 +- src/hooks/useAdvancedSearchFilters.ts | 123 +++++----- src/languages/de.ts | 8 +- src/languages/en.ts | 8 +- src/languages/es.ts | 8 +- src/languages/fr.ts | 8 +- src/languages/it.ts | 8 +- src/languages/ja.ts | 8 +- src/languages/nl.ts | 8 +- src/languages/pl.ts | 8 +- src/languages/pt-BR.ts | 8 +- src/languages/zh-hans.ts | 8 +- src/libs/SearchUIUtils.ts | 220 +++++++++++++---- src/libs/actions/Search.ts | 19 ++ .../domain/Members/DomainMembersPage.tsx | 2 +- src/styles/index.ts | 31 +++ 48 files changed, 1769 insertions(+), 348 deletions(-) create mode 100644 assets/images/paycheck.svg create mode 100644 assets/images/user-arrow-left.svg create mode 100644 src/components/SafeTriangle/index.native.tsx create mode 100644 src/components/SafeTriangle/index.tsx create mode 100644 src/components/SafeTriangle/type.ts create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/AdvancedFiltersFullscreen.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/AdvancedFiltersPopup.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/AmountFilterComponent.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/DateFilterComponent.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/FilterItem.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/FilterList.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/ReportFieldFilterComponent.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/SelectedFilterContent.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/index.tsx create mode 100644 src/components/Search/FilterDropdowns/AdvancedFilters/useFullscreenAdvancedFilters.tsx create mode 100644 src/components/Search/FilterDropdowns/FilterPopupButton.tsx create mode 100644 src/components/Search/SearchPageHeader/SearchFiltersClearButton.tsx diff --git a/assets/images/coins.svg b/assets/images/coins.svg index 3cf1c0e38204..ec362876c756 100644 --- a/assets/images/coins.svg +++ b/assets/images/coins.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/multifactorAuthentication/fingerprint.svg b/assets/images/multifactorAuthentication/fingerprint.svg index f6712ca78e14..da485148939f 100644 --- a/assets/images/multifactorAuthentication/fingerprint.svg +++ b/assets/images/multifactorAuthentication/fingerprint.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/assets/images/paycheck.svg b/assets/images/paycheck.svg new file mode 100644 index 000000000000..e20a48fd5ec6 --- /dev/null +++ b/assets/images/paycheck.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/images/user-arrow-left.svg b/assets/images/user-arrow-left.svg new file mode 100644 index 000000000000..83e9d63ae9f1 --- /dev/null +++ b/assets/images/user-arrow-left.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 4cedb7db122a..6829c6574e70 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -263,6 +263,7 @@ const CONST = { POPOVER_DATE_RANGE_WIDTH: 672, POPOVER_DATE_MAX_HEIGHT: 366, POPOVER_DATE_MIN_HEIGHT: 322, + ADVANCED_FILTERS_POPOVER_WIDTH: 582, TOOLTIP_ANIMATION_DURATION: 500, DROPDOWN_SCROLL_THRESHOLD: 5, // Multiplier for gyroscope animation in order to make it a bit more subtle diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts index 441af58e797c..03e21d745128 100644 --- a/src/components/Icon/chunks/expensify-icons.chunk.ts +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -183,6 +183,7 @@ import OfflineCloud from '@assets/images/offline-cloud.svg'; import Offline from '@assets/images/offline.svg'; import Paperclip from '@assets/images/paperclip.svg'; import Pause from '@assets/images/pause.svg'; +import Paycheck from '@assets/images/paycheck.svg'; import Pencil from '@assets/images/pencil.svg'; import Percent from '@assets/images/percent.svg'; import Phone from '@assets/images/phone.svg'; @@ -245,6 +246,7 @@ import TreasureChest from '@assets/images/treasure-chest.svg'; import Unlock from '@assets/images/unlock.svg'; import UploadAlt from '@assets/images/upload-alt.svg'; import Upload from '@assets/images/upload.svg'; +import UserArrowLeft from '@assets/images/user-arrow-left.svg'; import UserCheck from '@assets/images/user-check.svg'; import UserEye from '@assets/images/user-eye.svg'; import UserLock from '@assets/images/user-lock.svg'; @@ -411,6 +413,7 @@ const Expensicons = { OdometerEnd, Paperclip, Pause, + Paycheck, Pencil, Percent, Phone, @@ -454,6 +457,7 @@ const Expensicons = { Upload, UploadAlt, User, + UserArrowLeft, UserCheck, Users, VideoSlash, diff --git a/src/components/PopoverWithMeasuredContent/index.tsx b/src/components/PopoverWithMeasuredContent/index.tsx index a76434244264..043bffbb738f 100644 --- a/src/components/PopoverWithMeasuredContent/index.tsx +++ b/src/components/PopoverWithMeasuredContent/index.tsx @@ -11,7 +11,7 @@ import type PopoverWithMeasuredContentProps from './types'; * This component is a perf optimization, it return BOTTOM_DOCKED early, for small screens avoiding Popover measurement logic calculations. * It defers rendering of PopoverWithMeasuredContentBase to idle time to avoid blocking more priority UI updates with measurements. */ -function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode, ...props}: PopoverWithMeasuredContentProps) { +function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottomDockedInLandscapeMode, smallScreenModalType, ...props}: PopoverWithMeasuredContentProps) { // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -33,7 +33,7 @@ function PopoverWithMeasuredContent({shouldWrapModalChildrenInScrollViewIfBottom & { /** Whether to skip re-measurement when becoming visible (for components with static dimensions) */ shouldSkipRemeasurement?: boolean; + + smallScreenModalType?: BaseModalProps['type']; }; export default PopoverWithMeasuredContentProps; diff --git a/src/components/SafeTriangle/index.native.tsx b/src/components/SafeTriangle/index.native.tsx new file mode 100644 index 000000000000..6ff71d03d39c --- /dev/null +++ b/src/components/SafeTriangle/index.native.tsx @@ -0,0 +1,11 @@ +import type SafeTriangleProps from './type'; + +/** + * A component that provides a "safe triangle" wrapper. + * On native platforms, hover interactions are not applicable, so this is a no-op wrapper. + */ +function SafeTriangle({children}: SafeTriangleProps) { + return children; +} + +export default SafeTriangle; diff --git a/src/components/SafeTriangle/index.tsx b/src/components/SafeTriangle/index.tsx new file mode 100644 index 000000000000..b1e88744116c --- /dev/null +++ b/src/components/SafeTriangle/index.tsx @@ -0,0 +1,188 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {Polygon, Svg} from 'react-native-svg'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {isMobile} from '@libs/Browser'; +import htmlDivElementRef from '@src/types/utils/htmlDivElementRef'; +import type SafeTriangleProps from './type'; + +type Point = [number, number]; + +function isPointInPolygon(point: Point, polygon: Point[]) { + const [x, y] = point; + let isInside = false; + const length = polygon.length; + for (let i = 0, j = length - 1; i < length; j = i++) { + const [xi, yi] = polygon[i] || [0, 0]; + const [xj, yj] = polygon[j] || [0, 0]; + const intersect = yi >= y !== yj >= y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi; + if (intersect) { + isInside = !isInside; + } + } + return isInside; +} + +type SafeTriangleOverlay = { + submenuRef: React.RefObject; + containerRef: React.RefObject; +}; + +type Rect = { + top: number; + left: number; + width: number; + height: number; +}; + +function SafeTriangleOverlay({submenuRef, containerRef}: SafeTriangleOverlay) { + const styles = useThemeStyles(); + + const [points, setPoints] = useState(null); + const [svgRect, setSvgRect] = useState(null); + + const apexRef = useRef(null); + const lastCursorPos = useRef(null); + const lastCursorTime = useRef(0); + const timeoutRef = useRef(undefined); + + const getCursorSpeed = (x: number, y: number): number | null => { + const currentTime = performance.now(); + const elapsedTime = currentTime - lastCursorTime.current; + + if (lastCursorPos.current === null || elapsedTime === 0) { + lastCursorPos.current = [x, y]; + lastCursorTime.current = currentTime; + return null; + } + + const deltaX = x - lastCursorPos.current[0]; + const deltaY = y - lastCursorPos.current[1]; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const speed = distance / elapsedTime; // px / ms + + lastCursorPos.current = [x, y]; + lastCursorTime.current = currentTime; + + return speed; + }; + + const clearTriangle = () => { + setPoints(null); + setSvgRect(null); + apexRef.current = null; + }; + + const onMouseMove = (event: MouseEvent) => { + clearTimeout(timeoutRef.current); + + const {clientX, clientY} = event; + const speed = getCursorSpeed(clientX, clientY); + + if (!submenuRef.current) { + clearTriangle(); + return; + } + + const rect = submenuRef.current.getBoundingClientRect(); + if (!rect) { + clearTriangle(); + return; + } + + // If speed is slow, update the apex to the current cursor position + if (speed === null || speed < 0.1) { + apexRef.current = [clientX, clientY]; + } + + const [x, y] = apexRef.current ?? [clientX, clientY]; + + // Create a polygon from apex to the submenu's left edge + // We add 2 to x-coordinates to account for the -2 offset in the container's left style + const cursorPointOne: Point = [0, y - rect.top]; + const cursorPointTwo: Point = [rect.left - x + 2, 0]; + const cursorPointThree: Point = [rect.left - x + 2, rect.bottom - rect.top]; + + const polygon = [cursorPointOne, cursorPointTwo, cursorPointThree]; + + // Check if the current mouse position is within the safe triangle + // The polygon points are relative to [x - 2, rect.top], so we adjust the mouse position accordingly + const isSafe = isPointInPolygon([clientX - x + 2, clientY - rect.top], polygon); + + if (isSafe) { + const pointsStr = polygon.map((p) => p.join(',')).join(' '); + setPoints(pointsStr); + setSvgRect({ + top: rect.top, + left: x - 2, + height: rect.height, + width: rect.left - x + 2, + }); + timeoutRef.current = setTimeout(clearTriangle, 50); + } else { + clearTriangle(); + } + }; + + useEffect(() => { + const container = htmlDivElementRef(containerRef).current; + if (!container) { + return; + } + + container.addEventListener('mousemove', onMouseMove, true); + + return () => { + container.removeEventListener('mousemove', onMouseMove, true); + clearTimeout(timeoutRef.current); + }; + }, [onMouseMove]); + + if (!points || !svgRect) { + return null; + } + + return ( + + + + ); +} + +/** + * A component that creates a "safe triangle" area between the cursor and a submenu. + * This prevents the submenu from switching when the user moves the cursor + * diagonally towards the submenu by rendering an invisible SVG overlay. + */ +function SafeTriangle({submenuRef, children}: SafeTriangleProps) { + const styles = useThemeStyles(); + const containerRef = useRef(null); + + if (isMobile()) { + return children; + } + + return ( + + {children} + + + ); +} + +export default SafeTriangle; diff --git a/src/components/SafeTriangle/type.ts b/src/components/SafeTriangle/type.ts new file mode 100644 index 000000000000..5173cbb3acb7 --- /dev/null +++ b/src/components/SafeTriangle/type.ts @@ -0,0 +1,8 @@ +import type {View} from 'react-native'; + +type SafeTriangleProps = { + submenuRef: React.RefObject; + children: React.ReactNode; +}; + +export default SafeTriangleProps; diff --git a/src/components/Search/FilterComponents/index.tsx b/src/components/Search/FilterComponents/index.tsx index 4812cebd9d4a..87e1185d3aa8 100644 --- a/src/components/Search/FilterComponents/index.tsx +++ b/src/components/Search/FilterComponents/index.tsx @@ -5,7 +5,7 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import {FILTER_LABEL_MAP, getMultiSelectFilterOptions, getSingleSelectFilterOptions} from '@libs/SearchUIUtils'; +import {FILTER_VIEW_MAP, getMultiSelectFilterOptions, getSingleSelectFilterOptions} from '@libs/SearchUIUtils'; import type {SearchFilter} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -67,7 +67,7 @@ function TextInputFilterComponents({filterKey, value, onChange}: TextInputFilter const {translate} = useLocalize(); const styles = useThemeStyles(); - const label = translate(FILTER_LABEL_MAP[filterKey]); + const label = translate(FILTER_VIEW_MAP[filterKey].labelKey); return ( (null); + const [values, setValues] = useState>(searchAdvancedFiltersForm ?? {}); + + const updateFilterQueryParams = useUpdateFilterQuery(queryJSON, true); + + const advancedFiltersToReset = searchAdvancedFiltersForm ? getAdvancedFiltersToReset(searchAdvancedFiltersForm) : undefined; + + return ( + + { + if (selectedFilter) { + setSelectedFilter(null); + return; + } + closeOverlay(); + }} + /> + + + + {selectedFilter && ( + + { + setValues((prevValues) => ({...prevValues, ...values})); + setSelectedFilter(null); + }} + /> + + )} + {!selectedFilter && ( + <> + {!isEmptyObject(advancedFiltersToReset) && ( + @@ -209,36 +104,9 @@ function DropdownButton({ )} )} - - {/* Dropdown overlay */} - - {popoverContent} - - + /> ); } -export type {PopoverComponentProps, DropdownButtonProps}; -export default withViewportOffsetTop(DropdownButton); +export default DropdownButton; +export type {DropdownButtonProps}; diff --git a/src/components/Search/FilterDropdowns/FilterPopupButton.tsx b/src/components/Search/FilterDropdowns/FilterPopupButton.tsx new file mode 100644 index 000000000000..47d4007a7803 --- /dev/null +++ b/src/components/Search/FilterDropdowns/FilterPopupButton.tsx @@ -0,0 +1,146 @@ +import {willAlertModalBecomeVisibleSelector} from '@selectors/Modal'; +import type {ReactNode, RefObject} from 'react'; +import React, {useRef, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; +import type PopoverWithMeasuredContentProps from '@components/PopoverWithMeasuredContent/types'; +import withViewportOffsetTop from '@components/withViewportOffsetTop'; +import useOnyx from '@hooks/useOnyx'; +import usePopoverPosition from '@hooks/usePopoverPosition'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type PopoverComponentProps = { + isExpanded: boolean; + closeOverlay: () => void; + setPopoverWidth?: (width: number | undefined) => void; +}; + +type ButtonComponentProps = { + onPress: () => void; + ref: RefObject; + isExpanded: boolean; +}; + +type FilterPopupButtonProps = { + /** The viewport's offset */ + viewportOffsetTop: number; + + popoverWidth?: number; + + /** The component to render in the popover */ + PopoverComponent: (props: PopoverComponentProps) => ReactNode; + + /** The component to render as the button */ + ButtonComponent: React.ComponentType; + + /** Wrapper style for the outer view */ + wrapperStyle?: StyleProp; + + smallScreenModalType?: PopoverWithMeasuredContentProps['smallScreenModalType']; +}; + +const ANCHOR_ORIGIN = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, +}; + +function FilterPopupButton({viewportOffsetTop, popoverWidth, PopoverComponent, ButtonComponent, wrapperStyle, smallScreenModalType}: FilterPopupButtonProps) { + // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to distinguish RHL and narrow layout + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const {windowHeight} = useWindowDimensions(); + const triggerRef = useRef(null); + const anchorRef = useRef(null); + const [isOverlayVisible, setIsOverlayVisible] = useState(false); + const [customPopoverWidth, setCustomPopoverWidth] = useState(undefined); + const {calculatePopoverPosition} = usePopoverPosition(); + + const [popoverTriggerPosition, setPopoverTriggerPosition] = useState({ + horizontal: 0, + vertical: 0, + }); + + const [willAlertModalBecomeVisible] = useOnyx(ONYXKEYS.MODAL, {selector: willAlertModalBecomeVisibleSelector}); + + /** + * Toggle the overlay between open & closed + */ + const toggleOverlay = () => { + setIsOverlayVisible((previousValue) => { + if (!previousValue && willAlertModalBecomeVisible) { + return false; + } + + return !previousValue; + }); + }; + + /** + * Calculate popover position and toggle overlay + */ + const calculatePopoverPositionAndToggleOverlay = () => { + calculatePopoverPosition(anchorRef, ANCHOR_ORIGIN).then((pos) => { + setPopoverTriggerPosition({...pos, vertical: pos.vertical}); + toggleOverlay(); + }); + }; + + const actualPopoverWidth = customPopoverWidth ?? popoverWidth ?? CONST.POPOVER_DROPDOWN_WIDTH; + const containerStyles = isSmallScreenWidth ? styles.w100 : {width: actualPopoverWidth}; + + const popoverContent = PopoverComponent({closeOverlay: toggleOverlay, isExpanded: isOverlayVisible, setPopoverWidth: setCustomPopoverWidth}); + + return ( + + {/* Dropdown Trigger */} + + + {/* Dropdown overlay */} + + {popoverContent} + + + ); +} + +export type {PopoverComponentProps, FilterPopupButtonProps}; +export default withViewportOffsetTop(FilterPopupButton); diff --git a/src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx b/src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx index 801e835ec152..b66f04918b7c 100644 --- a/src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx +++ b/src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx @@ -1,6 +1,6 @@ import React from 'react'; import DateSelectPopup from '@components/Search/FilterDropdowns/DateSelectPopup'; -import type {PopoverComponentProps} from '@components/Search/FilterDropdowns/DropdownButton'; +import type {PopoverComponentProps} from '@components/Search/FilterDropdowns/FilterPopupButton'; import type {SearchDateFilterKeys} from '@components/Search/types'; import type {SearchDateValues} from '@libs/SearchQueryUtils'; import {getDatePresets} from '@libs/SearchUIUtils'; diff --git a/src/components/Search/SearchPageHeader/SearchAdvancedFiltersButton.tsx b/src/components/Search/SearchPageHeader/SearchAdvancedFiltersButton.tsx index f701468e8fa4..b8f8ae8d607e 100644 --- a/src/components/Search/SearchPageHeader/SearchAdvancedFiltersButton.tsx +++ b/src/components/Search/SearchPageHeader/SearchAdvancedFiltersButton.tsx @@ -10,10 +10,11 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSearchFilterSync from '@hooks/useSearchFilterSync'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {updateAdvancedFilters} from '@libs/actions/Search'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; +import AdvancedFilters from '../FilterDropdowns/AdvancedFilters'; +import useFullscreenAdvancedFilters from '../FilterDropdowns/AdvancedFilters/useFullscreenAdvancedFilters'; +import FilterPopupButton from '../FilterDropdowns/FilterPopupButton'; +import type {FilterPopupButtonProps, PopoverComponentProps} from '../FilterDropdowns/FilterPopupButton'; type SearchAdvancedFiltersButtonProp = { queryJSON: SearchQueryJSON; @@ -23,44 +24,63 @@ function SearchAdvancedFiltersButton({queryJSON}: SearchAdvancedFiltersButtonPro const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); - const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); + const fullscreen = useFullscreenAdvancedFilters(); + const {isMediumScreenWidth} = useResponsiveLayout(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter']); const filterFormValues = useFilterFormValues(queryJSON); useSearchFilterSync(queryJSON, filterFormValues); - const openAdvancedFilters = () => { - updateAdvancedFilters(filterFormValues); - Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS.getRoute()); - }; + const filtersPopup = ({closeOverlay}: PopoverComponentProps) => ( + + ); - if (shouldUseNarrowLayout || isMediumScreenWidth) { - return ( + if (fullscreen || isMediumScreenWidth) { + const ButtonComponent: FilterPopupButtonProps['ButtonComponent'] = ({onPress, ref}) => ( ); + + return ( + + ); } return ( -