Skip to content

Commit

Permalink
Merge pull request Expensify#53198 from software-mansion-labs/kicu/se…
Browse files Browse the repository at this point in the history
…arch-results-input

Update SearchPageHeaderInput to display recent search and chats
  • Loading branch information
luacmartins authored Dec 5, 2024
2 parents 18eca4b + 8e1fdcd commit fb8be4d
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 224 deletions.
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 @@ -19,6 +21,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 @@ -30,6 +33,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 @@ -68,23 +72,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 @@ -129,46 +145,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 @@ -195,6 +210,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 @@ -206,11 +227,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 @@ -220,12 +239,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 @@ -240,16 +259,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

0 comments on commit fb8be4d

Please sign in to comment.