-
Notifications
You must be signed in to change notification settings - Fork 4k
Add SearchRouter component and context to display it #48785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6d61200
1f7c608
d7a8311
2772431
b2547c4
3e26bf1
d89f4d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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="" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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={() => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| 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; |
| 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}; |
| 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(''); | ||
|
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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coming from #54151, we add |
||
| value={value} | ||
| onChangeText={onChangeText} | ||
| onSubmitEditing={onSubmit} | ||
| autoFocus | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| textInputContainerStyles={[{borderBottomWidth: 0}, modalWidth]} | ||
| inputStyle={[styles.searchInputStyle, styles.searchRouterInputStyle, styles.ph2]} | ||
| role={CONST.ROLE.PRESENTATION} | ||
| autoCapitalize="none" | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| SearchRouterInput.displayName = 'SearchRouterInput'; | ||
|
|
||
| export default SearchRouterInput; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3593,31 +3593,19 @@ const styles = (theme: ThemeColors) => | |
| flex: 1, | ||
| }, | ||
|
|
||
| searchPressable: { | ||
| height: variables.componentSizeNormal, | ||
| }, | ||
|
|
||
| searchContainer: { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| }, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.