From 0b6de4da9ba064acd79f62b7bbbeefa947e7c2fa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 30 Apr 2026 17:29:35 +0800 Subject: [PATCH 1/5] add close button to filter chip to clear individual filter --- .../Search/FilterDropdowns/DropdownButton.tsx | 70 +++++++++++++------ .../SearchPageHeader/SearchFilterBar.tsx | 19 +++-- .../SearchPageHeader/useSearchFiltersBar.tsx | 38 ++++++++-- 3 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index f3417baa889e..c86b6ac705e6 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -5,13 +5,16 @@ import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import Button from '@components/Button'; import CaretWrapper from '@components/CaretWrapper'; +import Icon from '@components/Icon'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import Text from '@components/Text'; import withViewportOffsetTop from '@components/withViewportOffsetTop'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useOnyx from '@hooks/useOnyx'; import usePopoverPosition from '@hooks/usePopoverPosition'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; @@ -56,6 +59,7 @@ type DropdownButtonProps = WithSentryLabel & { /** Wrapper style for the outer view */ wrapperStyle?: StyleProp; + onClosePress?: () => void; }; const ANCHOR_ORIGIN = { @@ -75,13 +79,16 @@ function DropdownButton({ caretWrapperStyle, wrapperStyle, sentryLabel, + onClosePress, }: DropdownButtonProps) { // 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 icons = useMemoizedLazyExpensifyIcons(['Close']); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const theme = useTheme(); const {windowHeight} = useWindowDimensions(); const triggerRef = useRef(null); const anchorRef = useRef(null); @@ -144,6 +151,8 @@ function DropdownButton({ return PopoverComponent({closeOverlay: toggleOverlay, isExpanded: isOverlayVisible, setPopoverWidth: setCustomPopoverWidth}); }, [PopoverComponent, toggleOverlay, isOverlayVisible]); + const showCloseButton = !!onClosePress; + return ( ) : ( - + + {buttonText} + + + + {showCloseButton && ( + <> + + + + )} + )} {/* Dropdown overlay */} diff --git a/src/components/Search/SearchPageHeader/SearchFilterBar.tsx b/src/components/Search/SearchPageHeader/SearchFilterBar.tsx index 7cea47d9a294..b98d8f107050 100644 --- a/src/components/Search/SearchPageHeader/SearchFilterBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFilterBar.tsx @@ -14,7 +14,7 @@ import type {FilterItem} from './useSearchFiltersBar'; type SearchDropdownProps = Omit; -function UserDropdown({label, value, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function UserDropdown({label, value, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const users = useFilterUserValue(value); return ( ); } -function WorkspaceDropdown({label, value, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function WorkspaceDropdown({label, value, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const workspaceValue = useFilterWorkspaceValue(value); return ( ); } -function FeedDropdown({label, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function FeedDropdown({label, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const feedValue = useFilterFeedValue(); return ( ); } -function CardDropdown({label, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function CardDropdown({label, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const cardValue = useFilterCardValue(); return ( ); } -function TaxRateDropdown({label, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function TaxRateDropdown({label, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const taxRateValue = useFilterTaxRateValue(); return ( ); } -function ReportDropdown({label, value, PopoverComponent, sentryLabel}: SearchDropdownProps) { +function ReportDropdown({label, value, PopoverComponent, sentryLabel, onClosePress}: SearchDropdownProps) { const reportValue = useFilterReportValue(value); return ( ); } @@ -111,6 +117,7 @@ function SearchFilterBar({item}: {item: SearchFilter & FilterItem}) { value={item.value} PopoverComponent={item.PopoverComponent} sentryLabel={item.sentryLabel} + onClosePress={item.onClosePress} /> ); } diff --git a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx index 01227e6d2fb9..2c19e66a7f21 100644 --- a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx @@ -41,6 +41,7 @@ import MultiSelectFilterPopup from './MultiSelectFilterPopup'; type FilterItem = WithSentryLabel & { PopoverComponent: (props: PopoverComponentProps) => ReactNode; + onClosePress: () => void; }; type UseSearchFiltersBarResult = { @@ -121,6 +122,13 @@ function makeDateFilterItem( /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => + updateFilterForm({ + [`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.ON}`]: undefined, + [`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.BEFORE}`]: undefined, + [`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}`]: undefined, + [`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.RANGE}`]: undefined, + }), }; } @@ -146,6 +154,12 @@ function makeAmountFilterItem( /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => + updateFilterForm({ + [`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.EQUAL_TO}`]: undefined, + [`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.GREATER_THAN}`]: undefined, + [`${filterKey}${CONST.SEARCH.AMOUNT_MODIFIERS.LESS_THAN}`]: undefined, + }), }; } @@ -210,12 +224,21 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => { + const formValues = Object.keys(searchAdvancedFiltersForm).reduce((acc, curr) => { + if (curr.startsWith(CONST.SEARCH.REPORT_FIELD.GLOBAL_PREFIX)) { + acc[curr as SearchAdvancedFiltersKey] = undefined; + } + return acc; + }, {} as Partial); + updateFilterForm(formValues); + }, }; } const label = FILTER_LABEL_MAP[filterKey]; if (!label) { - return {PopoverComponent: () => null}; + return {PopoverComponent: () => null, onClosePress: () => {}}; } switch (filterKey) { @@ -239,6 +262,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.CARD_ID: { @@ -251,6 +275,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.FEED: { @@ -263,6 +288,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.MERCHANT: @@ -282,6 +308,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.CURRENCY: @@ -300,6 +327,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.BILLABLE: @@ -317,7 +345,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes onChange={(item) => updateFilterForm({[filterKey]: item?.value})} /> ); - return {PopoverComponent: singleSelectComponent, sentryLabel: getFilterSentryLabel(filterKey)}; + return {PopoverComponent: singleSelectComponent, sentryLabel: getFilterSentryLabel(filterKey), onClosePress: () => updateFilterForm({[filterKey]: undefined})}; } case FILTER_KEYS.HAS: case FILTER_KEYS.IS: @@ -350,7 +378,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ); - return {PopoverComponent: multiSelectComponent, sentryLabel: getFilterSentryLabel(filterKey)}; + return {PopoverComponent: multiSelectComponent, sentryLabel: getFilterSentryLabel(filterKey), onClosePress: () => updateFilterForm({[filterKey]: undefined})}; } case FILTER_KEYS.ASSIGNEE: case FILTER_KEYS.ATTENDEE: @@ -370,6 +398,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; case FILTER_KEYS.POLICY_ID: return { @@ -381,10 +410,11 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; default: // This should be unreachable - return {PopoverComponent: () => null}; + return {PopoverComponent: () => null, onClosePress: () => {}}; } }); From c64aa97793390de445fdf1a3730f69fbeec2815e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 30 Apr 2026 23:50:51 +0800 Subject: [PATCH 2/5] don't show keyword filter chip --- src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx index 2c19e66a7f21..4e9d0652c910 100644 --- a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx @@ -62,6 +62,7 @@ const SKIPPED_FILTERS = new Set([ FILTER_KEYS.PAYER, FILTER_KEYS.ACTION, FILTER_KEYS.COLUMNS, + FILTER_KEYS.KEYWORD, ]); function getFilterSentryLabel(filterKey: SearchAdvancedFiltersKey | SearchFilterKey | ReportFieldKey) { @@ -294,7 +295,6 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes case FILTER_KEYS.MERCHANT: case FILTER_KEYS.DESCRIPTION: case FILTER_KEYS.REPORT_ID: - case FILTER_KEYS.KEYWORD: case FILTER_KEYS.TITLE: case FILTER_KEYS.WITHDRAWAL_ID: { return { From a82ab16a8d06dc56c2b08fdaca35d87186739333 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 1 May 2026 00:01:59 +0800 Subject: [PATCH 3/5] update width to 24 --- src/components/Search/FilterDropdowns/DropdownButton.tsx | 2 +- src/styles/index.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index c86b6ac705e6..053368ec939f 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -195,7 +195,7 @@ function DropdownButton({ - {showCloseButton && ( + {shouldShowCloseButton && ( <>