about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-10-01 00:53:11 +0300
committerGitHub <noreply@github.com>2024-09-30 16:53:11 -0500
commit2129f69fa0fdf85645fabe77f0803beb6d93f819 (patch)
treecb0c72c14951072de406dfbe5598c639f6af47d3 /src
parentb4941d8556492375ce3ce96292e00d4e21c73e5e (diff)
downloadvoidsky-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.tsx75
-rw-r--r--src/components/forms/TextField.tsx2
-rw-r--r--src/screens/StarterPack/Wizard/StepFeeds.tsx18
-rw-r--r--src/screens/StarterPack/Wizard/StepProfiles.tsx19
-rw-r--r--src/view/com/util/forms/SearchInput.tsx124
-rw-r--r--src/view/screens/Feeds.tsx39
-rw-r--r--src/view/screens/Search/Search.tsx95
-rw-r--r--src/view/shell/desktop/Search.tsx10
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]}>