From 19058b3756f51130fb3a3da7e7dfc6c27635c745 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 15 Nov 2024 13:09:32 +0100 Subject: [PATCH] Add autocomplete list to SearchPageHeader and refactor it --- src/components/Search/SearchPageHeader.tsx | 137 +--------- .../Search/SearchPageHeaderInput.tsx | 239 ++++++++++++++++++ .../Search/SearchRouter/SearchRouter.tsx | 37 ++- .../Search/SearchRouter/SearchRouterInput.tsx | 10 + .../Search/SearchRouter/SearchRouterList.tsx | 8 +- .../SearchRouter/buildSubstitutionsMap.ts | 41 +++ src/libs/SearchQueryUtils.ts | 1 + src/pages/Search/SearchPage.tsx | 5 +- src/pages/Search/SearchSelectedNarrow.tsx | 1 - .../Search/SearchSelectionModeHeader.tsx | 7 +- src/styles/index.ts | 9 + 11 files changed, 350 insertions(+), 145 deletions(-) create mode 100644 src/components/Search/SearchPageHeaderInput.tsx create mode 100644 src/components/Search/SearchRouter/buildSubstitutionsMap.ts diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index e33654cbb9f3..e7d2e7c39042 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useMemo, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -6,13 +6,8 @@ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; -import Header from '@components/Header'; -import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/types'; -import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; import {usePersonalDetails} from '@components/OnyxProvider'; -import Text from '@components/Text'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -20,101 +15,24 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@libs/actions/Search'; -import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -import type IconAsset from '@src/types/utils/IconAsset'; import {useSearchContext} from './SearchContext'; -import SearchButton from './SearchRouter/SearchButton'; -import SearchRouterInput from './SearchRouter/SearchRouterInput'; +import SearchPageHeaderInput from './SearchPageHeaderInput'; import type {SearchQueryJSON} from './types'; -type HeaderWrapperProps = Pick & { - text: string; - value: string; - isCannedQuery: boolean; - onSubmit: () => void; - setValue: (input: string) => void; -}; - -function HeaderWrapper({icon, children, text, value, isCannedQuery, onSubmit, setValue}: HeaderWrapperProps) { - const styles = useThemeStyles(); - // If the icon is present, the header bar should be taller and use different font. - const isCentralPaneSettings = !!icon; - - return ( - - {isCannedQuery ? ( - - {!!icon && ( - - )} -
{text}} /> - {children} - - ) : ( - - - - )} - - ); -} - -type SearchPageHeaderProps = { - queryJSON: SearchQueryJSON; - hash: number; -}; +type SearchPageHeaderProps = {queryJSON: SearchQueryJSON}; type SearchHeaderOptionValue = DeepValueOf | undefined; -type HeaderContent = { - icon: IconAsset; - titleText: TranslationPaths; -}; - -function getHeaderContent(type: SearchDataTypes): HeaderContent { - switch (type) { - case CONST.SEARCH.DATA_TYPES.INVOICE: - return {icon: Illustrations.EnvelopeReceipt, titleText: 'workspace.common.invoices'}; - case CONST.SEARCH.DATA_TYPES.TRIP: - return {icon: Illustrations.Luggage, titleText: 'travel.trips'}; - case CONST.SEARCH.DATA_TYPES.CHAT: - return {icon: Illustrations.CommentBubblesBlue, titleText: 'common.chats'}; - case CONST.SEARCH.DATA_TYPES.EXPENSE: - default: - return {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'}; - } -} - -function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { +function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -136,19 +54,10 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {status, type} = queryJSON; - const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON); - const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : SearchQueryUtils.buildUserReadableQueryString(queryJSON, personalDetails, cardList, reports, taxRates); - const [inputValue, setInputValue] = useState(headerText); - - useEffect(() => { - setInputValue(headerText); - }, [headerText]); + const {status, hash} = queryJSON; const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); - const headerIcon = getHeaderContent(type).icon; - const handleDeleteExpenses = () => { if (selectedTransactionsKeys.length === 0) { return; @@ -182,7 +91,7 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { return; } - const reportIDList = (selectedReports?.filter((report) => !!report) as string[]) ?? []; + const reportIDList = selectedReports.filter((report): report is string => !!report) ?? []; SearchActions.exportSearchItemsToCSV( {query: status, jsonQuery: JSON.stringify(queryJSON), reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']}, () => { @@ -327,41 +236,18 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { return null; } - const onPress = () => { + const onFiltersButtonPress = () => { const filterFormValues = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); SearchActions.updateAdvancedFilters(filterFormValues); Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS); }; - const onSubmit = () => { - if (!inputValue) { - return; - } - const inputQueryJSON = SearchQueryUtils.buildSearchQueryJSON(inputValue); - if (inputQueryJSON) { - // Todo traverse the tree to update all the display values into id values; this is only temporary until autocomplete code from SearchRouter is implement here - // After https://github.com/Expensify/App/pull/51633 is merged, autocomplete functionality will be included into this component, and `getFindIDFromDisplayValue` can be removed - const computeNodeValueFn = SearchQueryUtils.getFindIDFromDisplayValue(cardList, taxRates); - const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(inputQueryJSON, computeNodeValueFn); - const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); - SearchActions.clearAllFilters(); - Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); - } else { - Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} user query failed to parse`, inputValue, false); - } - }; + const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON); return ( <> - + {headerButtonsOptions.length > 0 ? ( null} @@ -377,11 +263,10 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { innerStyles={!isCannedQuery && [styles.searchRouterInputResults, styles.borderNone]} text={translate('search.filtersHeader')} icon={Expensicons.Filters} - onPress={onPress} + onPress={onFiltersButtonPress} /> )} - {isCannedQuery && } - + ({}); + // The actual input text that the user sees + const [textInputValue, setTextInputValue] = useState(''); + // The input text that was last used for autocomplete; needed for the SearchRouterLiteList when browsing list via arrow keys + const [autocompleteInputValue, setAutocompleteInputValue] = useState(textInputValue); + + const [displayAutocompleteList, setDisplayAutocompleteList] = useState(true); + const listRef = useRef(null); + + const {type} = queryJSON; + const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON); + const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : ''; + const queryText = SearchQueryUtils.buildUserReadableQueryString(queryJSON, personalDetails, cardList, reports, taxRates); + + useEffect(() => { + // Todo handle setting a new text + // const foobar = buildSubstitutionsMap(queryText); + setTextInputValue(queryText); + }, [queryText]); + + const onSearchQueryChange = useCallback( + (userQuery: string) => { + const prevParsedQuery = parseForAutocomplete(textInputValue); + + let updatedUserQuery = userQuery; + // If the prev value was query with autocomplete, and the current query ends with a comma, then we allow to continue autocompleting the next value + if (prevParsedQuery?.autocomplete && userQuery.endsWith(',')) { + updatedUserQuery = `${userQuery.slice(0, userQuery.length - 1).trim()},`; + } + setTextInputValue(updatedUserQuery); + setAutocompleteInputValue(updatedUserQuery); + + const updatedSubstitutionsMap = getUpdatedSubstitutionsMap(userQuery, autocompleteSubstitutions); + setAutocompleteSubstitutions(updatedSubstitutionsMap); + + if (updatedUserQuery) { + listRef.current?.updateAndScrollToFocusedIndex(0); + } else { + listRef.current?.updateAndScrollToFocusedIndex(-1); + } + }, + [autocompleteSubstitutions, setTextInputValue, textInputValue], + ); + + const onSearchSubmit = useCallback( + (queryString: SearchQueryString) => { + const cleanedQueryString = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); + const inputQueryJSON = SearchQueryUtils.buildSearchQueryJSON(cleanedQueryString); + if (!inputQueryJSON) { + return; + } + + const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(inputQueryJSON, SearchQueryUtils.getUpdatedAmountValue); + const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); + Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); + + SearchActions.clearAllFilters(); + setTextInputValue(''); + setAutocompleteInputValue(''); + setDisplayAutocompleteList(false); + + // Todo Old + // const inputQueryJSON = SearchQueryUtils.buildSearchQueryJSON(''); + // if (inputQueryJSON) { + // // Todo traverse the tree to update all the display values into id values; this is only temporary until autocomplete code from SearchRouter is implement here + // // After https://github.com/Expensify/App/pull/51633 is merged, autocomplete functionality will be included into this component, and `getFindIDFromDisplayValue` can be removed + // const computeNodeValueFn = SearchQueryUtils.getFindIDFromDisplayValue(cardList, taxRates); + // const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(inputQueryJSON, computeNodeValueFn); + // const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); + // } else { + // Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} user query failed to parse`, {}, false); + // } + }, + [autocompleteSubstitutions], + ); + + const onAutocompleteSuggestionClick = (autocompleteKey: string, autocompleteID: string) => { + const substitutions = {...autocompleteSubstitutions, [autocompleteKey]: autocompleteID}; + + setAutocompleteSubstitutions(substitutions); + }; + + const hideAutocompleteList = () => setDisplayAutocompleteList(false); + const showAutocompleteList = () => setDisplayAutocompleteList(true); + + if (isCannedQuery) { + const headerIcon = getHeaderContent(type).icon; + + return ( + + + +
{headerText}} /> + + {children} + + + + + ); + } + + const autocompleteParsedQuery = parseForAutocomplete(autocompleteInputValue); + const isListVisible = !!autocompleteParsedQuery?.autocomplete && displayAutocompleteList; + + const searchQueryItem = textInputValue + ? { + text: textInputValue, + singleIcon: Expensicons.MagnifyingGlass, + searchQuery: textInputValue, + itemStyle: styles.activeComponentBG, + keyForList: 'findItem', + searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH, + } + : undefined; + + return ( + + { + onSearchSubmit(textInputValue); + }} + autoFocus={false} + onFocus={showAutocompleteList} + onBlur={hideAutocompleteList} + wrapperStyle={[styles.searchRouterInputResults, styles.br2]} + wrapperFocusedStyle={styles.searchRouterInputResultsFocused} + rightComponent={children} + routerListRef={listRef} + /> + + + + + ); +} + +SearchPageHeaderInput.displayName = 'SearchPageHeaderInput'; + +export default SearchPageHeaderInput; diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index d0acd05697d8..cf2e27bb2d5e 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -2,6 +2,7 @@ import {useNavigationState} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -14,17 +15,19 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import type {SearchOption} from '@libs/OptionsListUtils'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; import {parseForAutocomplete} from '@libs/SearchAutocompleteUtils'; import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; -import * as Report from '@userActions/Report'; +import * as ReportUserActions from '@userActions/Report'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type Report from '@src/types/onyx/Report'; import {getQueryWithSubstitutions} from './getQueryWithSubstitutions'; import type {SubstitutionMap} from './getQueryWithSubstitutions'; import {getUpdatedSubstitutionsMap} from './getUpdatedSubstitutionsMap'; @@ -104,26 +107,46 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { return reportOptions.slice(0, 10); }, [debouncedInputValue, filteredOptions, searchOptions]); - const contextualReportData = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined; + const reportForContextualSearch = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined; useEffect(() => { - Report.searchInServer(debouncedInputValue.trim()); + ReportUserActions.searchInServer(debouncedInputValue.trim()); }, [debouncedInputValue]); const sections = []; - if (contextualReportData && !textInputValue) { - const reportQueryValue = contextualReportData.text ?? contextualReportData.alternateText ?? contextualReportData.reportID; + if (reportForContextualSearch && !textInputValue) { + const reportQueryValue = reportForContextualSearch.text ?? reportForContextualSearch.alternateText ?? reportForContextualSearch.reportID; + + let roomType: ValueOf = CONST.SEARCH.DATA_TYPES.CHAT; + let autocompleteID = reportForContextualSearch.reportID; + if (reportForContextualSearch.isInvoiceRoom) { + roomType = CONST.SEARCH.DATA_TYPES.INVOICE; + // Todo understand why this typecasting is needed here + const report = reportForContextualSearch as SearchOption; + if (report.item && report.item?.invoiceReceiver && report.item.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + autocompleteID = report.item.invoiceReceiver.accountID.toString(); + } else { + autocompleteID = ''; + } + } + if (reportForContextualSearch.isPolicyExpenseChat) { + roomType = CONST.SEARCH.DATA_TYPES.EXPENSE; + autocompleteID = reportForContextualSearch.policyID ?? ''; + } + sections.push({ data: [ { - text: `${translate('search.searchIn')} ${contextualReportData.text ?? contextualReportData.alternateText}`, + text: `${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`, singleIcon: Expensicons.MagnifyingGlass, searchQuery: reportQueryValue, - autocompleteID: contextualReportData.reportID, + autocompleteID, itemStyle: styles.activeComponentBG, keyForList: 'contextualSearch', searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION, + roomType, + policyID: reportForContextualSearch.policyID, }, ], }); diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index a127792d71fe..dad32f5c0dea 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -37,6 +37,12 @@ type SearchRouterInputProps = { /** Whether the input should be focused */ autoFocus?: boolean; + /** Callback to call when the input gets focus */ + onFocus?: () => void; + + /** Callback to call when the input gets blur */ + onBlur?: () => void; + /** Any additional styles to apply */ wrapperStyle?: StyleProp; @@ -62,6 +68,8 @@ function SearchRouterInput({ disabled = false, shouldShowOfflineMessage = false, autoFocus = true, + onFocus, + onBlur, wrapperStyle, wrapperFocusedStyle, outerWrapperStyle, @@ -102,10 +110,12 @@ function SearchRouterInput({ onFocus={() => { setIsFocused(true); routerListRef?.current?.updateExternalTextInputFocus(true); + onFocus?.(); }} onBlur={() => { setIsFocused(false); routerListRef?.current?.updateExternalTextInputFocus(false); + onBlur?.(); }} isLoading={!!isSearchingForReports} /> diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 9be594dc1719..93eaf07845fd 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -132,6 +132,7 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList ); } +// Todo rename to SearchAutocompleteList once it's used in both Router and SearchPage function SearchRouterList( {textInputValue, searchQueryItem, additionalSections, onSearchQueryChange, setTextInputValue, onSearchSubmit, onAutocompleteSuggestionClick, closeRouter}: SearchRouterListProps, ref: ForwardedRef, @@ -392,6 +393,10 @@ function SearchRouterList( } if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); + // Fixme + // even though onSearchQueryChange cleans the autocompleteMap internally, + // the `onAutocompleteSuggestionClick` still carries old autocompleteMap in its closure + // as a result the autocompleteMap will never get cleaned onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `); if (item.autocompleteID && item.text) { @@ -403,6 +408,7 @@ function SearchRouterList( onSearchSubmit(item.searchQuery); } + // Todo cleanup this onSelectRow, move reportID to Router // Handle selection of "Recent chat" closeRouter(); if ('reportID' in item && item?.reportID) { @@ -438,7 +444,7 @@ function SearchRouterList( ListItem={SearchRouterItem} containerStyle={[styles.mh100]} sectionListStyle={[shouldUseNarrowLayout ? styles.ph5 : styles.ph2, styles.pb2]} - listItemWrapperStyle={[styles.pr3, styles.pl3]} + listItemWrapperStyle={[styles.ph3]} getItemHeight={getItemHeight} onLayout={setPerformanceTimersEnd} ref={ref} diff --git a/src/components/Search/SearchRouter/buildSubstitutionsMap.ts b/src/components/Search/SearchRouter/buildSubstitutionsMap.ts new file mode 100644 index 000000000000..3c4351121975 --- /dev/null +++ b/src/components/Search/SearchRouter/buildSubstitutionsMap.ts @@ -0,0 +1,41 @@ +import type {OnyxCollection} from 'react-native-onyx'; +import type {SearchAutocompleteQueryRange, SearchFilterKey} from '@components/Search/types'; +import * as parser from '@libs/SearchParser/autocompleteParser'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {SubstitutionMap} from './getQueryWithSubstitutions'; + +const getSubstitutionsKey = (filterKey: SearchFilterKey, value: string) => `${filterKey}:${value}`; + +/** + * Given a plaintext query and data + * this function will build from scratch + * + * Ex: + * query: `Test from:John1` + * substitutions: { + * from:SomeOtherJohn: 12345 + * } + * return: {} + */ +function buildSubstitutionsMap(query: string, personalDetails: OnyxTypes.PersonalDetailsList, cardList: OnyxTypes.CardList, reports: OnyxCollection): SubstitutionMap { + const parsedQuery = parser.parse(query) as {ranges: SearchAutocompleteQueryRange[]}; + + const searchAutocompleteQueryRanges = parsedQuery.ranges; + + if (searchAutocompleteQueryRanges.length === 0) { + return {}; + } + + const autocompleteQueryKeys = searchAutocompleteQueryRanges.map((range) => getSubstitutionsKey(range.key, range.value)); + + // Build a new substitutions map consisting of only the keys from old map, that appear in query + const updatedSubstitutionMap = autocompleteQueryKeys.reduce((map, key) => { + // + return map; + }, {} as SubstitutionMap); + + return updatedSubstitutionMap; +} + +// eslint-disable-next-line import/prefer-default-export +export {buildSubstitutionsMap}; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 6c2e570d58f9..b3a3ba4f5768 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -533,6 +533,7 @@ function getPolicyIDFromSearchQuery(queryJSON: SearchQueryJSON) { /** * @private * Returns the human-readable "pretty" value for a filter. + * Fixme use this for generating map? */ function getDisplayValue(filterName: string, filter: string, personalDetails: OnyxTypes.PersonalDetailsList, cardList: OnyxTypes.CardList, reports: OnyxCollection) { if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO) { diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 4c68d84a0520..33cc1cb37755 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -43,10 +43,7 @@ function SearchPage({route}: SearchPageProps) { > {!!queryJSON && ( <> - + diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index 536d96c23ed0..f1eb3110febb 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -56,7 +56,6 @@ function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) isDisabled={options.length === 0} shouldShowRightIcon={options.length !== 0} /> - - {!!queryJSON && ( - - )} + {!!queryJSON && } ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index ade24ef9d5be..7e78843f4c03 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2568,6 +2568,15 @@ const styles = (theme: ThemeColors) => width: '100%', }, + searchResultsHeaderBar: { + display: 'flex', + justifyContent: 'center', + height: variables.contentHeaderHeight, + zIndex: variables.popoverzIndex, + position: 'relative', + paddingHorizontal: 20, + }, + headerBarDesktopHeight: { height: variables.contentHeaderDesktopHeight, },