Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Samuel Newman
Eric Bailey
and committed by
GitHub
2129f69f b4941d85

+129 -253
+75
src/components/forms/SearchInput.tsx
··· 1 + import React from 'react' 2 + import {TextInput, View} from 'react-native' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {HITSLOP_10} from '#/lib/constants' 7 + import {isNative} from '#/platform/detection' 8 + import {atoms as a, useTheme} from '#/alf' 9 + import {Button, ButtonIcon} from '#/components/Button' 10 + import * as TextField from '#/components/forms/TextField' 11 + import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass2' 12 + import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 13 + 14 + type SearchInputProps = Omit<TextField.InputProps, 'label'> & { 15 + label?: TextField.InputProps['label'] 16 + /** 17 + * Called when the user presses the (X) button 18 + */ 19 + onClearText?: () => void 20 + } 21 + 22 + export const SearchInput = React.forwardRef<TextInput, SearchInputProps>( 23 + function SearchInput({value, label, onClearText, ...rest}, ref) { 24 + const t = useTheme() 25 + const {_} = useLingui() 26 + 27 + return ( 28 + <View style={[a.w_full, a.relative]}> 29 + <TextField.Root> 30 + <TextField.Icon icon={MagnifyingGlassIcon} /> 31 + <TextField.Input 32 + inputRef={ref} 33 + label={label || _(msg`Search`)} 34 + value={value} 35 + placeholder={_(msg`Search`)} 36 + returnKeyType="search" 37 + keyboardAppearance={t.scheme} 38 + selectTextOnFocus={isNative} 39 + autoFocus={false} 40 + accessibilityRole="search" 41 + autoCorrect={false} 42 + autoComplete="off" 43 + autoCapitalize="none" 44 + {...rest} 45 + /> 46 + </TextField.Root> 47 + 48 + {value && value.length > 0 && ( 49 + <View 50 + style={[ 51 + a.absolute, 52 + a.z_10, 53 + a.my_auto, 54 + a.inset_0, 55 + a.justify_center, 56 + a.pr_sm, 57 + {left: 'auto'}, 58 + ]}> 59 + <Button 60 + testID="searchTextInputClearBtn" 61 + onPress={onClearText} 62 + label={_(msg`Clear search query`)} 63 + hitSlop={HITSLOP_10} 64 + size="tiny" 65 + shape="round" 66 + variant="ghost" 67 + color="secondary"> 68 + <ButtonIcon icon={X} size="xs" /> 69 + </Button> 70 + </View> 71 + )} 72 + </View> 73 + ) 74 + }, 75 + )
+1 -1
src/components/forms/TextField.tsx
··· 126 126 value?: string 127 127 onChangeText?: (value: string) => void 128 128 isInvalid?: boolean 129 - inputRef?: React.RefObject<TextInput> 129 + inputRef?: React.RefObject<TextInput> | React.ForwardedRef<TextInput> 130 130 } 131 131 132 132 export function createInput(Component: typeof TextInput) {
+8 -10
src/screens/StarterPack/Wizard/StepFeeds.tsx
··· 4 4 import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 + import {DISCOVER_FEED_URI} from '#/lib/constants' 7 8 import {useA11y} from '#/state/a11y' 8 - import {DISCOVER_FEED_URI} from 'lib/constants' 9 9 import { 10 10 useGetPopularFeedsQuery, 11 11 usePopularFeedsSearch, 12 12 useSavedFeeds, 13 - } from 'state/queries/feed' 14 - import {SearchInput} from 'view/com/util/forms/SearchInput' 15 - import {List} from 'view/com/util/List' 13 + } from '#/state/queries/feed' 14 + import {List} from '#/view/com/util/List' 16 15 import {useWizardState} from '#/screens/StarterPack/Wizard/State' 17 16 import {atoms as a, useTheme} from '#/alf' 17 + import {SearchInput} from '#/components/forms/SearchInput' 18 18 import {useThrottledValue} from '#/components/hooks/useThrottledValue' 19 19 import {Loader} from '#/components/Loader' 20 20 import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' ··· 81 81 return ( 82 82 <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}> 83 83 <View style={[a.border_b, t.atoms.border_contrast_medium]}> 84 - <View style={[a.my_sm, a.px_md, {height: 40}]}> 84 + <View style={[a.py_sm, a.px_md, {height: 60}]}> 85 85 <SearchInput 86 - query={query} 87 - onChangeQuery={t => setQuery(t)} 88 - onPressCancelSearch={() => setQuery('')} 89 - onSubmitQuery={() => {}} 86 + value={query} 87 + onChangeText={t => setQuery(t)} 88 + onClearText={() => setQuery('')} 90 89 /> 91 90 </View> 92 91 </View> ··· 94 93 data={query ? searchedFeeds : suggestedFeeds} 95 94 renderItem={renderItem} 96 95 keyExtractor={keyExtractor} 97 - contentContainerStyle={{paddingTop: 6}} 98 96 onEndReached={ 99 97 !query && !screenReaderEnabled ? () => fetchNextPage() : undefined 100 98 }
+9 -10
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 4 4 import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 + import {isNative} from '#/platform/detection' 7 8 import {useA11y} from '#/state/a11y' 8 - import {isNative} from 'platform/detection' 9 - import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete' 10 - import {useActorSearchPaginated} from 'state/queries/actor-search' 11 - import {SearchInput} from 'view/com/util/forms/SearchInput' 12 - import {List} from 'view/com/util/List' 9 + import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 10 + import {useActorSearchPaginated} from '#/state/queries/actor-search' 11 + import {List} from '#/view/com/util/List' 13 12 import {useWizardState} from '#/screens/StarterPack/Wizard/State' 14 13 import {atoms as a, useTheme} from '#/alf' 14 + import {SearchInput} from '#/components/forms/SearchInput' 15 15 import {Loader} from '#/components/Loader' 16 16 import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' 17 17 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' ··· 65 65 return ( 66 66 <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}> 67 67 <View style={[a.border_b, t.atoms.border_contrast_medium]}> 68 - <View style={[a.my_sm, a.px_md, {height: 40}]}> 68 + <View style={[a.py_sm, a.px_md, {height: 60}]}> 69 69 <SearchInput 70 - query={query} 71 - onChangeQuery={setQuery} 72 - onPressCancelSearch={() => setQuery('')} 73 - onSubmitQuery={() => {}} 70 + value={query} 71 + onChangeText={setQuery} 72 + onClearText={() => setQuery('')} 74 73 /> 75 74 </View> 76 75 </View>
-124
src/view/com/util/forms/SearchInput.tsx
··· 1 - import React from 'react' 2 - import { 3 - StyleProp, 4 - StyleSheet, 5 - TextInput, 6 - TouchableOpacity, 7 - View, 8 - ViewStyle, 9 - } from 'react-native' 10 - import { 11 - FontAwesomeIcon, 12 - FontAwesomeIconStyle, 13 - } from '@fortawesome/react-native-fontawesome' 14 - import {HITSLOP_10} from 'lib/constants' 15 - import {MagnifyingGlassIcon} from 'lib/icons' 16 - import {useTheme} from 'lib/ThemeContext' 17 - import {usePalette} from 'lib/hooks/usePalette' 18 - import {useLingui} from '@lingui/react' 19 - import {msg} from '@lingui/macro' 20 - 21 - interface Props { 22 - query: string 23 - setIsInputFocused?: (v: boolean) => void 24 - onChangeQuery: (v: string) => void 25 - onPressCancelSearch: () => void 26 - onSubmitQuery: () => void 27 - style?: StyleProp<ViewStyle> 28 - } 29 - 30 - export interface SearchInputRef { 31 - focus?: () => void 32 - } 33 - 34 - export const SearchInput = React.forwardRef<SearchInputRef, Props>( 35 - function SearchInput( 36 - { 37 - query, 38 - setIsInputFocused, 39 - onChangeQuery, 40 - onPressCancelSearch, 41 - onSubmitQuery, 42 - style, 43 - }, 44 - ref, 45 - ) { 46 - const theme = useTheme() 47 - const pal = usePalette('default') 48 - const {_} = useLingui() 49 - const textInput = React.useRef<TextInput>(null) 50 - 51 - const onPressCancelSearchInner = React.useCallback(() => { 52 - onPressCancelSearch() 53 - textInput.current?.blur() 54 - }, [onPressCancelSearch, textInput]) 55 - 56 - React.useImperativeHandle(ref, () => ({ 57 - focus: () => textInput.current?.focus(), 58 - blur: () => textInput.current?.blur(), 59 - })) 60 - 61 - return ( 62 - <View style={[pal.viewLight, styles.container, style]}> 63 - <MagnifyingGlassIcon style={[pal.icon, styles.icon]} size={21} /> 64 - <TextInput 65 - testID="searchTextInput" 66 - ref={textInput} 67 - placeholder={_(msg`Search`)} 68 - placeholderTextColor={pal.colors.textLight} 69 - selectTextOnFocus 70 - returnKeyType="search" 71 - value={query} 72 - style={[pal.text, styles.input]} 73 - keyboardAppearance={theme.colorScheme} 74 - onFocus={() => setIsInputFocused?.(true)} 75 - onBlur={() => setIsInputFocused?.(false)} 76 - onChangeText={onChangeQuery} 77 - onSubmitEditing={onSubmitQuery} 78 - accessibilityRole="search" 79 - accessibilityLabel={_(msg`Search`)} 80 - accessibilityHint="" 81 - autoCorrect={false} 82 - autoCapitalize="none" 83 - /> 84 - {query ? ( 85 - <TouchableOpacity 86 - onPress={onPressCancelSearchInner} 87 - accessibilityRole="button" 88 - accessibilityLabel={_(msg`Clear search query`)} 89 - accessibilityHint="" 90 - hitSlop={HITSLOP_10}> 91 - <FontAwesomeIcon 92 - icon="xmark" 93 - size={16} 94 - style={pal.textLight as FontAwesomeIconStyle} 95 - /> 96 - </TouchableOpacity> 97 - ) : undefined} 98 - </View> 99 - ) 100 - }, 101 - ) 102 - 103 - const styles = StyleSheet.create({ 104 - container: { 105 - flex: 1, 106 - flexDirection: 'row', 107 - alignItems: 'center', 108 - borderRadius: 30, 109 - paddingHorizontal: 12, 110 - paddingVertical: 8, 111 - }, 112 - icon: { 113 - marginRight: 6, 114 - alignSelf: 'center', 115 - }, 116 - input: { 117 - flex: 1, 118 - fontSize: 17, 119 - minWidth: 0, // overflow mitigation for firefox 120 - }, 121 - cancelBtn: { 122 - paddingLeft: 10, 123 - }, 124 - })
+20 -19
src/view/screens/Feeds.tsx
··· 6 6 import {useFocusEffect} from '@react-navigation/native' 7 7 import debounce from 'lodash.debounce' 8 8 9 + import {usePalette} from '#/lib/hooks/usePalette' 10 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 11 + import {ComposeIcon2} from '#/lib/icons' 12 + import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 13 + import {cleanError} from '#/lib/strings/errors' 14 + import {s} from '#/lib/styles' 9 15 import {isNative, isWeb} from '#/platform/detection' 10 16 import { 11 17 SavedFeedItem, ··· 16 22 import {useSession} from '#/state/session' 17 23 import {useSetMinimalShellMode} from '#/state/shell' 18 24 import {useComposerControls} from '#/state/shell/composer' 19 - import {usePalette} from 'lib/hooks/usePalette' 20 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 21 - import {ComposeIcon2} from 'lib/icons' 22 - import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 23 - import {cleanError} from 'lib/strings/errors' 24 - import {s} from 'lib/styles' 25 - import {ErrorMessage} from 'view/com/util/error/ErrorMessage' 26 - import {FAB} from 'view/com/util/fab/FAB' 27 - import {SearchInput} from 'view/com/util/forms/SearchInput' 28 - import {TextLink} from 'view/com/util/Link' 29 - import {List} from 'view/com/util/List' 30 - import {FeedFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' 31 - import {Text} from 'view/com/util/text/Text' 32 - import {ViewHeader} from 'view/com/util/ViewHeader' 25 + import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 26 + import {FAB} from '#/view/com/util/fab/FAB' 27 + import {TextLink} from '#/view/com/util/Link' 28 + import {List} from '#/view/com/util/List' 29 + import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 30 + import {Text} from '#/view/com/util/text/Text' 31 + import {ViewHeader} from '#/view/com/util/ViewHeader' 33 32 import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' 34 33 import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' 35 34 import {atoms as a, useTheme} from '#/alf' 36 35 import {Divider} from '#/components/Divider' 37 36 import * as FeedCard from '#/components/FeedCard' 37 + import {SearchInput} from '#/components/forms/SearchInput' 38 38 import {IconCircle} from '#/components/IconCircle' 39 39 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 40 40 import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' ··· 481 481 <FeedsAboutHeader /> 482 482 <View style={{paddingHorizontal: 12, paddingBottom: 4}}> 483 483 <SearchInput 484 - query={query} 485 - onChangeQuery={onChangeQuery} 486 - onPressCancelSearch={onPressCancelSearch} 487 - onSubmitQuery={onSubmitQuery} 488 - setIsInputFocused={onChangeSearchFocus} 484 + value={query} 485 + onChangeText={onChangeQuery} 486 + onClearText={onPressCancelSearch} 487 + onSubmitEditing={onSubmitQuery} 488 + onFocus={() => onChangeSearchFocus(true)} 489 + onBlur={() => onChangeSearchFocus(false)} 489 490 /> 490 491 </View> 491 492 </>
+11 -84
src/view/screens/Search/Search.tsx
··· 62 62 import {atoms as a, useBreakpoints, useTheme as useThemeNew, web} from '#/alf' 63 63 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 64 64 import * as FeedCard from '#/components/FeedCard' 65 - import * as TextField from '#/components/forms/TextField' 65 + import {SearchInput} from '#/components/forms/SearchInput' 66 66 import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' 67 - import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2' 68 67 import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' 69 68 import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' 70 - import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 71 69 72 70 function Loader() { 73 71 const pal = usePalette('default') ··· 864 862 <ButtonIcon icon={Menu} size="lg" /> 865 863 </Button> 866 864 )} 867 - <SearchInputBox 868 - textInput={textInput} 869 - searchText={searchText} 870 - showAutocomplete={showAutocomplete} 871 - onFocus={onSearchInputFocus} 872 - onChangeText={onChangeText} 873 - onSubmit={onSubmit} 874 - onPressClearQuery={onPressClearQuery} 875 - /> 865 + <View style={[a.flex_1]}> 866 + <SearchInput 867 + ref={textInput} 868 + value={searchText} 869 + onFocus={onSearchInputFocus} 870 + onChangeText={onChangeText} 871 + onClearText={onPressClearQuery} 872 + onSubmitEditing={onSubmit} 873 + /> 874 + </View> 876 875 {showFiltersButton && ( 877 876 <Button 878 877 onPress={() => setShowFilters(!showFilters)} ··· 960 959 </View> 961 960 ) 962 961 } 963 - 964 - let SearchInputBox = ({ 965 - textInput, 966 - searchText, 967 - showAutocomplete, 968 - onFocus, 969 - onChangeText, 970 - onSubmit, 971 - onPressClearQuery, 972 - }: { 973 - textInput: React.RefObject<TextInput> 974 - searchText: string 975 - showAutocomplete: boolean 976 - onFocus: () => void 977 - onChangeText: (text: string) => void 978 - onSubmit: () => void 979 - onPressClearQuery: () => void 980 - }): React.ReactNode => { 981 - const {_} = useLingui() 982 - const t = useThemeNew() 983 - 984 - return ( 985 - <View style={[a.flex_1]}> 986 - <TextField.Root> 987 - <TextField.Icon icon={MagnifyingGlass} /> 988 - <TextField.Input 989 - inputRef={textInput} 990 - label={_(msg`Search`)} 991 - value={searchText} 992 - placeholder={_(msg`Search`)} 993 - returnKeyType="search" 994 - onChangeText={onChangeText} 995 - onSubmitEditing={onSubmit} 996 - onFocus={onFocus} 997 - keyboardAppearance={t.scheme} 998 - selectTextOnFocus={isNative} 999 - autoFocus={false} 1000 - accessibilityRole="search" 1001 - autoCorrect={false} 1002 - autoComplete="off" 1003 - autoCapitalize="none" 1004 - /> 1005 - </TextField.Root> 1006 - 1007 - {showAutocomplete && searchText.length > 0 && ( 1008 - <View 1009 - style={[ 1010 - a.absolute, 1011 - a.z_10, 1012 - a.my_auto, 1013 - a.inset_0, 1014 - a.justify_center, 1015 - a.pr_sm, 1016 - {left: 'auto'}, 1017 - ]}> 1018 - <Button 1019 - testID="searchTextInputClearBtn" 1020 - onPress={onPressClearQuery} 1021 - label={_(msg`Clear search query`)} 1022 - hitSlop={HITSLOP_10} 1023 - size="tiny" 1024 - shape="round" 1025 - variant="ghost" 1026 - color="secondary"> 1027 - <ButtonIcon icon={X} size="sm" /> 1028 - </Button> 1029 - </View> 1030 - )} 1031 - </View> 1032 - ) 1033 - } 1034 - SearchInputBox = React.memo(SearchInputBox) 1035 962 1036 963 let AutocompleteResults = ({ 1037 964 isAutocompleteFetching,
+5 -5
src/view/shell/desktop/Search.tsx
··· 25 25 import {useModerationOpts} from '#/state/preferences/moderation-opts' 26 26 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 27 27 import {precacheProfile} from '#/state/queries/profile' 28 - import {SearchInput} from '#/view/com/util/forms/SearchInput' 29 28 import {Link} from '#/view/com/util/Link' 30 29 import {Text} from '#/view/com/util/text/Text' 31 30 import {UserAvatar} from '#/view/com/util/UserAvatar' 32 31 import {atoms as a} from '#/alf' 32 + import {SearchInput} from '#/components/forms/SearchInput' 33 33 34 34 let SearchLinkCard = ({ 35 35 label, ··· 184 184 return ( 185 185 <View style={[styles.container, pal.view]}> 186 186 <SearchInput 187 - query={query} 188 - onChangeQuery={onChangeText} 189 - onPressCancelSearch={onPressCancelSearch} 190 - onSubmitQuery={onSubmit} 187 + value={query} 188 + onChangeText={onChangeText} 189 + onClearText={onPressCancelSearch} 190 + onSubmitEditing={onSubmit} 191 191 /> 192 192 {query !== '' && isActive && moderationOpts && ( 193 193 <View style={[pal.view, pal.borderDark, styles.resultsContainer]}>