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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -94,6 +95,7 @@ function App({url}: AppProps) {
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
SearchRouterContextProvider,
]}
>
<CustomStatusBarAndBackground />
Expand Down
1 change: 0 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,6 @@ const CONST = {
BOTTOM_DOCKED: 'bottom_docked',
POPOVER: 'popover',
RIGHT_DOCKED: 'right_docked',
ONBOARDING: 'onboarding',
Comment thread
Kicu marked this conversation as resolved.
Outdated
},
ANCHOR_ORIGIN_VERTICAL: {
TOP: 'top',
Expand Down
37 changes: 37 additions & 0 deletions src/components/Search/SearchRouter/SearchButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<PressableWithoutFeedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this PR missed adding a tooltip for the search button and caused this issue: #50977

accessibilityLabel=""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB we'll need to update this

style={[styles.flexRow, styles.mr2, styles.touchableButtonImage]}
onPress={() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blurring the search button causes quite a unique edge case in mweb android platform that resulted in blue frame on the search button as mentioned in #52128

openSearchRouter();
}}
>
<Icon
src={Expensicons.MagnifyingGlass}
fill={theme.icon}
/>
</PressableWithoutFeedback>
);
}

SearchButton.displayName = 'SearchButton';

export default SearchButton;
102 changes: 102 additions & 0 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
@@ -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<SearchQueryJSON | undefined>(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 (
<Modal
type={modalType}
fullscreen
isVisible={isSearchRouterDisplayed}
popoverAnchorPosition={{right: 20, top: 20}}
onClose={closeSearchRouter}
>
<FocusTrapForModal active={isSearchRouterDisplayed}>
<View style={[styles.flex1, styles.p3]}>
<SearchRouterInput
isFullWidth={isFullWidth}
onChange={onSearchChange}
onSubmit={onSearchSubmit}
/>
</View>
</FocusTrapForModal>
</Modal>
);
}

SearchRouter.displayName = 'SearchRouter';

export default SearchRouter;
37 changes: 37 additions & 0 deletions src/components/Search/SearchRouter/SearchRouterContext.tsx
Original file line number Diff line number Diff line change
@@ -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<SearchRouterContext>(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 <Context.Provider value={routerContext}>{children}</Context.Provider>;
}

function useSearchRouterContext() {
return useContext(Context);
}

SearchRouterContextProvider.displayName = 'SearchRouterContextProvider';

export {SearchRouterContextProvider, useSearchRouterContext};
41 changes: 41 additions & 0 deletions src/components/Search/SearchRouter/SearchRouterInput.tsx
Original file line number Diff line number Diff line change
@@ -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('');
Comment thread
Kicu marked this conversation as resolved.
Outdated

const onChangeText = (text: string) => {
setValue(text);
onChange(text);
};

const modalWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth};

return (
<BaseTextInput

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #54151, we add maxLength to the input here to prevent freezing when searching with a long text

value={value}
onChangeText={onChangeText}
onSubmitEditing={onSubmit}
autoFocus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from this Issue #51010, We are adding autoFocus here, but it doesn’t always work consistently on Android. To handle this, we’ve an existing prop (shouldDelayFocus) for TextInput that enables autoFocus with a delay specifically for Android #51010 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On mWeb Safari, the autoFocus does not work well while animation is still ongoing, the caret lags behind (#52382). Usually we use useAutoFocusInput hook instead. However since this input is not rendered in RHP (or LHP) but it's inside a modal (and the timing in modal vs RHP are diff) we had to go for another solution which is to just hide the caret while modal is still animating.

textInputContainerStyles={[{borderBottomWidth: 0}, modalWidth]}
inputStyle={[styles.searchInputStyle, styles.searchRouterInputStyle, styles.ph2]}
role={CONST.ROLE.PRESENTATION}
autoCapitalize="none"
/>
);
}

SearchRouterInput.displayName = 'SearchRouterInput';

export default SearchRouterInput;
2 changes: 2 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -558,6 +559,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
);
})}
</RootStack.Navigator>
<SearchRouter />
</View>
</ComposeProviders>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -73,6 +74,8 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true,
<Text style={[styles.textBlue]}>{translate('common.cancel')}</Text>
</PressableWithoutFeedback>
)}
{/* This is only temporary for development and will be cleaned up in: https://github.com/Expensify/App/issues/49122 */}
<SearchButton />
{displaySearch && (
<Tooltip text={translate('common.find')}>
<PressableWithoutFeedback
Expand Down
13 changes: 13 additions & 0 deletions src/libs/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import type {IOUType} from '@src/CONST';
import type Beta from '@src/types/onyx/Beta';
import * as Environment from './Environment/Environment';

function canUseAllBetas(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.ALL);
Expand Down Expand Up @@ -49,6 +50,17 @@ function canUseCombinedTrackSubmit(betas: OnyxEntry<Beta[]>): 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.
*/
Expand All @@ -68,4 +80,5 @@ export default {
canUseNewDotCopilot,
canUseWorkspaceRules,
canUseCombinedTrackSubmit,
canUseNewSearchRouter,
};
26 changes: 7 additions & 19 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3593,31 +3593,19 @@ const styles = (theme: ThemeColors) =>
flex: 1,
},

searchPressable: {
height: variables.componentSizeNormal,
},

searchContainer: {

@Kicu Kicu Sep 10, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I searched in all project files using IDE (webstorm) search and found no usages of this. I believe these are leftovers after some temporary old search feature which was removed.

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,
},
Expand Down