diff --git a/src/App.tsx b/src/App.tsx
index cf0fd5528eec..35254fa29b2a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -20,6 +20,7 @@ import OnyxProvider from './components/OnyxProvider';
import PopoverContextProvider from './components/PopoverProvider';
import SafeArea from './components/SafeArea';
import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider';
+import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext';
import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider';
import ThemeProvider from './components/ThemeProvider';
import ThemeStylesProvider from './components/ThemeStylesProvider';
@@ -94,6 +95,7 @@ function App({url}: AppProps) {
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
+ SearchRouterContextProvider,
]}
>
diff --git a/src/CONST.ts b/src/CONST.ts
index 86cbd4c28fc9..7ea2af2b1522 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1054,7 +1054,6 @@ const CONST = {
BOTTOM_DOCKED: 'bottom_docked',
POPOVER: 'popover',
RIGHT_DOCKED: 'right_docked',
- ONBOARDING: 'onboarding',
},
ANCHOR_ORIGIN_VERTICAL: {
TOP: 'top',
diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx
new file mode 100644
index 000000000000..4948c90ce3d1
--- /dev/null
+++ b/src/components/Search/SearchRouter/SearchButton.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import {PressableWithoutFeedback} from '@components/Pressable';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Permissions from '@libs/Permissions';
+import {useSearchRouterContext} from './SearchRouterContext';
+
+function SearchButton() {
+ const styles = useThemeStyles();
+ const theme = useTheme();
+ const {openSearchRouter} = useSearchRouterContext();
+
+ if (!Permissions.canUseNewSearchRouter()) {
+ return;
+ }
+
+ return (
+ {
+ openSearchRouter();
+ }}
+ >
+
+
+ );
+}
+
+SearchButton.displayName = 'SearchButton';
+
+export default SearchButton;
diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx
new file mode 100644
index 000000000000..b38c55279c1e
--- /dev/null
+++ b/src/components/Search/SearchRouter/SearchRouter.tsx
@@ -0,0 +1,102 @@
+import debounce from 'lodash/debounce';
+import React, {useCallback, useState} from 'react';
+import {View} from 'react-native';
+import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal';
+import Modal from '@components/Modal';
+import type {SearchQueryJSON} from '@components/Search/types';
+import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as SearchUtils from '@libs/SearchUtils';
+import Navigation from '@navigation/Navigation';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import {useSearchRouterContext} from './SearchRouterContext';
+import SearchRouterInput from './SearchRouterInput';
+
+const SEARCH_DEBOUNCE_DELAY = 200;
+
+function SearchRouter() {
+ const styles = useThemeStyles();
+
+ const {isSmallScreenWidth} = useResponsiveLayout();
+ const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext();
+ const [currentQuery, setCurrentQuery] = useState(undefined);
+
+ const clearUserQuery = () => {
+ setCurrentQuery(undefined);
+ };
+
+ const onSearchChange = debounce((userQuery: string) => {
+ if (!userQuery) {
+ clearUserQuery();
+ return;
+ }
+
+ const queryJSON = SearchUtils.buildSearchQueryJSON(userQuery);
+
+ if (queryJSON) {
+ // eslint-disable-next-line
+ console.log('parsedQuery', queryJSON);
+
+ setCurrentQuery(queryJSON);
+ } else {
+ // Handle query parsing error
+ }
+ }, SEARCH_DEBOUNCE_DELAY);
+
+ const onSearchSubmit = useCallback(() => {
+ closeSearchRouter();
+
+ const query = SearchUtils.buildSearchQueryString(currentQuery);
+ Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
+ clearUserQuery();
+ }, [currentQuery, closeSearchRouter]);
+
+ useKeyboardShortcut(
+ CONST.KEYBOARD_SHORTCUTS.ENTER,
+ () => {
+ if (!currentQuery) {
+ return;
+ }
+
+ onSearchSubmit();
+ },
+ {
+ captureOnInputs: true,
+ shouldBubble: false,
+ },
+ );
+
+ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => {
+ closeSearchRouter();
+ clearUserQuery();
+ });
+
+ const modalType = isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.CENTERED : CONST.MODAL.MODAL_TYPE.POPOVER;
+ const isFullWidth = isSmallScreenWidth;
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+SearchRouter.displayName = 'SearchRouter';
+
+export default SearchRouter;
diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx
new file mode 100644
index 000000000000..d935fff110a4
--- /dev/null
+++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx
@@ -0,0 +1,37 @@
+import React, {useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+const defaultSearchContext = {
+ isSearchRouterDisplayed: false,
+ openSearchRouter: () => {},
+ closeSearchRouter: () => {},
+};
+
+type SearchRouterContext = typeof defaultSearchContext;
+
+const Context = React.createContext(defaultSearchContext);
+
+function SearchRouterContextProvider({children}: ChildrenProps) {
+ const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false);
+
+ const routerContext = useMemo(() => {
+ const openSearchRouter = () => setIsSearchRouterDisplayed(true);
+ const closeSearchRouter = () => setIsSearchRouterDisplayed(false);
+
+ return {
+ isSearchRouterDisplayed,
+ openSearchRouter,
+ closeSearchRouter,
+ };
+ }, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]);
+
+ return {children};
+}
+
+function useSearchRouterContext() {
+ return useContext(Context);
+}
+
+SearchRouterContextProvider.displayName = 'SearchRouterContextProvider';
+
+export {SearchRouterContextProvider, useSearchRouterContext};
diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx
new file mode 100644
index 000000000000..860a46239d21
--- /dev/null
+++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx
@@ -0,0 +1,41 @@
+import React, {useState} from 'react';
+import BaseTextInput from '@components/TextInput/BaseTextInput';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+
+type SearchRouterInputProps = {
+ isFullWidth: boolean;
+ onChange: (searchTerm: string) => void;
+ onSubmit: () => void;
+};
+
+function SearchRouterInput({isFullWidth, onChange, onSubmit}: SearchRouterInputProps) {
+ const styles = useThemeStyles();
+
+ const [value, setValue] = useState('');
+
+ const onChangeText = (text: string) => {
+ setValue(text);
+ onChange(text);
+ };
+
+ const modalWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth};
+
+ return (
+
+ );
+}
+
+SearchRouterInput.displayName = 'SearchRouterInput';
+
+export default SearchRouterInput;
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index f2461f400678..8f1b27baa888 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest';
import ComposeProviders from '@components/ComposeProviders';
import OptionsListContextProvider from '@components/OptionListContextProvider';
import {SearchContextProvider} from '@components/Search/SearchContext';
+import SearchRouter from '@components/Search/SearchRouter/SearchRouter';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
@@ -558,6 +559,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
);
})}
+
);
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
index f4728a80f2ca..985c16d50c22 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
@@ -5,6 +5,7 @@ import Breadcrumbs from '@components/Breadcrumbs';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
+import SearchButton from '@components/Search/SearchRouter/SearchButton';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton';
@@ -73,6 +74,8 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true,
{translate('common.cancel')}
)}
+ {/* This is only temporary for development and will be cleaned up in: https://github.com/Expensify/App/issues/49122 */}
+
{displaySearch && (
): boolean {
return !!betas?.includes(CONST.BETAS.ALL);
@@ -49,6 +50,17 @@ function canUseCombinedTrackSubmit(betas: OnyxEntry): boolean {
return !!betas?.includes(CONST.BETAS.COMBINED_TRACK_SUBMIT);
}
+/**
+ * New Search Router is under construction and for now should be displayed only in dev to allow developers to work on it.
+ * We are not using BETA for this feature, as betas are heavier to cleanup,
+ * and the development of new router is expected to take 2-3 weeks at most
+ *
+ * After everything is implemented this function can be removed, as we will always use SearchRouter in the App.
+ */
+function canUseNewSearchRouter() {
+ return Environment.isDevelopment();
+}
+
/**
* Link previews are temporarily disabled.
*/
@@ -68,4 +80,5 @@ export default {
canUseNewDotCopilot,
canUseWorkspaceRules,
canUseCombinedTrackSubmit,
+ canUseNewSearchRouter,
};
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 9dfd18b641e8..44362ab75ded 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -3593,31 +3593,19 @@ const styles = (theme: ThemeColors) =>
flex: 1,
},
- searchPressable: {
- height: variables.componentSizeNormal,
- },
-
- searchContainer: {
- flex: 1,
- flexDirection: 'row',
- alignItems: 'center',
- gap: 8,
- paddingHorizontal: 24,
- backgroundColor: theme.hoverComponentBG,
- borderRadius: variables.componentBorderRadiusRounded,
- justifyContent: 'center',
- },
-
- searchContainerHovered: {
- backgroundColor: theme.border,
- },
-
searchInputStyle: {
color: theme.textSupporting,
fontSize: 13,
lineHeight: 16,
},
+ searchRouterInputStyle: {
+ borderRadius: variables.componentBorderRadiusSmall,
+ borderWidth: 2,
+ borderColor: theme.borderFocus,
+ paddingHorizontal: 8,
+ },
+
searchTableHeaderActive: {
fontWeight: FontUtils.fontWeight.bold,
},