diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 4ecd440d4a51c..b489fb8bfc840 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -13,7 +13,7 @@ import {buildSubstitutionsMap} from '@components/Search/SearchRouter/buildSubsti import type {SubstitutionMap} from '@components/Search/SearchRouter/getQueryWithSubstitutions'; import {getQueryWithSubstitutions} from '@components/Search/SearchRouter/getQueryWithSubstitutions'; import {getUpdatedSubstitutionsMap} from '@components/Search/SearchRouter/getUpdatedSubstitutionsMap'; -import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; +import {useSearchRouterActions} from '@components/Search/SearchRouter/SearchRouterContext'; import type {SearchQueryJSON, SearchQueryString} from '@components/Search/types'; import type {SearchQueryItem} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; import {isSearchQueryItem} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; @@ -84,7 +84,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const textInputRef = useRef(null); const hasMountedRef = useRef(false); const isFocused = useIsFocused(); - const {registerSearchPageInput} = useSearchRouterContext(); + const {registerSearchPageInput} = useSearchRouterActions(); useEffect(() => { hasMountedRef.current = true; diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 1dad9fc115d3e..d14e57374653b 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -12,7 +12,7 @@ import {startSpan} from '@libs/telemetry/activeSpans'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; -import {useSearchRouterContext} from './SearchRouterContext'; +import {useSearchRouterActions} from './SearchRouterContext'; type SearchButtonProps = { style?: StyleProp; @@ -23,10 +23,25 @@ function SearchButton({style, shouldUseAutoHitSlop = false}: SearchButtonProps) const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const {openSearchRouter} = useSearchRouterContext(); + const {openSearchRouter} = useSearchRouterActions(); const pressableRef = useRef(null); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); + const onPress = () => { + callFunctionIfActionIsAllowed(() => { + pressableRef.current?.blur(); + + Timing.start(CONST.TIMING.OPEN_SEARCH); + Performance.markStart(CONST.TIMING.OPEN_SEARCH); + startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { + name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, + op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, + }); + + openSearchRouter(); + })(); + }; + return ( { - pressableRef?.current?.blur(); - - Timing.start(CONST.TIMING.OPEN_SEARCH); - Performance.markStart(CONST.TIMING.OPEN_SEARCH); - startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { - name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, - op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, - }); - - openSearchRouter(); - })} + onPress={onPress} > void; closeSearchRouter: () => void; toggleSearch: () => void; @@ -22,8 +25,7 @@ type HistoryState = { isSearchModalOpen?: boolean; }; -const defaultSearchContext: SearchRouterContext = { - isSearchRouterDisplayed: false, +const defaultSearchRouterActionsContext: SearchRouterActionsContextType = { openSearchRouter: () => {}, closeSearchRouter: () => {}, toggleSearch: () => {}, @@ -31,7 +33,9 @@ const defaultSearchContext: SearchRouterContext = { unregisterSearchPageInput: () => {}, }; -const Context = React.createContext(defaultSearchContext); +const SearchRouterStateContext = React.createContext({isSearchRouterDisplayed: false}); + +const SearchRouterActionsContext = React.createContext(defaultSearchRouterActionsContext); const isBrowserWithHistory = typeof window !== 'undefined' && typeof window.history !== 'undefined'; const canListenPopState = typeof window !== 'undefined' && typeof window.addEventListener === 'function'; @@ -69,87 +73,98 @@ function SearchRouterContextProvider({children}: ChildrenProps) { return () => window.removeEventListener('popstate', handlePopState); }, []); - const routerContext = useMemo(() => { - const openSearchRouter = () => { - if (isBrowserWithHistory) { - window.history.pushState({isSearchModalOpen: true} satisfies HistoryState, ''); - } - close( - () => { - setIsSearchRouterDisplayed(true); - searchRouterDisplayedRef.current = true; - }, - false, - true, - ); - }; - const closeSearchRouter = () => { - setIsSearchRouterDisplayed(false); - searchRouterDisplayedRef.current = false; - if (isBrowserWithHistory) { - const state = window.history.state as HistoryState | null; - if (state?.isSearchModalOpen) { - window.history.replaceState({isSearchModalOpen: false} satisfies HistoryState, ''); - } + const openSearchRouter = () => { + if (isBrowserWithHistory) { + window.history.pushState({isSearchModalOpen: true} satisfies HistoryState, ''); + } + close( + () => { + setIsSearchRouterDisplayed(true); + searchRouterDisplayedRef.current = true; + }, + false, + true, + ); + }; + const closeSearchRouter = () => { + setIsSearchRouterDisplayed(false); + searchRouterDisplayedRef.current = false; + if (isBrowserWithHistory) { + const state = window.history.state as HistoryState | null; + if (state?.isSearchModalOpen) { + window.history.replaceState({isSearchModalOpen: false} satisfies HistoryState, ''); } - }; - - const startSearchRouterOpenSpan = () => { - startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { - name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, - op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, - attributes: { - trigger: 'keyboard', - }, - }); - }; - - // There are callbacks that live outside of React render-loop and interact with SearchRouter - // So we need a function that is based on ref to correctly open/close it - // When user is on `/search` page we focus the Input instead of showing router - const toggleSearch = () => { - const searchFullScreenRoutes = navigationRef.getRootState()?.routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); - const lastRoute = searchFullScreenRoutes?.state?.routes?.at(-1); - const isUserOnSearchPage = isSearchTopmostFullScreenRoute() && lastRoute?.name === SCREENS.SEARCH.ROOT; - - if (isUserOnSearchPage && searchPageInputRef.current) { - if (searchPageInputRef.current.isFocused()) { - searchPageInputRef.current.blur(); - } else { - startSearchRouterOpenSpan(); - searchPageInputRef.current.focus(); - } - } else if (searchRouterDisplayedRef.current) { - closeSearchRouter(); + } + }; + + const startSearchRouterOpenSpan = () => { + startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { + name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, + op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, + attributes: { + trigger: 'keyboard', + }, + }); + }; + + // There are callbacks that live outside of React render-loop and interact with SearchRouter + // So we need a function that is based on ref to correctly open/close it + // When user is on `/search` page we focus the Input instead of showing router + const toggleSearch = () => { + const searchFullScreenRoutes = navigationRef.getRootState()?.routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); + const lastRoute = searchFullScreenRoutes?.state?.routes?.at(-1); + const isUserOnSearchPage = isSearchTopmostFullScreenRoute() && lastRoute?.name === SCREENS.SEARCH.ROOT; + + if (isUserOnSearchPage && searchPageInputRef.current) { + if (searchPageInputRef.current.isFocused()) { + searchPageInputRef.current.blur(); } else { startSearchRouterOpenSpan(); - openSearchRouter(); + searchPageInputRef.current.focus(); } - }; - - const registerSearchPageInput = (ref: AnimatedTextInputRef) => { - searchPageInputRef.current = ref; - }; - - const unregisterSearchPageInput = () => { - searchPageInputRef.current = undefined; - }; - - return { - isSearchRouterDisplayed, - openSearchRouter, - closeSearchRouter, - toggleSearch, - registerSearchPageInput, - unregisterSearchPageInput, - }; - }, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]); + } else if (searchRouterDisplayedRef.current) { + closeSearchRouter(); + } else { + startSearchRouterOpenSpan(); + openSearchRouter(); + } + }; + + const registerSearchPageInput = (ref: AnimatedTextInputRef) => { + searchPageInputRef.current = ref; + }; + + const unregisterSearchPageInput = () => { + searchPageInputRef.current = undefined; + }; + + // Because of the React Compiler we don't need to memoize it manually + // eslint-disable-next-line react/jsx-no-constructed-context-values + const actionsContextValue = { + openSearchRouter, + closeSearchRouter, + toggleSearch, + registerSearchPageInput, + unregisterSearchPageInput, + }; + + // Because of the React Compiler we don't need to memoize it manually + // eslint-disable-next-line react/jsx-no-constructed-context-values + const stateContextValue = {isSearchRouterDisplayed}; + + return ( + + {children} + + ); +} - return {children}; +function useSearchRouterState() { + return useContext(SearchRouterStateContext); } -function useSearchRouterContext() { - return useContext(Context); +function useSearchRouterActions() { + return useContext(SearchRouterActionsContext); } -export {SearchRouterContextProvider, useSearchRouterContext}; +export {SearchRouterContextProvider, useSearchRouterState, useSearchRouterActions}; diff --git a/src/components/Search/SearchRouter/SearchRouterModal.tsx b/src/components/Search/SearchRouter/SearchRouterModal.tsx index efa0ea5667f6e..d456e095e401d 100644 --- a/src/components/Search/SearchRouter/SearchRouterModal.tsx +++ b/src/components/Search/SearchRouter/SearchRouterModal.tsx @@ -8,13 +8,14 @@ import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import {isMobileIOS} from '@libs/Browser'; import CONST from '@src/CONST'; import SearchRouter from './SearchRouter'; -import {useSearchRouterContext} from './SearchRouterContext'; +import {useSearchRouterActions, useSearchRouterState} from './SearchRouterContext'; const isMobileWebIOS = isMobileIOS(); function SearchRouterModal() { const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext(); + const {isSearchRouterDisplayed} = useSearchRouterState(); + const {closeSearchRouter} = useSearchRouterActions(); const viewportOffsetTop = useViewportOffsetTop(); // On mWeb Safari, the input caret stuck for a moment while the modal is animating. So, we hide the caret until the animation is done. diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index df3c85bebbd4e..77704a77f2ca6 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -11,7 +11,7 @@ import OpenAppFailureModal from '@components/OpenAppFailureModal'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import PriorityModeController from '@components/PriorityModeController'; import {SearchContextProvider} from '@components/Search/SearchContext'; -import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; +import {useSearchRouterActions} from '@components/Search/SearchRouter/SearchRouterContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import SupportalPermissionDeniedModalProvider from '@components/SupportalPermissionDeniedModalProvider'; import {WideRHPContext} from '@components/WideRHPContextProvider'; @@ -143,7 +143,7 @@ function AuthScreens() { const {shouldUseNarrowLayout} = useResponsiveLayout(); const rootNavigatorScreenOptions = useRootNavigatorScreenOptions(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const {toggleSearch} = useSearchRouterContext(); + const {toggleSearch} = useSearchRouterActions(); const currentUrl = getCurrentUrl(); const delegatorEmail = getSearchParamFromUrl(currentUrl, 'delegatorEmail'); const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS, {canBeMissing: true});