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>
zio/stable
Hailey 2024-04-28 21:12:20 -07:00 committed by GitHub
parent dfce190cb6
commit 388c4f79cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 63 additions and 41 deletions

View File

@ -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