about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-04-28 21:12:20 -0700
committerGitHub <noreply@github.com>2024-04-29 05:12:20 +0100
commit388c4f79cf47f6f1a7bf5fe5cc4336674930c24a (patch)
treeb859d36429f54294023069e5a438157212b4d4fd /src
parentdfce190cb68603f98fd603ca85e8b39d94f584c0 (diff)
downloadvoidsky-388c4f79cf47f6f1a7bf5fe5cc4336674930c24a.tar.zst
Increase search `TextInput` hit area and improve the related UI (#3748)
* improve hit area of search text input

use text cursor on web

use a pressable instead

use a vertical padding of 9

oops

move vertical padding to `TextInput` to increase hit area

* Hide it from a11y tree, change cursor

* Hide clear on empty text

* Render either Clear or Cancel

* Remove Clear button

* Animate it

* Better animation

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/view/screens/Search/Search.tsx104
1 files changed, 63 insertions, 41 deletions
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 9c148af5b..9355c2d60 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -7,6 +7,13 @@ import {
   TextInput,
   View,
 } from 'react-native'
+import Animated, {
+  FadeIn,
+  FadeOut,
+  LinearTransition,
+  useAnimatedStyle,
+  withSpring,
+} from 'react-native-reanimated'
 import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
 import {
   FontAwesomeIcon,
@@ -56,6 +63,7 @@ import {
 } from '#/view/shell/desktop/Search'
 import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
 import {atoms as a} from '#/alf'
+const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
 
 function Loader() {
   const pal = usePalette('default')
@@ -527,23 +535,10 @@ export function SearchScreen(
 
   const onPressCancelSearch = React.useCallback(() => {
     scrollToTopWeb()
-
-    if (showAutocomplete) {
-      textInput.current?.blur()
-      setShowAutocomplete(false)
-      setSearchText(queryParam)
-    } else {
-      // If we just `setParams` and set `q` to an empty string, the URL still displays `q=`, which isn't pretty.
-      // However, `.replace()` on native has a "push" animation that we don't want. So we need to handle these
-      // differently.
-      if (isWeb) {
-        navigation.replace('Search', {})
-      } else {
-        setSearchText('')
-        navigation.setParams({q: ''})
-      }
-    }
-  }, [showAutocomplete, navigation, queryParam])
+    textInput.current?.blur()
+    setShowAutocomplete(false)
+    setSearchText(queryParam)
+  }, [queryParam])
 
   const onChangeText = React.useCallback(async (text: string) => {
     scrollToTopWeb()
@@ -629,6 +624,14 @@ export function SearchScreen(
     )
   }
 
+  const showClearButton = showAutocomplete && searchText.length > 0
+  const clearButtonStyle = useAnimatedStyle(() => ({
+    opacity: withSpring(showClearButton ? 1 : 0, {
+      overshootClamping: true,
+      duration: 50,
+    }),
+  }))
+
   return (
     <View style={isWeb ? null : {flex: 1}}>
       <CenteredView
@@ -656,11 +659,24 @@ export function SearchScreen(
           </Pressable>
         )}
 
-        <View
+        <AnimatedPressable
+          // This only exists only for extra hitslop so don't expose it to the a11y tree.
+          accessible={false}
+          focusable={false}
+          // @ts-ignore web-only
+          tabIndex={-1}
+          layout={isNative ? LinearTransition.duration(200) : undefined}
           style={[
             {backgroundColor: pal.colors.backgroundLight},
             styles.headerSearchContainer,
-          ]}>
+            isWeb && {
+              // @ts-ignore web only
+              cursor: 'default',
+            },
+          ]}
+          onPress={() => {
+            textInput.current?.focus()
+          }}>
           <MagnifyingGlassIcon
             style={[pal.icon, styles.headerSearchIcon]}
             size={21}
@@ -702,33 +718,36 @@ export function SearchScreen(
             autoComplete="off"
             autoCapitalize="none"
           />
-          {showAutocomplete ? (
-            <Pressable
-              testID="searchTextInputClearBtn"
-              onPress={onPressClearQuery}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Clear search query`)}
-              accessibilityHint=""
-              hitSlop={HITSLOP_10}>
-              <FontAwesomeIcon
-                icon="xmark"
-                size={16}
-                style={pal.textLight as FontAwesomeIconStyle}
-              />
-            </Pressable>
-          ) : undefined}
-        </View>
-
-        {(queryParam || showAutocomplete) && (
-          <View style={styles.headerCancelBtn}>
-            <Pressable
+          <AnimatedPressable
+            layout={isNative ? LinearTransition.duration(200) : undefined}
+            disabled={!showClearButton}
+            style={clearButtonStyle}
+            testID="searchTextInputClearBtn"
+            onPress={onPressClearQuery}
+            accessibilityRole="button"
+            accessibilityLabel={_(msg`Clear search query`)}
+            accessibilityHint=""
+            hitSlop={HITSLOP_10}>
+            <FontAwesomeIcon
+              icon="xmark"
+              size={16}
+              style={pal.textLight as FontAwesomeIconStyle}
+            />
+          </AnimatedPressable>
+        </AnimatedPressable>
+        {showAutocomplete && (
+          <View style={[styles.headerCancelBtn]}>
+            <AnimatedPressable
+              entering={isNative ? FadeIn.duration(300) : undefined}
+              exiting={isNative ? FadeOut.duration(50) : undefined}
+              key="cancel"
               onPress={onPressCancelSearch}
               accessibilityRole="button"
               hitSlop={HITSLOP_10}>
-              <Text style={[pal.text]}>
+              <Text style={pal.text}>
                 <Trans>Cancel</Trans>
               </Text>
-            </Pressable>
+            </AnimatedPressable>
           </View>
         )}
       </CenteredView>
@@ -880,6 +899,9 @@ const styles = StyleSheet.create({
   },
   headerCancelBtn: {
     paddingLeft: 10,
+    alignSelf: 'center',
+    zIndex: -1,
+    elevation: -1, // For Android
   },
   tabBarContainer: {
     // @ts-ignore web only