Skip to content

Update SearchPageHeaderInput to display recent search and chats #53198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 75 additions & 54 deletions src/components/Search/SearchPageHeaderInput.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {useIsFocused} from '@react-navigation/native';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand All @@ -6,6 +7,7 @@ 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 type {AnimatedTextInputRef} from '@components/RNTextInput';
import {isSearchQueryItem} from '@components/SelectionList/Search/SearchQueryListItem';
import type {SearchQueryItem} from '@components/SelectionList/Search/SearchQueryListItem';
import type {SelectionListHandle} from '@components/SelectionList/types';
Expand All @@ -20,6 +22,7 @@ import type {OptionData} from '@libs/ReportUtils';
import * as SearchAutocompleteUtils from '@libs/SearchAutocompleteUtils';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import variables from '@styles/variables';
import * as ReportUserActions from '@userActions/Report';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -31,6 +34,7 @@ import {getQueryWithSubstitutions} from './SearchRouter/getQueryWithSubstitution
import type {SubstitutionMap} from './SearchRouter/getQueryWithSubstitutions';
import {getUpdatedSubstitutionsMap} from './SearchRouter/getUpdatedSubstitutionsMap';
import SearchButton from './SearchRouter/SearchButton';
import {useSearchRouterContext} from './SearchRouter/SearchRouterContext';
import SearchRouterInput from './SearchRouter/SearchRouterInput';
import SearchRouterList from './SearchRouter/SearchRouterList';
import type {SearchQueryJSON, SearchQueryString} from './types';
Expand Down Expand Up @@ -69,23 +73,35 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const taxRates = useMemo(() => getAllTaxRates(), []);

const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState<SubstitutionMap>({});
const {type, inputQuery: originalInputQuery} = queryJSON;
const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON);
const queryText = SearchQueryUtils.buildUserReadableQueryString(queryJSON, personalDetails, reports, taxRates);
const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : '';

// The actual input text that the user sees
const [textInputValue, setTextInputValue] = useState(' '); // initial empty space to avoid quick flash of placeholder text
const [textInputValue, setTextInputValue] = useState(queryText);
// The input text that was last used for autocomplete; needed for the SearchRouterList when browsing list via arrow keys
const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(textInputValue);
const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(queryText);

const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState<SubstitutionMap>({});
const [isAutocompleteListVisible, setIsAutocompleteListVisible] = useState(false);
const listRef = useRef<SelectionListHandle>(null);
const textInputRef = useRef<AnimatedTextInputRef>(null);
const isFocused = useIsFocused();
const {registerSearchPageInput, unregisterSearchPageInput} = useSearchRouterContext();

const {type, inputQuery: originalInputQuery} = queryJSON;
const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON);
const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : '';
const queryText = SearchQueryUtils.buildUserReadableQueryString(queryJSON, personalDetails, reports, taxRates);

// If query is non-canned that means Search Input is displayed, so we need to register its ref in the context.
useEffect(() => {
setTextInputValue(queryText);
}, [queryText]);
if (!isFocused) {
return;
}

if (!isCannedQuery && textInputRef.current) {
registerSearchPageInput(textInputRef.current);
} else {
unregisterSearchPageInput();
}
}, [isCannedQuery, isFocused, registerSearchPageInput, unregisterSearchPageInput]);

useEffect(() => {
const substitutionsMap = buildSubstitutionsMap(originalInputQuery, personalDetails, reports, taxRates);
Expand Down Expand Up @@ -143,46 +159,45 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
[autocompleteSubstitutions, originalInputQuery, queryJSON.policyID],
);

const onListItemPress = (item: OptionData | SearchQueryItem) => {
if (!isSearchQueryItem(item)) {
return;
}

if (!item.searchQuery) {
return;
}

if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) {
const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue);
onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `);

if (item.text && item.autocompleteID) {
const substitutions = {...autocompleteSubstitutions, [item.text]: item.autocompleteID};

setAutocompleteSubstitutions(substitutions);
const onListItemPress = useCallback(
(item: OptionData | SearchQueryItem) => {
if (isSearchQueryItem(item)) {
if (!item.searchQuery) {
return;
}

if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) {
const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue);
onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `);

if (item.text && item.autocompleteID) {
const substitutions = {...autocompleteSubstitutions, [item.text]: item.autocompleteID};

setAutocompleteSubstitutions(substitutions);
}
} else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH) {
submitSearch(item.searchQuery);
}
} else if (item?.reportID) {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID));
} else if ('login' in item) {
ReportUserActions.navigateToAndOpenReport(item.login ? [item.login] : [], false);
}
} else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH) {
submitSearch(item.searchQuery);
}
};

const onListItemFocus = (focusedItem: SearchQueryItem) => {
if (!focusedItem.searchQuery) {
return;
}

const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue);
setTextInputValue(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(focusedItem.searchQuery)} `);
},
[autocompleteSubstitutions, onSearchQueryChange, submitSearch, textInputValue],
);

if (focusedItem.autocompleteID && focusedItem.text) {
const substitutions = {...autocompleteSubstitutions, [focusedItem.text]: focusedItem.autocompleteID};
const updateAutocompleteSubstitutions = useCallback(
(item: SearchQueryItem) => {
if (!item.autocompleteID || !item.text) {
return;
}

const substitutions = {...autocompleteSubstitutions, [item.text]: item.autocompleteID};
setAutocompleteSubstitutions(substitutions);
}
};

const hideAutocompleteList = () => setIsAutocompleteListVisible(false);
const showAutocompleteList = () => setIsAutocompleteListVisible(true);
},
[autocompleteSubstitutions],
);

if (isCannedQuery) {
const headerIcon = getHeaderContent(type).icon;
Expand All @@ -209,6 +224,12 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
);
}

const hideAutocompleteList = () => setIsAutocompleteListVisible(false);
const showAutocompleteList = () => {
listRef.current?.updateAndScrollToFocusedIndex(0);
setIsAutocompleteListVisible(true);
};

const searchQueryItem = textInputValue
? {
text: textInputValue,
Expand All @@ -220,11 +241,9 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
}
: undefined;

const isHeaderInputActive = isAutocompleteListVisible;

// we need `- BORDER_WIDTH` to achieve the effect that the input will not "jump"
const popoverHorizontalPosition = 12 - BORDER_WIDTH;
const autocompleteInputStyle = isHeaderInputActive
const autocompleteInputStyle = isAutocompleteListVisible
? [
styles.border,
styles.borderRadiusComponentLarge,
Expand All @@ -234,12 +253,12 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
{boxShadow: variables.popoverMenuShadow},
]
: [styles.pt4];
const inputWrapperStyle = isHeaderInputActive ? styles.ph2 : null;
const inputWrapperActiveStyle = isAutocompleteListVisible ? styles.ph2 : null;

return (
<View
dataSet={{dragArea: false}}
style={[styles.searchResultsHeaderBar, styles.mh85vh, isHeaderInputActive && styles.ph3]}
style={[styles.searchResultsHeaderBar, isAutocompleteListVisible && styles.ph3]}
>
<View style={[styles.appBG, ...autocompleteInputStyle]}>
<SearchRouterInput
Expand All @@ -254,16 +273,18 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
onBlur={hideAutocompleteList}
wrapperStyle={[styles.searchRouterInputResults, styles.br2]}
wrapperFocusedStyle={styles.searchRouterInputResultsFocused}
outerWrapperStyle={inputWrapperStyle}
outerWrapperStyle={[inputWrapperActiveStyle, styles.pb2]}
rightComponent={children}
routerListRef={listRef}
ref={textInputRef}
/>
<View style={[styles.pt2, !isHeaderInputActive && styles.dNone]}>
<View style={[styles.mh85vh, !isAutocompleteListVisible && styles.dNone]}>
<SearchRouterList
autocompleteQueryValue={autocompleteQueryValue}
searchQueryItem={searchQueryItem}
onListItemPress={onListItemPress}
onListItemFocus={onListItemFocus}
setTextQuery={setTextInputValue}
updateAutocompleteSubstitutions={updateAutocompleteSubstitutions}
ref={listRef}
/>
</View>
Expand Down
Loading
Loading