Skip to content

Commit

Permalink
Add initial SearchRouter component and context to display it
Browse files Browse the repository at this point in the history
  • Loading branch information
Kicu committed Sep 10, 2024
1 parent 1725041 commit c19c62d
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 1 deletion.
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 @@ -1045,7 +1045,6 @@ const CONST = {
BOTTOM_DOCKED: 'bottom_docked',
POPOVER: 'popover',
RIGHT_DOCKED: 'right_docked',
ONBOARDING: 'onboarding',
},
ANCHOR_ORIGIN_VERTICAL: {
TOP: 'top',
Expand Down
2 changes: 2 additions & 0 deletions src/components/Search/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/typ
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import SearchButton from '@components/Search/SearchRouter/SearchButton';
import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
Expand Down Expand Up @@ -306,6 +307,7 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa
isSplitButton={false}
/>
)}
<SearchButton />
<Button
text={translate('search.filtersHeader')}
icon={Expensicons.Filters}
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 * as SearchUtils from '@libs/SearchUtils';
import {useSearchRouterContext} from './SearchRouterContext';

function SearchButton() {
const styles = useThemeStyles();
const theme = useTheme();
const {toggleSearchRouter} = useSearchRouterContext();

if (!SearchUtils.shouldDisplayNewSearchRouter()) {
return;
}

return (
<PressableWithoutFeedback
accessibilityLabel=""
style={[styles.flexRow, styles.mr2, styles.touchableButtonImage]}
onPress={() => {
toggleSearchRouter();
}}
>
<Icon
src={Expensicons.MagnifyingGlass}
fill={theme.icon}
/>
</PressableWithoutFeedback>
);
}

SearchButton.displayName = 'SearchButton';

export default SearchButton;
64 changes: 64 additions & 0 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import Modal from '@components/Modal';
import type {SearchQueryJSON} from '@components/Search/types';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as SearchUtils from '@libs/SearchUtils';
import CONST from '@src/CONST';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
import {useSearchRouterContext} from './SearchRouterContext';
import SearchRouterInput from './SearchRouterInput';

type SearchRouterProps = {
type?: SearchDataTypes;
};

function SearchRouter({type}: SearchRouterProps) {
const styles = useThemeStyles();
const {isSmallScreenWidth} = useResponsiveLayout();

const {isSearchRouterDisplayed, toggleSearchRouter} = useSearchRouterContext();
const [, setCurrentQuery] = useState<SearchQueryJSON | undefined>(undefined);

const modalType = isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE : CONST.MODAL.MODAL_TYPE.POPOVER;

const onSearch = (userQuery: string) => {
if (!userQuery) {
setCurrentQuery(undefined);
return;
}

const query = type ? `type:${type} ${userQuery}` : userQuery;
const queryJSON = SearchUtils.buildSearchQueryJSON(query);

if (queryJSON) {
// eslint-disable-next-line
console.log('parsedQuery', queryJSON);

setCurrentQuery(queryJSON);
} else {
// Handle query parsing error
}
};

return (
<Modal
type={modalType}
fullscreen
isVisible={isSearchRouterDisplayed}
popoverAnchorPosition={{right: 20, top: 20}}
onClose={() => {
toggleSearchRouter();
}}
>
<View style={[styles.flex1, styles.p5]}>
<SearchRouterInput onSearch={onSearch} />
</View>
</Modal>
);
}

SearchRouter.displayName = 'SearchRouter';

export default SearchRouter;
33 changes: 33 additions & 0 deletions src/components/Search/SearchRouter/SearchRouterContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, {useCallback, useContext, useState} from 'react';
import type ChildrenProps from '@src/types/utils/ChildrenProps';

const defaultSearchContext = {
isSearchRouterDisplayed: false,
toggleSearchRouter: () => {},
};

type SearchRouterContext = typeof defaultSearchContext;

const Context = React.createContext<SearchRouterContext>(defaultSearchContext);

function SearchRouterContextProvider({children}: ChildrenProps) {
const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false);

const toggleSearchRouter = useCallback(() => {
setIsSearchRouterDisplayed(!isSearchRouterDisplayed);
}, [isSearchRouterDisplayed]);

const routerContext = {
isSearchRouterDisplayed,
toggleSearchRouter,
};
return <Context.Provider value={routerContext}>{children}</Context.Provider>;
}

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

SearchRouterContextProvider.displayName = 'SearchRouterContextProvider';

export {SearchRouterContextProvider, useSearchRouterContext};
50 changes: 50 additions & 0 deletions src/components/Search/SearchRouter/SearchRouterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import TextInput from '@components/TextInput';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type SearchRouterInputProps = {
onSearch: (searchTerm: string) => void;
};

function SearchRouterInput({onSearch}: SearchRouterInputProps) {
const styles = useThemeStyles();
const theme = useTheme();

const [value, setValue] = useState('');

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

return (
<View style={[]}>
<TextInput
value={value}
onChangeText={onChangeText}
textInputContainerStyles={[{borderWidth: 0}]}
containerStyles={[]}
hideFocusedState
inputStyle={[
styles.w80,
styles.h13,
styles.ph4,
{
width: 400,
borderRadius: 8,
borderWidth: 4,
borderColor: theme.borderFocus,
},
]}
role={CONST.ROLE.PRESENTATION}
/>
</View>
);
}

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 @@ -561,6 +562,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
);
})}
</RootStack.Navigator>
<SearchRouter />
</View>
</ComposeProviders>
);
Expand Down
11 changes: 11 additions & 0 deletions src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ChatListItem from '@components/SelectionList/ChatListItem';
import ReportListItem from '@components/SelectionList/Search/ReportListItem';
import TransactionListItem from '@components/SelectionList/Search/TransactionListItem';
import type {ListItem, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import * as Environment from '@libs/Environment/Environment';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -692,6 +693,15 @@ function isCannedSearchQuery(queryJSON: SearchQueryJSON) {
return !queryJSON.filters;
}

/**
* New Search Router is under construction and for now should be displayed only in dev.
*
* After everything is implemented this function can be removed, as we will start to always use SearchRouter.
*/
function shouldDisplayNewSearchRouter() {
return Environment.isDevelopment();
}

export {
buildQueryStringFromFilters,
buildSearchQueryJSON,
Expand All @@ -715,4 +725,5 @@ export {
getExpenseTypeTranslationKey,
getChatFiltersTranslationKey,
getChatStatusTranslationKey,
shouldDisplayNewSearchRouter,
};

0 comments on commit c19c62d

Please sign in to comment.