diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index f3417baa889e..1b584bf742f7 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 shouldShowCloseButton = !!onClosePress; + return ( ) : ( - + + {buttonText} + + + + {shouldShowCloseButton && ( + <> + + + + )} + )} {/* Dropdown overlay */} diff --git a/src/components/Search/SearchAutocompleteInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx index af3ce43d86c4..e4e2e075bf53 100644 --- a/src/components/Search/SearchAutocompleteInput.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -58,12 +58,10 @@ type SearchAutocompleteInputProps = { /** Any additional styles to apply to text input along with FormHelperMessage */ outerWrapperStyle?: StyleProp; - inputStyle?: StyleProp; - inputContainerStyle?: StyleProp; - touchableInputWrapperStyle?: StyleProp; + clearButtonStyle?: StyleProp; /** Whether the search reports API call is running */ isSearchingForReports?: boolean; @@ -96,6 +94,7 @@ function SearchAutocompleteInput({ inputStyle, inputContainerStyle, touchableInputWrapperStyle, + clearButtonStyle, isSearchingForReports, selection, substitutionMap, @@ -222,6 +221,7 @@ function SearchAutocompleteInput({ textInputContainerStyles={[styles.borderNone, styles.pb0, styles.ph3, inputContainerStyle]} inputStyle={[inputWidth, styles.lineHeightUndefined, inputStyle]} touchableInputWrapperStyle={touchableInputWrapperStyle} + clearButtonStyle={clearButtonStyle} placeholderTextColor={theme.textSupporting} loadingSpinnerStyle={[styles.mt0, styles.mr1, styles.justifyContentCenter]} onFocus={() => { 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/SearchPageInputWide.tsx b/src/components/Search/SearchPageHeader/SearchPageInputWide.tsx index ea57d862fdb8..bd3b50034ddc 100644 --- a/src/components/Search/SearchPageHeader/SearchPageInputWide.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageInputWide.tsx @@ -74,6 +74,7 @@ function SearchPageInputWide({queryJSON, handleSearch}: SearchPageInputWideProps inputStyle={isAutocompleteListVisible ? undefined : styles.fontSizeLabel} inputContainerStyle={isAutocompleteListVisible ? undefined : styles.ph2} touchableInputWrapperStyle={isAutocompleteListVisible ? undefined : styles.searchPageInputWideTouchableWrapper} + clearButtonStyle={isAutocompleteListVisible ? undefined : styles.mh0} onSubmit={() => { const focusedOption = listRef.current?.getFocusedOption(); if (focusedOption) { diff --git a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx index 01227e6d2fb9..4e9d0652c910 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 = { @@ -61,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) { @@ -121,6 +123,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 +155,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 +225,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 +263,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.CARD_ID: { @@ -251,6 +276,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } case FILTER_KEYS.FEED: { @@ -263,12 +289,12 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes /> ), sentryLabel: getFilterSentryLabel(filterKey), + onClosePress: () => updateFilterForm({[filterKey]: undefined}), }; } 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 { @@ -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: () => {}}; } }); diff --git a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx index 39f8b0adc516..7f8967d929b1 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx @@ -80,6 +80,7 @@ function BaseTextInput({ placeholderTextColor, onClearInput, iconContainerStyle, + clearButtonStyle, shouldUseDefaultLineHeightForPrefix = true, ref, sentryLabel, @@ -452,7 +453,7 @@ function BaseTextInput({ setValue(''); onClearInput?.(); }} - style={[StyleUtils.getTextInputIconContainerStyles(hasLabel, false, verticalPaddingDiff)]} + style={[StyleUtils.getTextInputIconContainerStyles(hasLabel, false, verticalPaddingDiff), clearButtonStyle]} /> )} {!!inputProps.isLoading && !shouldShowClearButton && ( diff --git a/src/components/TextInput/BaseTextInput/implementation/index.tsx b/src/components/TextInput/BaseTextInput/implementation/index.tsx index 35f0d3e19d2b..7de0f789caa6 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.tsx @@ -82,6 +82,7 @@ function BaseTextInput({ placeholderTextColor, onClearInput, iconContainerStyle, + clearButtonStyle, shouldUseDefaultLineHeightForPrefix = true, ref, sentryLabel, @@ -528,7 +529,7 @@ function BaseTextInput({ setValue(''); onClearInput?.(); }} - style={[StyleUtils.getTextInputIconContainerStyles(hasLabel, false, verticalPaddingDiff)]} + style={[StyleUtils.getTextInputIconContainerStyles(hasLabel, false, verticalPaddingDiff), clearButtonStyle]} sentryLabel={sentryLabel ? `${sentryLabel}-ClearButton` : undefined} /> diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index bf3e11a4c4c4..123cfeb1c85f 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -165,6 +165,9 @@ type CustomBaseTextInputProps = ForwardedFSClassProps & /** Style for the icon container */ iconContainerStyle?: StyleProp; + /** Style for the clear button */ + clearButtonStyle?: StyleProp; + /** The width of inner content */ contentWidth?: number; diff --git a/src/styles/index.ts b/src/styles/index.ts index 4e3bb976d6dc..0ccc28cc1a3d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4448,6 +4448,10 @@ const staticStyles = (theme: ThemeColors) => minWidth: 22, }, + filterDropDownCloseIcon: { + minWidth: 24, + }, + dropDownSmallButtonArrowContain: { marginLeft: 3, marginRight: 6,