diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-10-01 00:53:11 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-30 16:53:11 -0500 |
commit | 2129f69fa0fdf85645fabe77f0803beb6d93f819 (patch) | |
tree | cb0c72c14951072de406dfbe5598c639f6af47d3 /src | |
parent | b4941d8556492375ce3ce96292e00d4e21c73e5e (diff) | |
download | voidsky-2129f69fa0fdf85645fabe77f0803beb6d93f819.tar.zst |
ALF text input for generic search input (#5511)
* alf text input for generic search input * clearer ref naming * Adjust props and definition * Migrate props * Migrate props * Migrate props * Replace on search screen * rm old props --------- Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/forms/SearchInput.tsx | 75 | ||||
-rw-r--r-- | src/components/forms/TextField.tsx | 2 | ||||
-rw-r--r-- | src/screens/StarterPack/Wizard/StepFeeds.tsx | 18 | ||||
-rw-r--r-- | src/screens/StarterPack/Wizard/StepProfiles.tsx | 19 | ||||
-rw-r--r-- | src/view/com/util/forms/SearchInput.tsx | 124 | ||||
-rw-r--r-- | src/view/screens/Feeds.tsx | 39 | ||||
-rw-r--r-- | src/view/screens/Search/Search.tsx | 95 | ||||
-rw-r--r-- | src/view/shell/desktop/Search.tsx | 10 |
8 files changed, 129 insertions, 253 deletions
diff --git a/src/components/forms/SearchInput.tsx b/src/components/forms/SearchInput.tsx new file mode 100644 index 000000000..30791d005 --- /dev/null +++ b/src/components/forms/SearchInput.tsx @@ -0,0 +1,75 @@ +import React from 'react' +import {TextInput, View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {HITSLOP_10} from '#/lib/constants' +import {isNative} from '#/platform/detection' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' +import * as TextField from '#/components/forms/TextField' +import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass2' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' + +type SearchInputProps = Omit<TextField.InputProps, 'label'> & { + label?: TextField.InputProps['label'] + /** + * Called when the user presses the (X) button + */ + onClearText?: () => void +} + +export const SearchInput = React.forwardRef<TextInput, SearchInputProps>( + function SearchInput({value, label, onClearText, ...rest}, ref) { + const t = useTheme() + const {_} = useLingui() + + return ( + <View style={[a.w_full, a.relative]}> + <TextField.Root> + <TextField.Icon icon={MagnifyingGlassIcon} /> + <TextField.Input + inputRef={ref} + label={label || _(msg`Search`)} + value={value} + placeholder={_(msg`Search`)} + returnKeyType="search" + keyboardAppearance={t.scheme} + selectTextOnFocus={isNative} + autoFocus={false} + accessibilityRole="search" + autoCorrect={false} + autoComplete="off" + autoCapitalize="none" + {...rest} + /> + </TextField.Root> + + {value && value.length > 0 && ( + <View + style={[ + a.absolute, + a.z_10, + a.my_auto, + a.inset_0, + a.justify_center, + a.pr_sm, + {left: 'auto'}, + ]}> + <Button + testID="searchTextInputClearBtn" + onPress={onClearText} + label={_(msg`Clear search query`)} + hitSlop={HITSLOP_10} + size="tiny" + shape="round" + variant="ghost" + color="secondary"> + <ButtonIcon icon={X} size="xs" /> + </Button> + </View> + )} + </View> + ) + }, +) diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 21928d3df..96d3481cd 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -126,7 +126,7 @@ export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & { value?: string onChangeText?: (value: string) => void isInvalid?: boolean - inputRef?: React.RefObject<TextInput> + inputRef?: React.RefObject<TextInput> | React.ForwardedRef<TextInput> } export function createInput(Component: typeof TextInput) { diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index f047b612a..0cf6ab231 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -4,17 +4,17 @@ import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' import {Trans} from '@lingui/macro' +import {DISCOVER_FEED_URI} from '#/lib/constants' import {useA11y} from '#/state/a11y' -import {DISCOVER_FEED_URI} from 'lib/constants' import { useGetPopularFeedsQuery, usePopularFeedsSearch, useSavedFeeds, -} from 'state/queries/feed' -import {SearchInput} from 'view/com/util/forms/SearchInput' -import {List} from 'view/com/util/List' +} from '#/state/queries/feed' +import {List} from '#/view/com/util/List' import {useWizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, useTheme} from '#/alf' +import {SearchInput} from '#/components/forms/SearchInput' import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' @@ -81,12 +81,11 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { return ( <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}> <View style={[a.border_b, t.atoms.border_contrast_medium]}> - <View style={[a.my_sm, a.px_md, {height: 40}]}> + <View style={[a.py_sm, a.px_md, {height: 60}]}> <SearchInput - query={query} - onChangeQuery={t => setQuery(t)} - onPressCancelSearch={() => setQuery('')} - onSubmitQuery={() => {}} + value={query} + onChangeText={t => setQuery(t)} + onClearText={() => setQuery('')} /> </View> </View> @@ -94,7 +93,6 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { data={query ? searchedFeeds : suggestedFeeds} renderItem={renderItem} keyExtractor={keyExtractor} - contentContainerStyle={{paddingTop: 6}} onEndReached={ !query && !screenReaderEnabled ? () => fetchNextPage() : undefined } diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index c14de847f..054a6a63e 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -4,14 +4,14 @@ import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' import {Trans} from '@lingui/macro' +import {isNative} from '#/platform/detection' import {useA11y} from '#/state/a11y' -import {isNative} from 'platform/detection' -import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete' -import {useActorSearchPaginated} from 'state/queries/actor-search' -import {SearchInput} from 'view/com/util/forms/SearchInput' -import {List} from 'view/com/util/List' +import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' +import {useActorSearchPaginated} from '#/state/queries/actor-search' +import {List} from '#/view/com/util/List' import {useWizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, useTheme} from '#/alf' +import {SearchInput} from '#/components/forms/SearchInput' import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' @@ -65,12 +65,11 @@ export function StepProfiles({ return ( <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}> <View style={[a.border_b, t.atoms.border_contrast_medium]}> - <View style={[a.my_sm, a.px_md, {height: 40}]}> + <View style={[a.py_sm, a.px_md, {height: 60}]}> <SearchInput - query={query} - onChangeQuery={setQuery} - onPressCancelSearch={() => setQuery('')} - onSubmitQuery={() => {}} + value={query} + onChangeText={setQuery} + onClearText={() => setQuery('')} /> </View> </View> diff --git a/src/view/com/util/forms/SearchInput.tsx b/src/view/com/util/forms/SearchInput.tsx deleted file mode 100644 index 5a21d8fdd..000000000 --- a/src/view/com/util/forms/SearchInput.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react' -import { - StyleProp, - StyleSheet, - TextInput, - TouchableOpacity, - View, - ViewStyle, -} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {HITSLOP_10} from 'lib/constants' -import {MagnifyingGlassIcon} from 'lib/icons' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {useLingui} from '@lingui/react' -import {msg} from '@lingui/macro' - -interface Props { - query: string - setIsInputFocused?: (v: boolean) => void - onChangeQuery: (v: string) => void - onPressCancelSearch: () => void - onSubmitQuery: () => void - style?: StyleProp<ViewStyle> -} - -export interface SearchInputRef { - focus?: () => void -} - -export const SearchInput = React.forwardRef<SearchInputRef, Props>( - function SearchInput( - { - query, - setIsInputFocused, - onChangeQuery, - onPressCancelSearch, - onSubmitQuery, - style, - }, - ref, - ) { - const theme = useTheme() - const pal = usePalette('default') - const {_} = useLingui() - const textInput = React.useRef<TextInput>(null) - - const onPressCancelSearchInner = React.useCallback(() => { - onPressCancelSearch() - textInput.current?.blur() - }, [onPressCancelSearch, textInput]) - - React.useImperativeHandle(ref, () => ({ - focus: () => textInput.current?.focus(), - blur: () => textInput.current?.blur(), - })) - - return ( - <View style={[pal.viewLight, styles.container, style]}> - <MagnifyingGlassIcon style={[pal.icon, styles.icon]} size={21} /> - <TextInput - testID="searchTextInput" - ref={textInput} - placeholder={_(msg`Search`)} - placeholderTextColor={pal.colors.textLight} - selectTextOnFocus - returnKeyType="search" - value={query} - style={[pal.text, styles.input]} - keyboardAppearance={theme.colorScheme} - onFocus={() => setIsInputFocused?.(true)} - onBlur={() => setIsInputFocused?.(false)} - onChangeText={onChangeQuery} - onSubmitEditing={onSubmitQuery} - accessibilityRole="search" - accessibilityLabel={_(msg`Search`)} - accessibilityHint="" - autoCorrect={false} - autoCapitalize="none" - /> - {query ? ( - <TouchableOpacity - onPress={onPressCancelSearchInner} - accessibilityRole="button" - accessibilityLabel={_(msg`Clear search query`)} - accessibilityHint="" - hitSlop={HITSLOP_10}> - <FontAwesomeIcon - icon="xmark" - size={16} - style={pal.textLight as FontAwesomeIconStyle} - /> - </TouchableOpacity> - ) : undefined} - </View> - ) - }, -) - -const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - borderRadius: 30, - paddingHorizontal: 12, - paddingVertical: 8, - }, - icon: { - marginRight: 6, - alignSelf: 'center', - }, - input: { - flex: 1, - fontSize: 17, - minWidth: 0, // overflow mitigation for firefox - }, - cancelBtn: { - paddingLeft: 10, - }, -}) diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 310c3dc60..87af59f7f 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -6,6 +6,12 @@ import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import debounce from 'lodash.debounce' +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {ComposeIcon2} from '#/lib/icons' +import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' +import {cleanError} from '#/lib/strings/errors' +import {s} from '#/lib/styles' import {isNative, isWeb} from '#/platform/detection' import { SavedFeedItem, @@ -16,25 +22,19 @@ import { import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ComposeIcon2} from 'lib/icons' -import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' -import {cleanError} from 'lib/strings/errors' -import {s} from 'lib/styles' -import {ErrorMessage} from 'view/com/util/error/ErrorMessage' -import {FAB} from 'view/com/util/fab/FAB' -import {SearchInput} from 'view/com/util/forms/SearchInput' -import {TextLink} from 'view/com/util/Link' -import {List} from 'view/com/util/List' -import {FeedFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' -import {Text} from 'view/com/util/text/Text' -import {ViewHeader} from 'view/com/util/ViewHeader' +import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' +import {FAB} from '#/view/com/util/fab/FAB' +import {TextLink} from '#/view/com/util/Link' +import {List} from '#/view/com/util/List' +import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {Text} from '#/view/com/util/text/Text' +import {ViewHeader} from '#/view/com/util/ViewHeader' import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' import {atoms as a, useTheme} from '#/alf' import {Divider} from '#/components/Divider' import * as FeedCard from '#/components/FeedCard' +import {SearchInput} from '#/components/forms/SearchInput' import {IconCircle} from '#/components/IconCircle' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' @@ -481,11 +481,12 @@ export function FeedsScreen(_props: Props) { <FeedsAboutHeader /> <View style={{paddingHorizontal: 12, paddingBottom: 4}}> <SearchInput - query={query} - onChangeQuery={onChangeQuery} - onPressCancelSearch={onPressCancelSearch} - onSubmitQuery={onSubmitQuery} - setIsInputFocused={onChangeSearchFocus} + value={query} + onChangeText={onChangeQuery} + onClearText={onPressCancelSearch} + onSubmitEditing={onSubmitQuery} + onFocus={() => onChangeSearchFocus(true)} + onBlur={() => onChangeSearchFocus(false)} /> </View> </> diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 77ef448ed..4a6c87f10 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -62,12 +62,10 @@ import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils' import {atoms as a, useBreakpoints, useTheme as useThemeNew, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as FeedCard from '#/components/FeedCard' -import * as TextField from '#/components/forms/TextField' +import {SearchInput} from '#/components/forms/SearchInput' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' -import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2' import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' -import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' function Loader() { const pal = usePalette('default') @@ -864,15 +862,16 @@ export function SearchScreen( <ButtonIcon icon={Menu} size="lg" /> </Button> )} - <SearchInputBox - textInput={textInput} - searchText={searchText} - showAutocomplete={showAutocomplete} - onFocus={onSearchInputFocus} - onChangeText={onChangeText} - onSubmit={onSubmit} - onPressClearQuery={onPressClearQuery} - /> + <View style={[a.flex_1]}> + <SearchInput + ref={textInput} + value={searchText} + onFocus={onSearchInputFocus} + onChangeText={onChangeText} + onClearText={onPressClearQuery} + onSubmitEditing={onSubmit} + /> + </View> {showFiltersButton && ( <Button onPress={() => setShowFilters(!showFilters)} @@ -961,78 +960,6 @@ export function SearchScreen( ) } -let SearchInputBox = ({ - textInput, - searchText, - showAutocomplete, - onFocus, - onChangeText, - onSubmit, - onPressClearQuery, -}: { - textInput: React.RefObject<TextInput> - searchText: string - showAutocomplete: boolean - onFocus: () => void - onChangeText: (text: string) => void - onSubmit: () => void - onPressClearQuery: () => void -}): React.ReactNode => { - const {_} = useLingui() - const t = useThemeNew() - - return ( - <View style={[a.flex_1]}> - <TextField.Root> - <TextField.Icon icon={MagnifyingGlass} /> - <TextField.Input - inputRef={textInput} - label={_(msg`Search`)} - value={searchText} - placeholder={_(msg`Search`)} - returnKeyType="search" - onChangeText={onChangeText} - onSubmitEditing={onSubmit} - onFocus={onFocus} - keyboardAppearance={t.scheme} - selectTextOnFocus={isNative} - autoFocus={false} - accessibilityRole="search" - autoCorrect={false} - autoComplete="off" - autoCapitalize="none" - /> - </TextField.Root> - - {showAutocomplete && searchText.length > 0 && ( - <View - style={[ - a.absolute, - a.z_10, - a.my_auto, - a.inset_0, - a.justify_center, - a.pr_sm, - {left: 'auto'}, - ]}> - <Button - testID="searchTextInputClearBtn" - onPress={onPressClearQuery} - label={_(msg`Clear search query`)} - hitSlop={HITSLOP_10} - size="tiny" - shape="round" - variant="ghost" - color="secondary"> - <ButtonIcon icon={X} size="sm" /> - </Button> - </View> - )} - </View> - ) -} -SearchInputBox = React.memo(SearchInputBox) - let AutocompleteResults = ({ isAutocompleteFetching, autocompleteData, diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index b43dbcce3..2780944f1 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -25,11 +25,11 @@ import {s} from '#/lib/styles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {precacheProfile} from '#/state/queries/profile' -import {SearchInput} from '#/view/com/util/forms/SearchInput' import {Link} from '#/view/com/util/Link' import {Text} from '#/view/com/util/text/Text' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a} from '#/alf' +import {SearchInput} from '#/components/forms/SearchInput' let SearchLinkCard = ({ label, @@ -184,10 +184,10 @@ export function DesktopSearch() { return ( <View style={[styles.container, pal.view]}> <SearchInput - query={query} - onChangeQuery={onChangeText} - onPressCancelSearch={onPressCancelSearch} - onSubmitQuery={onSubmit} + value={query} + onChangeText={onChangeText} + onClearText={onPressCancelSearch} + onSubmitEditing={onSubmit} /> {query !== '' && isActive && moderationOpts && ( <View style={[pal.view, pal.borderDark, styles.resultsContainer]}> |