diff --git a/patches/react-native-reanimated+3.16.4+004+fix-transform-animation-gitter.patch b/patches/react-native-reanimated+3.16.4+004+fix-transform-animation-gitter.patch
new file mode 100644
index 000000000000..9e7657c2dda4
--- /dev/null
+++ b/patches/react-native-reanimated+3.16.4+004+fix-transform-animation-gitter.patch
@@ -0,0 +1,20 @@
+diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp b/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
+index 673ebd1..08d65fe 100644
+--- a/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
++++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
+@@ -710,6 +710,15 @@ void NativeReanimatedModule::performOperations() {
+ jsPropsUpdater.call(rt, viewTag, nonAnimatableProps);
+ }
+
++ if (propsRegistry_->shouldReanimatedSkipCommit()) {
++ // It may happen that `performOperations` is called on the UI thread
++ // while React Native tries to commit a new tree on the JS thread.
++ // In this case, we should skip the commit here and let React Native do
++ // it. The commit will include the current values from PropsRegistry which
++ // will be applied in ReanimatedCommitHook.
++ return;
++ }
++
+ bool hasLayoutUpdates = false;
+ for (const auto &[shadowNode, props] : copiedOperationsQueue) {
+ if (isThereAnyLayoutProp(rt, props->asObject(rt))) {
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index b98717b51f5d..b05985507176 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -37,7 +37,7 @@ const ROUTES = {
// This route renders the list of reports.
HOME: 'home',
- SEARCH_CENTRAL_PANE: {
+ SEARCH_ROOT: {
route: 'search',
getRoute: ({query, name}: {query: SearchQueryString; name?: string}) => `search?q=${encodeURIComponent(query)}${name ? `&name=${name}` : ''}` as const,
},
diff --git a/src/components/Navigation/BottomTabBar/index.tsx b/src/components/Navigation/BottomTabBar/index.tsx
index f516eb43d04f..729f234c5b4b 100644
--- a/src/components/Navigation/BottomTabBar/index.tsx
+++ b/src/components/Navigation/BottomTabBar/index.tsx
@@ -116,7 +116,7 @@ function BottomTabBar({selectedTab, isTooltipAllowed = false}: BottomTabBarProps
const cleanedQuery = handleQueryWithPolicyID(q, activeWorkspaceID);
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: cleanedQuery,
...rest,
}),
@@ -127,7 +127,7 @@ function BottomTabBar({selectedTab, isTooltipAllowed = false}: BottomTabBarProps
const defaultCannedQuery = SearchQueryUtils.buildCannedSearchQuery();
// when navigating to search we might have an activePolicyID set from workspace switcher
const query = activeWorkspaceID ? `${defaultCannedQuery} ${CONST.SEARCH.SYNTAX_ROOT_KEYS.POLICY_ID}:${activeWorkspaceID}` : defaultCannedQuery;
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query}));
});
}, [activeWorkspaceID, selectedTab]);
diff --git a/src/components/Navigation/TopBar.tsx b/src/components/Navigation/TopBar.tsx
index 775e60666bef..a9ca480aed90 100644
--- a/src/components/Navigation/TopBar.tsx
+++ b/src/components/Navigation/TopBar.tsx
@@ -10,28 +10,25 @@ import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
-import Navigation from '@libs/Navigation/Navigation';
-import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import SignInButton from '@pages/home/sidebar/SignInButton';
-import * as Session from '@userActions/Session';
+import {isAnonymousUser as isAnonymousUserUtil} from '@userActions/Session';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
type TopBarProps = {
breadcrumbLabel: string;
activeWorkspaceID?: string;
shouldDisplaySearch?: boolean;
- shouldDisplayCancelSearch?: boolean;
+ cancelSearch?: () => void;
};
-function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, shouldDisplayCancelSearch = false}: TopBarProps) {
+function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, cancelSearch}: TopBarProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const policy = usePolicy(activeWorkspaceID);
const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}});
const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
- const isAnonymousUser = Session.isAnonymousUser(session);
+ const isAnonymousUser = isAnonymousUserUtil(session);
const headerBreadcrumb = policy?.name
? {type: CONST.BREADCRUMB_TYPE.STRONG, text: policy.name}
@@ -63,12 +60,12 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true,
{displaySignIn && }
- {shouldDisplayCancelSearch && (
+ {!!cancelSearch && (
{
- Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()}));
+ cancelSearch();
}}
>
{translate('common.cancel')}
diff --git a/src/components/Search/SearchAutocompleteInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx
index a89c68364ad9..f0f3468a9481 100644
--- a/src/components/Search/SearchAutocompleteInput.tsx
+++ b/src/components/Search/SearchAutocompleteInput.tsx
@@ -1,9 +1,10 @@
+/* eslint-disable rulesdir/no-acc-spread-in-reduce */
import type {ForwardedRef, ReactNode, RefObject} from 'react';
-import React, {forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useState} from 'react';
+import React, {forwardRef, useCallback, useEffect, useLayoutEffect, useMemo} from 'react';
import {View} from 'react-native';
import type {StyleProp, TextInputProps, ViewStyle} from 'react-native';
import {useOnyx} from 'react-native-onyx';
-import {useSharedValue} from 'react-native-reanimated';
+import Animated, {LinearTransition, useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
import FormHelpMessage from '@components/FormHelpMessage';
import type {SelectionListHandle} from '@components/SelectionList/types';
import TextInput from '@components/TextInput';
@@ -12,6 +13,7 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {parseFSAttributes} from '@libs/Fullstory';
import runOnLiveMarkdownRuntime from '@libs/runOnLiveMarkdownRuntime';
@@ -52,10 +54,10 @@ type SearchAutocompleteInputProps = {
onBlur?: () => void;
/** Any additional styles to apply */
- wrapperStyle?: StyleProp;
+ wrapperStyle?: ViewStyle;
/** Any additional styles to apply when input is focused */
- wrapperFocusedStyle?: StyleProp;
+ wrapperFocusedStyle?: ViewStyle;
/** Any additional styles to apply to text input along with FormHelperMessage */
outerWrapperStyle?: StyleProp;
@@ -84,7 +86,7 @@ function SearchAutocompleteInput(
onBlur,
caretHidden = false,
wrapperStyle,
- wrapperFocusedStyle,
+ wrapperFocusedStyle = {},
outerWrapperStyle,
rightComponent,
isSearchingForReports,
@@ -94,8 +96,8 @@ function SearchAutocompleteInput(
ref: ForwardedRef,
) {
const styles = useThemeStyles();
+ const theme = useTheme();
const {translate} = useLocalize();
- const [isFocused, setIsFocused] = useState(false);
const {isOffline} = useNetwork();
const {activeWorkspaceID} = useActiveWorkspace();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
@@ -122,6 +124,12 @@ function SearchAutocompleteInput(
const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : '';
+ // we are handling focused/unfocused style using shared value instead of using state to avoid re-rendering. Otherwise layout animation in `Animated.View` will lag.
+ const focusedSharedValue = useSharedValue(false);
+ const wrapperAnimatedStyle = useAnimatedStyle(() => {
+ return focusedSharedValue.get() ? wrapperFocusedStyle : wrapperStyle ?? {};
+ });
+
useEffect(() => {
runOnLiveMarkdownRuntime(() => {
'worklet';
@@ -170,7 +178,10 @@ function SearchAutocompleteInput(
return (
-
+
{
- setIsFocused(true);
autocompleteListRef?.current?.updateExternalTextInputFocus(true);
+ focusedSharedValue.set(true);
onFocus?.();
}}
onBlur={() => {
- setIsFocused(false);
autocompleteListRef?.current?.updateExternalTextInputFocus(false);
+ focusedSharedValue.set(false);
onBlur?.();
}}
isLoading={!!isSearchingForReports}
@@ -216,8 +228,15 @@ function SearchAutocompleteInput(
selection={selection}
/>
- {!!rightComponent && {rightComponent}}
-
+ {!!rightComponent && (
+
+ {rightComponent}
+
+ )}
+
mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList), [userCardList, workspaceCardFeeds]);
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
const cardAutocompleteList = Object.values(allCards);
const participantsAutocompleteList = useMemo(() => {
diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx
similarity index 75%
rename from src/components/Search/SearchPageHeader.tsx
rename to src/components/Search/SearchPageHeader/SearchPageHeader.tsx
index d9c2d44adb6b..9f68e504abc3 100644
--- a/src/components/Search/SearchPageHeader.tsx
+++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx
@@ -10,6 +10,8 @@ import DecisionModal from '@components/DecisionModal';
import * as Expensicons from '@components/Icon/Expensicons';
import {usePersonalDetails} from '@components/OnyxProvider';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
+import {useSearchContext} from '@components/Search/SearchContext';
+import type {PaymentData, SearchQueryJSON} from '@components/Search/types';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
@@ -28,22 +30,20 @@ import {
import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getAllTaxRates, hasVBBA} from '@libs/PolicyUtils';
-import {buildFilterFormValuesFromQuery, isCannedSearchQuery} from '@libs/SearchQueryUtils';
+import {buildFilterFormValuesFromQuery} from '@libs/SearchQueryUtils';
import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
-import {useSearchContext} from './SearchContext';
import SearchPageHeaderInput from './SearchPageHeaderInput';
-import type {PaymentData, SearchQueryJSON} from './types';
-type SearchPageHeaderProps = {queryJSON: SearchQueryJSON};
+type SearchPageHeaderProps = {queryJSON: SearchQueryJSON; searchName?: string; searchRouterListVisible?: boolean; onSearchRouterFocus?: () => void};
type SearchHeaderOptionValue = DeepValueOf | undefined;
-function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
+function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, onSearchRouterFocus}: SearchPageHeaderProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
@@ -57,9 +57,9 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const taxRates = getAllTaxRates();
- const [userCardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
- const [workspaceCardFeeds = {}] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
- const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList), [userCardList, workspaceCardFeeds]);
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST);
const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
@@ -67,7 +67,6 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
-
const [isScreenFocused, setIsScreenFocused] = useState(false);
const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
@@ -315,94 +314,107 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
styles.textWrap,
]);
- if (shouldUseNarrowLayout) {
- if (selectionMode?.isEnabled) {
- return (
-
-
- {
- setIsDeleteExpensesConfirmModalVisible(false);
- }}
- title={translate('iou.deleteExpense', {count: selectedTransactionsKeys.length})}
- prompt={translate('iou.deleteConfirmation', {count: selectedTransactionsKeys.length})}
- confirmText={translate('common.delete')}
- cancelText={translate('common.cancel')}
- danger
- />
- setIsOfflineModalVisible(false)}
- secondOptionText={translate('common.buttonConfirm')}
- isVisible={isOfflineModalVisible}
- onClose={() => setIsOfflineModalVisible(false)}
- />
- setIsDownloadErrorModalVisible(false)}
- secondOptionText={translate('common.buttonConfirm')}
- isVisible={isDownloadErrorModalVisible}
- onClose={() => setIsDownloadErrorModalVisible(false)}
- />
-
- );
- }
- return null;
- }
-
- const onFiltersButtonPress = () => {
+ const onFiltersButtonPress = useCallback(() => {
hideProductTrainingTooltip();
const filterFormValues = buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, reports, taxRates);
updateAdvancedFilters(filterFormValues);
Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
- };
+ }, [allCards, currencyList, hideProductTrainingTooltip, personalDetails, policyCategories, policyTagsLists, queryJSON, reports, taxRates]);
+
+ const InputRightComponent = useMemo(() => {
+ return headerButtonsOptions.length > 0 ? (
+ null}
+ shouldAlwaysShowDropdownMenu
+ buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
+ customText={translate('workspace.common.selected', {count: selectedTransactionsKeys.length})}
+ options={headerButtonsOptions}
+ isSplitButton={false}
+ shouldUseStyleUtilityForAnchorPosition
+ />
+ ) : (
+
+
+
+ );
+ }, [
+ headerButtonsOptions,
+ onFiltersButtonPress,
+ renderProductTrainingTooltip,
+ selectedTransactionsKeys.length,
+ shouldShowProductTrainingTooltip,
+ styles.bgTransparent,
+ styles.borderNone,
+ styles.productTrainingTooltipWrapper,
+ styles.searchAutocompleteInputResults,
+ translate,
+ ]);
- const isCannedQuery = isCannedSearchQuery(queryJSON);
+ if (shouldUseNarrowLayout && selectionMode?.isEnabled) {
+ return (
+
+
+ {
+ setIsDeleteExpensesConfirmModalVisible(false);
+ }}
+ title={translate('iou.deleteExpense', {count: selectedTransactionsKeys.length})}
+ prompt={translate('iou.deleteConfirmation', {count: selectedTransactionsKeys.length})}
+ confirmText={translate('common.delete')}
+ cancelText={translate('common.cancel')}
+ danger
+ />
+ setIsOfflineModalVisible(false)}
+ secondOptionText={translate('common.buttonConfirm')}
+ isVisible={isOfflineModalVisible}
+ onClose={() => setIsOfflineModalVisible(false)}
+ />
+ setIsDownloadErrorModalVisible(false)}
+ secondOptionText={translate('common.buttonConfirm')}
+ isVisible={isDownloadErrorModalVisible}
+ onClose={() => setIsDownloadErrorModalVisible(false)}
+ />
+
+ );
+ }
return (
<>
-
- {headerButtonsOptions.length > 0 ? (
- null}
- shouldAlwaysShowDropdownMenu
- buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
- customText={translate('workspace.common.selected', {count: selectedTransactionsKeys.length})}
- options={headerButtonsOptions}
- isSplitButton={false}
- shouldUseStyleUtilityForAnchorPosition
- />
- ) : (
-
-
-
- )}
-
+
void;
+ searchName?: string;
+ inputRightComponent: React.ReactNode;
};
-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 SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps) {
- const {translate} = useLocalize();
+function SearchPageHeaderInput({queryJSON, searchRouterListVisible, onSearchRouterFocus, searchName, inputRightComponent}: SearchPageHeaderInputProps) {
+ const [showPopupButton, setShowPopupButton] = useState(true);
const styles = useThemeStyles();
+ const {shouldUseNarrowLayout: displayNarrowHeader} = useResponsiveLayout();
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const taxRates = useMemo(() => getAllTaxRates(), []);
- const [userCardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
- const [workspaceCardFeeds = {}] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
- const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList), [userCardList, workspaceCardFeeds]);
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
- const {type, inputQuery: originalInputQuery} = queryJSON;
- const isCannedQuery = isCannedSearchQuery(queryJSON);
+ const {inputQuery: originalInputQuery} = queryJSON;
+ const isDefaultQuery = isDefaultExpensesQuery(queryJSON);
const queryText = buildUserReadableQueryString(queryJSON, personalDetails, reports, taxRates, allCards);
- const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : '';
// The actual input text that the user sees
- const [textInputValue, setTextInputValue] = useState(queryText);
+ const [textInputValue, setTextInputValue] = useState(isDefaultQuery ? '' : queryText);
// The input text that was last used for autocomplete; needed for the SearchAutocompleteList when browsing list via arrow keys
- const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(queryText);
+ const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(isDefaultQuery ? '' : queryText);
const [selection, setSelection] = useState({start: textInputValue.length, end: textInputValue.length});
const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState({});
@@ -93,30 +71,53 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
const listRef = useRef(null);
const textInputRef = useRef(null);
const isFocused = useIsFocused();
- const {registerSearchPageInput, unregisterSearchPageInput} = useSearchRouterContext();
+ const {registerSearchPageInput} = useSearchRouterContext();
- // If query is non-canned that means Search Input is displayed, so we need to register its ref in the context.
+ // useEffect for blurring TextInput when we cancel SearchRouter interaction on narrow layout
useEffect(() => {
- if (!isFocused) {
+ if (!displayNarrowHeader || !!searchRouterListVisible || !textInputRef.current || !textInputRef.current.isFocused()) {
return;
}
+ textInputRef.current.blur();
+ // eslint-disable-next-line react-compiler/react-compiler
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [searchRouterListVisible]);
- if (!isCannedQuery && textInputRef.current) {
- registerSearchPageInput(textInputRef.current);
- } else {
- unregisterSearchPageInput();
+ useEffect(() => {
+ if (displayNarrowHeader || !isFocused || !textInputRef.current) {
+ return;
}
- }, [isCannedQuery, isFocused, registerSearchPageInput, unregisterSearchPageInput]);
+
+ registerSearchPageInput(textInputRef.current);
+ }, [isFocused, registerSearchPageInput, displayNarrowHeader]);
useEffect(() => {
- setTextInputValue(queryText);
- }, [queryText]);
+ setTextInputValue(isDefaultQuery ? '' : queryText);
+ setAutocompleteQueryValue(isDefaultQuery ? '' : queryText);
+ }, [isDefaultQuery, queryText]);
useEffect(() => {
const substitutionsMap = buildSubstitutionsMap(originalInputQuery, personalDetails, reports, taxRates, allCards);
setAutocompleteSubstitutions(substitutionsMap);
}, [allCards, originalInputQuery, personalDetails, reports, taxRates]);
+ useEffect(() => {
+ if (searchRouterListVisible) {
+ return;
+ }
+ setShowPopupButton(true);
+ // eslint-disable-next-line react-compiler/react-compiler
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [searchRouterListVisible]);
+
+ const onFocus = useCallback(() => {
+ onSearchRouterFocus?.();
+ listRef.current?.updateAndScrollToFocusedIndex(0);
+ setShowPopupButton(false);
+ // eslint-disable-next-line react-compiler/react-compiler
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const onSearchQueryChange = useCallback(
(userQuery: string) => {
const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, userQuery);
@@ -145,7 +146,7 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
return;
}
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: updatedQuery}));
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: updatedQuery}));
if (updatedQuery !== originalInputQuery) {
clearAllFilters();
@@ -210,26 +211,64 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
[setSelection, setTextInputValue],
);
- if (isCannedQuery) {
- const headerIcon = getHeaderContent(type).icon;
+ const searchQueryItem = textInputValue
+ ? {
+ text: textInputValue,
+ singleIcon: Expensicons.MagnifyingGlass,
+ searchQuery: textInputValue,
+ itemStyle: styles.activeComponentBG,
+ keyForList: 'findItem',
+ searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
+ }
+ : undefined;
+ if (displayNarrowHeader) {
return (
-
-
- {headerText}} />
-
- {children}
-
+
+
+
+ {
+ submitSearch(textInputValue);
+ }}
+ autoFocus={false}
+ onFocus={onFocus}
+ wrapperStyle={{...styles.searchAutocompleteInputResults, ...styles.br2}}
+ wrapperFocusedStyle={styles.searchAutocompleteInputResultsFocused}
+ rightComponent={inputRightComponent}
+ autocompleteListRef={listRef}
+ ref={textInputRef}
+ />
+
+ {showPopupButton && (
+
+
+
+ )}
+ {!!searchRouterListVisible && (
+
+
+
+ )}
);
@@ -240,18 +279,6 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
listRef.current?.updateAndScrollToFocusedIndex(0);
setIsAutocompleteListVisible(true);
};
-
- const searchQueryItem = textInputValue
- ? {
- text: textInputValue,
- singleIcon: Expensicons.MagnifyingGlass,
- searchQuery: textInputValue,
- itemStyle: styles.activeComponentBG,
- keyForList: 'findItem',
- searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
- }
- : undefined;
-
// we need `- BORDER_WIDTH` to achieve the effect that the input will not "jump"
const popoverHorizontalPosition = 12 - BORDER_WIDTH;
const autocompleteInputStyle = isAutocompleteListVisible
@@ -286,16 +313,16 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps
autoFocus={false}
onFocus={showAutocompleteList}
onBlur={hideAutocompleteList}
- wrapperStyle={[styles.searchAutocompleteInputResults, styles.br2]}
+ wrapperStyle={{...styles.searchAutocompleteInputResults, ...styles.br2}}
wrapperFocusedStyle={styles.searchAutocompleteInputResultsFocused}
outerWrapperStyle={[inputWrapperActiveStyle, styles.pb2]}
- rightComponent={children}
+ rightComponent={inputRightComponent}
autocompleteListRef={listRef}
ref={textInputRef}
selection={selection}
substitutionMap={autocompleteSubstitutions}
/>
-
+
;
+};
+
+type SearchTypeMenuNarrowProps = {
+ queryJSON: SearchQueryJSON;
+ searchName?: string;
+};
+
+function SearchTypeMenuPopover({queryJSON, searchName}: SearchTypeMenuNarrowProps) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const {singleExecution} = useSingleExecution();
+ const {windowHeight} = useWindowDimensions();
+ const {translate} = useLocalize();
+ const {hash, policyID} = queryJSON;
+ const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
+ const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const personalDetails = usePersonalDetails();
+ const [reports = {}] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
+ const taxRates = getAllTaxRates();
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
+ const {unmodifiedPaddings} = useStyledSafeAreaInsets();
+
+ const [isPopoverVisible, setIsPopoverVisible] = useState(false);
+ const buttonRef = useRef(null);
+ const openMenu = useCallback(() => {
+ setIsPopoverVisible(true);
+ }, []);
+ const closeMenu = useCallback(() => setIsPopoverVisible(false), []);
+
+ // this is a performance fix, rendering popover menu takes a lot of time and we don't need this component initially, that's why we postpone rendering it until everything else is rendered
+ const [delayPopoverMenuFirstRender, setDelayPopoverMenuFirstRender] = useState(true);
+ useEffect(() => {
+ setTimeout(() => {
+ setDelayPopoverMenuFirstRender(false);
+ }, 100);
+ }, []);
+
+ const typeMenuItems = useMemo(() => createTypeMenuItems(allPolicies, session?.email), [allPolicies, session?.email]);
+ const isCannedQuery = isCannedSearchQuery(queryJSON);
+ const title = searchName ?? (isCannedQuery ? undefined : buildUserReadableQueryString(queryJSON, personalDetails, reports, taxRates, allCards));
+ const activeItemIndex = isCannedQuery ? typeMenuItems.findIndex((item) => item.type === queryJSON.type) : -1;
+
+ const getOverflowMenu = useCallback(
+ (itemName: string, itemHash: number, itemQuery: string) => getOverflowMenuUtil(itemName, itemHash, itemQuery, showDeleteModal, true, closeMenu),
+ [closeMenu, showDeleteModal],
+ );
+ const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
+ const createSavedSearchMenuItem = useCallback(
+ (item: SaveSearchItem, key: string, index: number) => {
+ let savedSearchTitle = item.name;
+ if (savedSearchTitle === item.query) {
+ const jsonQuery = buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON);
+ savedSearchTitle = buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates, allCards);
+ }
+
+ const baseMenuItem: SavedSearchMenuItem = createBaseSavedSearchMenuItem(item, key, index, savedSearchTitle, hash);
+
+ return {
+ ...baseMenuItem,
+ onSelected: baseMenuItem.onPress,
+ rightComponent: (
+
+ ),
+ styles: [styles.textSupporting],
+ isSelected: false,
+ shouldCallAfterModalHide: true,
+ };
+ },
+ [hash, getOverflowMenu, styles.textSupporting, personalDetails, reports, taxRates, allCards],
+ );
+
+ const savedSearchItems = useMemo(() => {
+ if (!savedSearches) {
+ return [];
+ }
+ return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, index));
+ }, [createSavedSearchMenuItem, savedSearches]);
+
+ const currentSavedSearch = savedSearchItems.find((item) => Number(item.hash) === hash);
+
+ const popoverMenuItems = useMemo(() => {
+ const items = typeMenuItems.map((item, index) => {
+ const isSelected = title ? false : index === activeItemIndex;
+
+ return {
+ text: translate(item.translationPath),
+ onSelected: singleExecution(() => {
+ clearAllFilters();
+ Navigation.navigate(item.getRoute(policyID));
+ }),
+ isSelected,
+ icon: item.icon,
+ iconFill: isSelected ? theme.iconSuccessFill : theme.icon,
+ iconRight: Expensicons.Checkmark,
+ shouldShowRightIcon: isSelected,
+ success: isSelected,
+ containerStyle: isSelected ? [{backgroundColor: theme.border}] : undefined,
+ shouldCallAfterModalHide: true,
+ };
+ });
+
+ if (title && !currentSavedSearch) {
+ items.push({
+ text: title,
+ onSelected: closeMenu,
+ isSelected: !currentSavedSearch,
+ icon: Expensicons.Filters,
+ iconFill: theme.iconSuccessFill,
+ success: true,
+ containerStyle: undefined,
+ iconRight: Expensicons.Checkmark,
+ shouldShowRightIcon: false,
+ shouldCallAfterModalHide: true,
+ });
+ }
+
+ return items;
+ }, [typeMenuItems, title, currentSavedSearch, activeItemIndex, translate, singleExecution, theme.iconSuccessFill, theme.icon, theme.border, policyID, closeMenu]);
+
+ const allMenuItems = useMemo(() => {
+ const items = [];
+ items.push(...popoverMenuItems);
+
+ if (savedSearchItems.length > 0) {
+ items.push({
+ text: translate('search.savedSearchesMenuItemTitle'),
+ styles: [styles.textSupporting],
+ disabled: true,
+ });
+ items.push(...savedSearchItems);
+ }
+ return items;
+ }, [popoverMenuItems, savedSearchItems, styles.textSupporting, translate]);
+
+ return (
+ <>
+
+ {!delayPopoverMenuFirstRender && (
+
+ )}
+
+ >
+ );
+}
+
+SearchTypeMenuPopover.displayName = 'SearchTypeMenuPopover';
+
+export default SearchTypeMenuPopover;
diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx
index 5e201a921e0d..50e937e65125 100644
--- a/src/components/Search/SearchRouter/SearchRouter.tsx
+++ b/src/components/Search/SearchRouter/SearchRouter.tsx
@@ -214,7 +214,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps)
}
onRouterClose();
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: updatedQuery}));
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: updatedQuery}));
setTextInputValue('');
setAutocompleteQueryValue('');
@@ -327,9 +327,9 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps)
caretHidden={shouldHideInputCaret}
autocompleteListRef={listRef}
shouldShowOfflineMessage
- wrapperStyle={[styles.border, styles.alignItemsCenter]}
+ wrapperStyle={{...styles.border, ...styles.alignItemsCenter}}
outerWrapperStyle={[shouldUseNarrowLayout ? styles.mv3 : styles.mv2, shouldUseNarrowLayout ? styles.mh5 : styles.mh2]}
- wrapperFocusedStyle={[styles.borderColorFocus]}
+ wrapperFocusedStyle={styles.borderColorFocus}
isSearchingForReports={isSearchingForReports}
selection={selection}
substitutionMap={autocompleteSubstitutions}
diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx
index af537a981cee..7191369e602a 100644
--- a/src/components/TextInput/BaseTextInput/index.native.tsx
+++ b/src/components/TextInput/BaseTextInput/index.native.tsx
@@ -72,6 +72,7 @@ function BaseTextInput(
contentWidth,
loadingSpinnerStyle,
uncontrolled,
+ placeholderTextColor,
...props
}: BaseTextInputProps,
ref: ForwardedRef,
@@ -347,7 +348,7 @@ function BaseTextInput(
autoFocus={autoFocus && !shouldDelayFocus}
autoCorrect={inputProps.secureTextEntry ? false : autoCorrect}
placeholder={placeholderValue}
- placeholderTextColor={theme.placeholderText}
+ placeholderTextColor={placeholderTextColor ?? theme.placeholderText}
underlineColorAndroid="transparent"
style={[
styles.flex1,
diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx
index b04ea7b89b5b..855825a7c5d9 100644
--- a/src/components/TextInput/BaseTextInput/index.tsx
+++ b/src/components/TextInput/BaseTextInput/index.tsx
@@ -76,6 +76,7 @@ function BaseTextInput(
contentWidth,
loadingSpinnerStyle,
uncontrolled = false,
+ placeholderTextColor,
...inputProps
}: BaseTextInputProps,
ref: ForwardedRef,
@@ -352,7 +353,7 @@ function BaseTextInput(
{...inputProps}
autoCorrect={inputProps.secureTextEntry ? false : autoCorrect}
placeholder={newPlaceholder}
- placeholderTextColor={theme.placeholderText}
+ placeholderTextColor={placeholderTextColor ?? theme.placeholderText}
underlineColorAndroid="transparent"
style={[
styles.flex1,
diff --git a/src/hooks/useDeleteSavedSearch.tsx b/src/hooks/useDeleteSavedSearch.tsx
index 19e5def4601d..8e0606d3f674 100644
--- a/src/hooks/useDeleteSavedSearch.tsx
+++ b/src/hooks/useDeleteSavedSearch.tsx
@@ -21,7 +21,7 @@ export default function useDeleteSavedSearch() {
setIsDeleteModalVisible(false);
SearchActions.clearAdvancedFilters();
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: SearchQueryUtils.buildCannedSearchQuery(),
}),
);
diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts
index a626606bf3e0..056396d82bdd 100644
--- a/src/hooks/useOnboardingFlow.ts
+++ b/src/hooks/useOnboardingFlow.ts
@@ -48,7 +48,7 @@ function useOnboardingFlowRouter() {
if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.migratedUserWelcomeModal) {
const defaultCannedQuery = SearchQueryUtils.buildCannedSearchQuery();
const query = defaultCannedQuery;
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query}));
Navigation.navigate(ROUTES.MIGRATED_USER_WELCOME_MODAL);
return;
}
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 544f06748f4d..4f9ab8ae13ff 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -30,7 +30,7 @@ const config: LinkingOptions['config'] = {
[SCREENS.TRANSACTION_RECEIPT]: ROUTES.TRANSACTION_RECEIPT.route,
[SCREENS.WORKSPACE_JOIN_USER]: ROUTES.WORKSPACE_JOIN_USER.route,
[SCREENS.SEARCH.ROOT]: {
- path: ROUTES.SEARCH_CENTRAL_PANE.route,
+ path: ROUTES.SEARCH_ROOT.route,
},
[SCREENS.NOT_FOUND]: '*',
diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts
index d110929ff8bf..aec1dcfec96c 100644
--- a/src/libs/SearchQueryUtils.ts
+++ b/src/libs/SearchQueryUtils.ts
@@ -657,6 +657,10 @@ function isCannedSearchQuery(queryJSON: SearchQueryJSON) {
return !queryJSON.filters;
}
+function isDefaultExpensesQuery(queryJSON: SearchQueryJSON) {
+ return queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE && queryJSON.status === CONST.SEARCH.STATUS.EXPENSE.ALL && !queryJSON.filters;
+}
+
/**
* Given a search query, this function will standardize the query by replacing display values with their corresponding IDs.
*/
@@ -736,4 +740,5 @@ export {
sanitizeSearchValue,
getQueryWithUpdatedValues,
getUserFriendlyKey,
+ isDefaultExpensesQuery,
};
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 3772320b25a8..0b4718bbbdd3 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1,5 +1,7 @@
+import type {TextStyle, ViewStyle} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
+import type {MenuItemWithLink} from '@components/MenuItemList';
import type {SearchColumnType, SearchStatus, SortOrder} from '@components/Search/types';
import ChatListItem from '@components/SelectionList/ChatListItem';
import ReportListItem from '@components/SelectionList/Search/ReportListItem';
@@ -10,7 +12,9 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {Route} from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
+import type {SaveSearchItem} from '@src/types/onyx/SaveSearch';
import type SearchResults from '@src/types/onyx/SearchResults';
import type {
ListItemDataType,
@@ -22,14 +26,18 @@ import type {
SearchTransaction,
SearchTransactionAction,
} from '@src/types/onyx/SearchResults';
+import type IconAsset from '@src/types/utils/IconAsset';
import {canApproveIOU, canIOUBePaid, canSubmitReport} from './actions/IOU';
+import {clearAllFilters} from './actions/Search';
import {convertToDisplayString} from './CurrencyUtils';
import DateUtils from './DateUtils';
import {translateLocal} from './Localize';
import Navigation from './Navigation/Navigation';
+import {canSendInvoice} from './PolicyUtils';
import {isAddCommentAction, isDeletedAction} from './ReportActionsUtils';
import {
getSearchReportName,
+ hasInvoiceReports,
hasOnlyHeldExpenses,
hasViolations,
isAllowedToApproveExpenseReport as isAllowedToApproveExpenseReportUtils,
@@ -38,6 +46,7 @@ import {
isMoneyRequestReport,
isSettled,
} from './ReportUtils';
+import {buildCannedSearchQuery} from './SearchQueryUtils';
import {getAmount as getTransactionAmount, getCreated as getTransactionCreatedDate, getMerchant as getTransactionMerchant, isExpensifyCardTransaction, isPending} from './TransactionUtils';
const columnNamesToSortingProperty = {
@@ -71,6 +80,20 @@ type ReportActionKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`;
type PolicyKey = `${typeof ONYXKEYS.COLLECTION.POLICY}${string}`;
type ViolationKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${string}`;
+type SavedSearchMenuItem = MenuItemWithLink & {
+ key: string;
+ hash: string;
+ query: string;
+ styles?: Array;
+};
+
+type SearchTypeMenuItem = {
+ translationPath: TranslationPaths;
+ type: SearchDataTypes;
+ icon: IconAsset;
+ getRoute: (policyID?: string) => Route;
+};
+
/**
* @private
*
@@ -659,6 +682,71 @@ function isCorrectSearchUserName(displayName?: string) {
return displayName && displayName.toUpperCase() !== CONST.REPORT.OWNER_EMAIL_FAKE;
}
+function createTypeMenuItems(allPolicies: OnyxCollection | null, email: string | undefined): SearchTypeMenuItem[] {
+ const typeMenuItems: SearchTypeMenuItem[] = [
+ {
+ translationPath: 'common.expenses',
+ type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+ icon: Expensicons.Receipt,
+ getRoute: (policyID?: string) => {
+ const query = buildCannedSearchQuery({policyID});
+ return ROUTES.SEARCH_ROOT.getRoute({query});
+ },
+ },
+ {
+ translationPath: 'common.chats',
+ type: CONST.SEARCH.DATA_TYPES.CHAT,
+ icon: Expensicons.ChatBubbles,
+ getRoute: (policyID?: string) => {
+ const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT, status: CONST.SEARCH.STATUS.CHAT.ALL, policyID});
+ return ROUTES.SEARCH_ROOT.getRoute({query});
+ },
+ },
+ ];
+
+ if (canSendInvoice(allPolicies, email) || hasInvoiceReports()) {
+ typeMenuItems.push({
+ translationPath: 'workspace.common.invoices',
+ type: CONST.SEARCH.DATA_TYPES.INVOICE,
+ icon: Expensicons.InvoiceGeneric,
+ getRoute: (policyID?: string) => {
+ const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.INVOICE, status: CONST.SEARCH.STATUS.INVOICE.ALL, policyID});
+ return ROUTES.SEARCH_ROOT.getRoute({query});
+ },
+ });
+ }
+
+ typeMenuItems.push({
+ translationPath: 'travel.trips',
+ type: CONST.SEARCH.DATA_TYPES.TRIP,
+ icon: Expensicons.Suitcase,
+ getRoute: (policyID?: string) => {
+ const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.TRIP, status: CONST.SEARCH.STATUS.TRIP.ALL, policyID});
+ return ROUTES.SEARCH_ROOT.getRoute({query});
+ },
+ });
+
+ return typeMenuItems;
+}
+
+function createBaseSavedSearchMenuItem(item: SaveSearchItem, key: string, index: number, title: string, hash: number): SavedSearchMenuItem {
+ return {
+ key,
+ title,
+ hash: key,
+ query: item.query,
+ shouldShowRightComponent: true,
+ focused: Number(key) === hash,
+ onPress: () => {
+ clearAllFilters();
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: item?.query ?? '', name: item?.name}));
+ },
+ pendingAction: item.pendingAction,
+ disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+ shouldIconUseAutoWidthStyle: true,
+ };
+}
+
export {
getListItem,
getSections,
@@ -674,4 +762,7 @@ export {
isCorrectSearchUserName,
isReportActionEntry,
getAction,
+ createTypeMenuItems,
+ createBaseSavedSearchMenuItem,
};
+export type {SavedSearchMenuItem, SearchTypeMenuItem};
diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx
index 54ab7b812cd9..d5644f63f8cd 100644
--- a/src/pages/Search/AdvancedSearchFilters.tsx
+++ b/src/pages/Search/AdvancedSearchFilters.tsx
@@ -374,9 +374,9 @@ function AdvancedSearchFilters() {
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
const [searchAdvancedFilters = {} as SearchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const policyID = searchAdvancedFilters.policyID;
- const [userCardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
- const [workspaceCardFeeds = {}] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
- const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList, true), [userCardList, workspaceCardFeeds]);
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList, true), [userCardList, workspaceCardFeeds]);
const taxRates = getAllTaxRates();
const personalDetails = usePersonalDetails();
@@ -427,7 +427,7 @@ function AdvancedSearchFilters() {
const applyFiltersAndNavigate = () => {
clearAllFilters();
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: queryString,
}),
{forceReplace: true},
diff --git a/src/pages/Search/SavedSearchRenamePage.tsx b/src/pages/Search/SavedSearchRenamePage.tsx
index 2475c9ea602b..efa28e962053 100644
--- a/src/pages/Search/SavedSearchRenamePage.tsx
+++ b/src/pages/Search/SavedSearchRenamePage.tsx
@@ -27,7 +27,7 @@ function SavedSearchRenamePage({route}: {route: {params: {q: string; name: strin
SearchActions.clearAdvancedFilters();
Navigation.dismissModal();
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: q,
name: newName?.trim(),
}),
diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx
index 03a7f579a5a0..a372898bce8f 100644
--- a/src/pages/Search/SearchPage.tsx
+++ b/src/pages/Search/SearchPage.tsx
@@ -9,8 +9,8 @@ import TopBar from '@components/Navigation/TopBar';
import ScreenWrapper from '@components/ScreenWrapper';
import Search from '@components/Search';
import {useSearchContext} from '@components/Search/SearchContext';
-import SearchPageHeader from '@components/Search/SearchPageHeader';
-import SearchStatusBar from '@components/Search/SearchStatusBar';
+import SearchPageHeader from '@components/Search/SearchPageHeader/SearchPageHeader';
+import SearchStatusBar from '@components/Search/SearchPageHeader/SearchStatusBar';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -41,7 +41,7 @@ function SearchPage({route}: SearchPageProps) {
return {queryJSON: parsedQuery, policyID: extractedPolicyID};
}, [q]);
- const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: buildCannedSearchQuery()}));
+ const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()}));
const {clearSelectedTransactions} = useSearchContext();
const isSearchNameModified = name === q;
@@ -78,10 +78,7 @@ function SearchPage({route}: SearchPageProps) {
breadcrumbLabel={translate('common.search')}
shouldDisplaySearch={false}
/>
-
+
) : (
{
if (!selectionMode?.isEnabled) {
@@ -56,7 +57,7 @@ function SearchPageNarrow({queryJSON, policyID, searchName}: SearchPageBottomTab
useHandleBackButton(handleBackButtonPress);
const scrollOffset = useSharedValue(0);
- const topBarOffset = useSharedValue(StyleUtils.searchHeaderHeight);
+ const topBarOffset = useSharedValue(StyleUtils.searchHeaderDefaultOffset);
const topBarAnimatedStyle = useAnimatedStyle(() => ({
top: topBarOffset.get(),
}));
@@ -71,9 +72,9 @@ function SearchPageNarrow({queryJSON, policyID, searchName}: SearchPageBottomTab
const isScrollingDown = currentOffset > scrollOffset.get();
const distanceScrolled = currentOffset - scrollOffset.get();
if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) {
- topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderHeight));
+ topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderDefaultOffset));
} else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) {
- topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}));
+ topBarOffset.set(withTiming(StyleUtils.searchHeaderDefaultOffset, {duration: ANIMATION_DURATION_IN_MS}));
}
scrollOffset.set(currentOffset);
},
@@ -84,12 +85,21 @@ function SearchPageNarrow({queryJSON, policyID, searchName}: SearchPageBottomTab
if (windowHeight <= h) {
return;
}
- topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}));
+ topBarOffset.set(withTiming(StyleUtils.searchHeaderDefaultOffset, {duration: ANIMATION_DURATION_IN_MS}));
},
- [windowHeight, topBarOffset, StyleUtils.searchHeaderHeight],
+ [windowHeight, topBarOffset, StyleUtils.searchHeaderDefaultOffset],
);
- const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: buildCannedSearchQuery()}));
+ const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()}));
+
+ const shouldDisplayCancelSearch = shouldUseNarrowLayout && ((!!queryJSON && !isCannedSearchQuery(queryJSON)) || searchRouterListVisible);
+ const cancelSearchCallback = useCallback(() => {
+ if (searchRouterListVisible) {
+ setSearchRouterListVisible(false);
+ return;
+ }
+ Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()}));
+ }, [searchRouterListVisible]);
if (!queryJSON) {
return (
@@ -107,50 +117,72 @@ function SearchPageNarrow({queryJSON, policyID, searchName}: SearchPageBottomTab
);
}
- const shouldDisplayCancelSearch = shouldUseNarrowLayout && !isCannedSearchQuery(queryJSON);
-
return (
}
headerGapStyles={styles.searchHeaderGap}
>
-
- {!selectionMode?.isEnabled ? (
- <>
-
-
-
-
-
-
+
+
+
+
+
+ {
- topBarOffset.set(withTiming(StyleUtils.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}));
+ searchRouterListVisible={searchRouterListVisible}
+ onSearchRouterFocus={() => {
+ topBarOffset.set(StyleUtils.searchHeaderDefaultOffset);
+ setSearchRouterListVisible(true);
}}
/>
+ {!searchRouterListVisible && (
+ {
+ topBarOffset.set(withTiming(StyleUtils.searchHeaderDefaultOffset, {duration: ANIMATION_DURATION_IN_MS}));
+ }}
+ />
+ )}
- >
- ) : (
-
- )}
-
-
+
+
+ ) : (
+ <>
+ {
+ clearSelectedTransactions();
+ turnOffMobileSelectionMode();
+ }}
+ />
+
+ >
+ )}
+ {!searchRouterListVisible && (
+
+
+
+ )}
);
}
diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx
index 1f618c76b968..7d09dea28569 100644
--- a/src/pages/Search/SearchSelectedNarrow.tsx
+++ b/src/pages/Search/SearchSelectedNarrow.tsx
@@ -4,7 +4,7 @@ import Button from '@components/Button';
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import MenuItem from '@components/MenuItem';
import Modal from '@components/Modal';
-import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader';
+import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/SearchPageHeader';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Expensicons from '@src/components/Icon/Expensicons';
diff --git a/src/pages/Search/SearchSelectionModeHeader.tsx b/src/pages/Search/SearchSelectionModeHeader.tsx
deleted file mode 100644
index 19ecf6113a65..000000000000
--- a/src/pages/Search/SearchSelectionModeHeader.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import {useSearchContext} from '@components/Search/SearchContext';
-import SearchPageHeader from '@components/Search/SearchPageHeader';
-import type {SearchQueryJSON} from '@components/Search/types';
-import useLocalize from '@hooks/useLocalize';
-import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
-
-type SearchSelectedModeHeaderProps = {
- queryJSON: SearchQueryJSON | undefined;
-};
-
-function SearchSelectionModeHeader({queryJSON}: SearchSelectedModeHeaderProps) {
- const {translate} = useLocalize();
- const {clearSelectedTransactions} = useSearchContext();
-
- return (
- <>
- {
- clearSelectedTransactions();
- turnOffMobileSelectionMode();
- }}
- />
-
- {!!queryJSON && }
- >
- );
-}
-
-export default SearchSelectionModeHeader;
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 7c68c9db5979..95c3bd47b408 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -16,44 +16,29 @@ import Text from '@components/Text';
import useDeleteSavedSearch from '@hooks/useDeleteSavedSearch';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import {clearAllFilters} from '@libs/actions/Search';
import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
import Navigation from '@libs/Navigation/Navigation';
-import {canSendInvoice, getAllTaxRates} from '@libs/PolicyUtils';
-import {hasInvoiceReports} from '@libs/ReportUtils';
-import {buildCannedSearchQuery, buildSearchQueryJSON, buildUserReadableQueryString, isCannedSearchQuery} from '@libs/SearchQueryUtils';
-import {getOverflowMenu as getOverflowMenuUtil} from '@libs/SearchUIUtils';
+import {getAllTaxRates} from '@libs/PolicyUtils';
+import {buildSearchQueryJSON, buildUserReadableQueryString, isCannedSearchQuery} from '@libs/SearchQueryUtils';
+import {createBaseSavedSearchMenuItem, createTypeMenuItems, getOverflowMenu as getOverflowMenuUtil} from '@libs/SearchUIUtils';
+import type {SavedSearchMenuItem, SearchTypeMenuItem} from '@libs/SearchUIUtils';
import variables from '@styles/variables';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Route} from '@src/ROUTES';
-import ROUTES from '@src/ROUTES';
import type {SaveSearchItem} from '@src/types/onyx/SaveSearch';
-import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
-import type IconAsset from '@src/types/utils/IconAsset';
import SavedSearchItemThreeDotMenu from './SavedSearchItemThreeDotMenu';
-import SearchTypeMenuNarrow from './SearchTypeMenuNarrow';
type SearchTypeMenuProps = {
queryJSON: SearchQueryJSON;
- searchName?: string;
};
-type SearchTypeMenuItem = {
- title: string;
- type: SearchDataTypes;
- icon: IconAsset;
- getRoute: (policyID?: string) => Route;
-};
-
-function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
+function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
const {type, hash} = queryJSON;
const styles = useThemeStyles();
- const {shouldUseNarrowLayout} = useResponsiveLayout();
const {singleExecution} = useSingleExecution();
const {translate} = useLocalize();
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
@@ -68,100 +53,59 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
- const [userCardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
- const [workspaceCardFeeds = {}] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
- const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList), [userCardList, workspaceCardFeeds]);
+ const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
+ const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
+ const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
const taxRates = getAllTaxRates();
- const typeMenuItems: SearchTypeMenuItem[] = [
- {
- title: translate('common.expenses'),
- type: CONST.SEARCH.DATA_TYPES.EXPENSE,
- icon: Expensicons.Receipt,
- getRoute: (policyID?: string) => {
- const query = buildCannedSearchQuery({policyID});
- return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query});
- },
- },
- {
- title: translate('common.chats'),
- type: CONST.SEARCH.DATA_TYPES.CHAT,
- icon: Expensicons.ChatBubbles,
- getRoute: (policyID?: string) => {
- const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT, status: CONST.SEARCH.STATUS.CHAT.ALL, policyID});
- return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query});
- },
- },
- ];
-
- if (canSendInvoice(allPolicies, session?.email) || hasInvoiceReports()) {
- typeMenuItems.push({
- title: translate('workspace.common.invoices'),
- type: CONST.SEARCH.DATA_TYPES.INVOICE,
- icon: Expensicons.InvoiceGeneric,
- getRoute: (policyID?: string) => {
- const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.INVOICE, status: CONST.SEARCH.STATUS.INVOICE.ALL, policyID});
- return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query});
- },
- });
- }
- typeMenuItems.push({
- title: translate('travel.trips'),
- type: CONST.SEARCH.DATA_TYPES.TRIP,
- icon: Expensicons.Suitcase,
- getRoute: (policyID?: string) => {
- const query = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.TRIP, status: CONST.SEARCH.STATUS.TRIP.ALL, policyID});
- return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query});
- },
- });
+ const typeMenuItems: SearchTypeMenuItem[] = useMemo(() => createTypeMenuItems(allPolicies, session?.email), [allPolicies, session?.email]);
const getOverflowMenu = useCallback((itemName: string, itemHash: number, itemQuery: string) => getOverflowMenuUtil(itemName, itemHash, itemQuery, showDeleteModal), [showDeleteModal]);
-
const createSavedSearchMenuItem = useCallback(
- (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => {
+ (item: SaveSearchItem, key: string, index: number) => {
let title = item.name;
if (title === item.query) {
const jsonQuery = buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON);
title = buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates, allCards);
}
+ const baseMenuItem: SavedSearchMenuItem = createBaseSavedSearchMenuItem(item, key, index, title, hash);
return {
- key,
- title,
- hash: key,
- query: item.query,
- shouldShowRightComponent: true,
- focused: Number(key) === hash,
- onPress: () => {
- clearAllFilters();
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name}));
- },
+ ...baseMenuItem,
rightComponent: (
),
- styles: [styles.alignItemsCenter],
- pendingAction: item.pendingAction,
- disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- shouldIconUseAutoWidthStyle: true,
+ style: [styles.alignItemsCenter],
+ tooltipAnchorAlignment: {
+ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
+ vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
+ },
+ tooltipShiftHorizontal: variables.savedSearchShiftHorizontal,
+ tooltipShiftVertical: variables.savedSearchShiftVertical,
+ tooltipWrapperStyle: [styles.mh4, styles.pv2, styles.productTrainingTooltipWrapper],
+ renderTooltipContent: renderProductTrainingTooltip,
};
},
[
- allCards,
hash,
getOverflowMenu,
+ shouldShowProductTrainingTooltip,
+ hideProductTrainingTooltip,
styles.alignItemsCenter,
+ styles.mh4,
+ styles.pv2,
+ styles.productTrainingTooltipWrapper,
+ renderProductTrainingTooltip,
personalDetails,
reports,
taxRates,
- shouldShowProductTrainingTooltip,
- hideProductTrainingTooltip,
- renderProductTrainingTooltip,
+ allCards,
],
);
@@ -192,8 +136,8 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
if (!savedSearches) {
return [];
}
- return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index));
- }, [createSavedSearchMenuItem, savedSearches, shouldUseNarrowLayout]);
+ return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, index));
+ }, [createSavedSearchMenuItem, savedSearches]);
const renderSavedSearchesSection = useCallback(
(menuItems: MenuItemWithLink[]) => (
@@ -214,20 +158,6 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
const isCannedQuery = isCannedSearchQuery(queryJSON);
const activeItemIndex = isCannedQuery ? typeMenuItems.findIndex((item) => item.type === type) : -1;
- if (shouldUseNarrowLayout) {
- const title = searchName ?? (isCannedQuery ? undefined : buildUserReadableQueryString(queryJSON, personalDetails, reports, taxRates, allCards));
-
- return (
-
- );
- }
-
return (
;
-};
-
-type SearchTypeMenuNarrowProps = {
- typeMenuItems: SearchTypeMenuItem[];
- activeItemIndex: number;
- queryJSON: SearchQueryJSON;
- title?: string;
- savedSearchesMenuItems: SavedSearchMenuItem[];
-};
-
-function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, savedSearchesMenuItems}: SearchTypeMenuNarrowProps) {
- const theme = useTheme();
- const styles = useThemeStyles();
- const StyleUtils = useStyleUtils();
- const {singleExecution} = useSingleExecution();
- const {windowHeight} = useWindowDimensions();
- const {translate} = useLocalize();
- const {hash, policyID} = queryJSON;
- const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
- const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST);
- const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
- const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
- const personalDetails = usePersonalDetails();
- const [reports = {}] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
- const taxRates = getAllTaxRates();
- const [userCardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
- const [workspaceCardFeeds = {}] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
- const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds, userCardList), [userCardList, workspaceCardFeeds]);
- const {unmodifiedPaddings} = useStyledSafeAreaInsets();
-
- const [isPopoverVisible, setIsPopoverVisible] = useState(false);
- const buttonRef = useRef(null);
-
- const platform = getPlatform();
- const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP;
-
- const openMenu = useCallback(() => setIsPopoverVisible(true), []);
- const closeMenu = useCallback(() => setIsPopoverVisible(false), []);
-
- const [isScreenFocused, setIsScreenFocused] = useState(false);
-
- useEffect(() => {
- const listener = (event: EventArg<'state', false, NavigationContainerEventMap['state']['data']>) => {
- if (Navigation.getRouteNameFromStateEvent(event) === SCREENS.SEARCH.ROOT) {
- setIsScreenFocused(true);
- return;
- }
- setIsScreenFocused(false);
- };
- navigationRef.addListener('state', listener);
- return () => navigationRef.removeListener('state', listener);
- }, []);
-
- const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
- CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP,
- isScreenFocused,
- );
-
- const onPress = () => {
- hideProductTrainingTooltip();
- const values = buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, reports, taxRates);
- updateAdvancedFilters(values);
- Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
- };
-
- const currentSavedSearch = savedSearchesMenuItems.find((item) => Number(item.hash) === hash);
-
- const popoverMenuItems = useMemo(() => {
- const items = typeMenuItems.map((item, index) => {
- const isSelected = title ? false : index === activeItemIndex;
-
- return {
- text: item.title,
- onSelected: singleExecution(() => {
- clearAllFilters();
- Navigation.navigate(item.getRoute(policyID));
- }),
- isSelected,
- icon: item.icon,
- iconFill: isSelected ? theme.iconSuccessFill : theme.icon,
- iconRight: Expensicons.Checkmark,
- shouldShowRightIcon: isSelected,
- success: isSelected,
- containerStyle: isSelected ? [{backgroundColor: theme.border}] : undefined,
- shouldCallAfterModalHide: true,
- };
- });
-
- if (title && !currentSavedSearch) {
- items.push({
- text: title,
- onSelected: closeMenu,
- isSelected: !currentSavedSearch,
- icon: Expensicons.Filters,
- iconFill: theme.iconSuccessFill,
- success: true,
- containerStyle: undefined,
- iconRight: Expensicons.Checkmark,
- shouldShowRightIcon: false,
- shouldCallAfterModalHide: true,
- });
- }
-
- return items;
- }, [typeMenuItems, title, activeItemIndex, singleExecution, theme, policyID, closeMenu, currentSavedSearch]);
-
- const {menuIcon, menuTitle} = useMemo(() => {
- if (title) {
- return {
- menuIcon: Expensicons.Filters,
- menuTitle: title,
- };
- }
-
- const item = activeItemIndex !== -1 ? popoverMenuItems.at(activeItemIndex) : undefined;
- return {
- menuIcon: item?.icon ?? Expensicons.Receipt,
- menuTitle: item?.text,
- };
- }, [activeItemIndex, popoverMenuItems, title]);
-
- const titleViewStyles = useMemo(() => (title ? {...styles.flex1, ...styles.justifyContentCenter} : {}), [title, styles]);
-
- const savedSearchItems = savedSearchesMenuItems.map((item) => ({
- text: item.title ?? '',
- styles: [styles.textSupporting],
- onSelected: item.onPress,
- icon: Expensicons.Bookmark,
- iconFill: currentSavedSearch?.hash === item.hash ? theme.iconSuccessFill : theme.icon,
- shouldShowRightComponent: true,
- rightComponent: (
-
- ),
- isSelected: currentSavedSearch?.hash === item.hash,
- shouldCallAfterModalHide: true,
- pendingAction: item.pendingAction,
- disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- }));
- const allMenuItems = [];
- allMenuItems.push(...popoverMenuItems);
-
- if (savedSearchesMenuItems.length > 0) {
- allMenuItems.push({
- text: translate('search.savedSearchesMenuItemTitle'),
- styles: [styles.textSupporting],
- disabled: true,
- });
- allMenuItems.push(...savedSearchItems);
- }
- return (
-
-
- {({hovered}) => (
-
-
-
-
- {menuTitle}
-
-
-
-
- )}
-
-
-
-
-
-
-
- );
-}
-
-SearchTypeMenuNarrow.displayName = 'SearchTypeMenuNarrow';
-
-export default SearchTypeMenuNarrow;
diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx
index 30a66ee22ae4..74e7301bfa29 100644
--- a/src/pages/settings/Subscription/CardSection/CardSection.tsx
+++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx
@@ -63,7 +63,7 @@ function CardSection() {
const viewPurchases = useCallback(() => {
const query = buildQueryStringFromFilterFormValues({merchant: CONST.EXPENSIFY_MERCHANT});
- Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
+ Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query}));
}, []);
const [billingStatus, setBillingStatus] = useState(() => CardSectionUtils.getBillingStatus(translate, defaultCard?.accountData ?? {}));
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx
index 710e3e37b493..c85afb36fe75 100644
--- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx
+++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx
@@ -279,7 +279,7 @@ function ExpensifyCardPage({
style={styles.mt3}
onPress={() => {
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
}),
);
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
index 2811e5187afa..56dc8349777a 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
@@ -677,7 +677,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
title={translate('workspace.common.viewTransactions')}
onPress={() => {
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: buildCannedSearchQuery({
type: CONST.SEARCH.DATA_TYPES.EXPENSE,
status: CONST.SEARCH.STATUS.EXPENSE.ALL,
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
index 4c012932546d..20eac995a72b 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx
@@ -175,7 +175,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
style={styles.mt3}
onPress={() => {
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
}),
);
diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx
index 0f988d1ca811..cc948346f913 100644
--- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx
+++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx
@@ -183,7 +183,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail
style={styles.mt3}
onPress={() => {
Navigation.navigate(
- ROUTES.SEARCH_CENTRAL_PANE.getRoute({
+ ROUTES.SEARCH_ROOT.getRoute({
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
}),
);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 589d04267712..6c2214cfaa60 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -1100,7 +1100,7 @@ const styles = (theme: ThemeColors) =>
},
searchHeaderGap: {
- zIndex: variables.searchTopBarZIndex + 1,
+ zIndex: variables.searchTopBarZIndex + 2,
backgroundColor: theme.appBG,
},
@@ -3739,7 +3739,12 @@ const styles = (theme: ThemeColors) =>
paddingTop: variables.searchListContentMarginTop,
},
- searchTopBarStyle: {
+ narrowSearchHeaderStyle: {
+ paddingTop: 1,
+ flex: 1,
+ },
+
+ narrowSearchRouterInactiveStyle: {
left: 0,
right: 0,
position: 'absolute',
@@ -4410,10 +4415,6 @@ const styles = (theme: ThemeColors) =>
backgroundColor: hovered && !isFocused ? theme.highlightBG : background,
}),
- tabBackground: (hovered: boolean, isFocused: boolean, background: string) => ({
- backgroundColor: hovered && !isFocused ? theme.highlightBG : background,
- }),
-
tabOpacity: (
hovered: boolean,
isFocused: boolean,
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 64737d7b6bef..97cbc7b4a8e3 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -29,7 +29,8 @@ import getSafeAreaInsets from './getSafeAreaInsets';
import getSignInBgStyles from './getSignInBgStyles';
import {compactContentContainerStyles} from './optionRowStyles';
import positioning from './positioning';
-import searchHeaderHeight from './searchHeaderHeight';
+import getSearchBottomTabHeaderStyles from './searchBottomTabHeaderStyles.ts';
+import searchHeaderDefaultOffset from './searchHeaderDefaultOffset';
import type {
AllStyles,
AvatarSize,
@@ -1175,7 +1176,7 @@ function getItemBackgroundColorStyle(isSelected: boolean, isFocused: boolean, is
const staticStyleUtils = {
positioning,
- searchHeaderHeight,
+ searchHeaderDefaultOffset,
combineStyles,
displayIfTrue,
getAmountFontSizeAndLineHeight,
@@ -1239,6 +1240,7 @@ const staticStyleUtils = {
getFileExtensionColorCode,
getNavigationModalCardStyle,
getCardStyles,
+ getSearchBottomTabHeaderStyles,
getOpacityStyle,
getMultiGestureCanvasContainerStyle,
getSignInBgStyles,
diff --git a/src/styles/utils/searchBottomTabHeaderStyles.ts/index.android.ts b/src/styles/utils/searchBottomTabHeaderStyles.ts/index.android.ts
new file mode 100644
index 000000000000..1cd90feb2c67
--- /dev/null
+++ b/src/styles/utils/searchBottomTabHeaderStyles.ts/index.android.ts
@@ -0,0 +1,4 @@
+// on Android, setting zIndex breaks scroll in SearchStatusBar, but it is needed on other platforms for TabBar to overlay rest of the header
+const getSearchBottomTabHeaderStyles = () => ({});
+
+export default getSearchBottomTabHeaderStyles;
diff --git a/src/styles/utils/searchBottomTabHeaderStyles.ts/index.ts b/src/styles/utils/searchBottomTabHeaderStyles.ts/index.ts
new file mode 100644
index 000000000000..f0f1a29e4b65
--- /dev/null
+++ b/src/styles/utils/searchBottomTabHeaderStyles.ts/index.ts
@@ -0,0 +1,5 @@
+const getSearchBottomTabHeaderStyles = () => ({
+ zIndex: 10,
+});
+
+export default getSearchBottomTabHeaderStyles;
diff --git a/src/styles/utils/searchHeaderDefaultOffset/index.ts b/src/styles/utils/searchHeaderDefaultOffset/index.ts
new file mode 100644
index 000000000000..47d99efcf289
--- /dev/null
+++ b/src/styles/utils/searchHeaderDefaultOffset/index.ts
@@ -0,0 +1,3 @@
+import variables from '@styles/variables';
+
+export default variables.searchHeaderDefaultOffset;
diff --git a/src/styles/utils/searchHeaderHeight/index.desktop.ts b/src/styles/utils/searchHeaderHeight/index.desktop.ts
deleted file mode 100644
index 0eb7963d7ad5..000000000000
--- a/src/styles/utils/searchHeaderHeight/index.desktop.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import variables from '@styles/variables';
-import CONST from '@src/CONST';
-
-export default variables.searchHeaderHeight + CONST.DESKTOP_HEADER_PADDING;
diff --git a/src/styles/utils/searchHeaderHeight/index.ts b/src/styles/utils/searchHeaderHeight/index.ts
deleted file mode 100644
index 19ff8287e012..000000000000
--- a/src/styles/utils/searchHeaderHeight/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import variables from '@styles/variables';
-
-export default variables.searchHeaderHeight;
diff --git a/src/styles/utils/sizing.ts b/src/styles/utils/sizing.ts
index 4c39f7a20d3a..ee7c04b57b13 100644
--- a/src/styles/utils/sizing.ts
+++ b/src/styles/utils/sizing.ts
@@ -37,6 +37,10 @@ export default {
maxHeight: '100%',
},
+ mh65vh: {
+ maxHeight: '65vh',
+ },
+
mh85vh: {
maxHeight: '85vh',
},
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 35f0e3f73907..f087a9a19373 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -263,7 +263,7 @@ export default {
gbrTooltipShiftHorizontal: -15,
fabTooltipShiftHorizontal: -11,
workspaceLHNtooltipShiftHorizontal: 23,
- searchFiltersTooltipShiftHorizontal: -25,
+ searchFiltersTooltipShiftHorizontal: -4,
quickActionTooltipShiftHorizontal: 24,
savedSearchShiftHorizontal: -10,
savedSearchShiftVertical: 6,
@@ -274,10 +274,12 @@ export default {
inlineImagePreviewMinSize: 64,
inlineImagePreviewMaxSize: 148,
- minimalTopBarOffset: -26,
- searchHeaderHeight: 80,
+ minimalTopBarOffset: -106,
+ searchHeaderDefaultOffset: 0,
searchListContentMarginTop: 116,
searchTopBarZIndex: 9,
+ searchTopBarHeight: 52,
+ searchRouterInputMargin: 52,
h20: 20,
h28: 28,