Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

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>

authored by

Hailey
Dan Abramov
and committed by
GitHub
388c4f79 dfce190c

+63 -41
+63 -41
src/view/screens/Search/Search.tsx
··· 7 7 TextInput, 8 8 View, 9 9 } from 'react-native' 10 + import Animated, { 11 + FadeIn, 12 + FadeOut, 13 + LinearTransition, 14 + useAnimatedStyle, 15 + withSpring, 16 + } from 'react-native-reanimated' 10 17 import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api' 11 18 import { 12 19 FontAwesomeIcon, ··· 56 63 } from '#/view/shell/desktop/Search' 57 64 import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' 58 65 import {atoms as a} from '#/alf' 66 + const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 59 67 60 68 function Loader() { 61 69 const pal = usePalette('default') ··· 527 535 528 536 const onPressCancelSearch = React.useCallback(() => { 529 537 scrollToTopWeb() 530 - 531 - if (showAutocomplete) { 532 - textInput.current?.blur() 533 - setShowAutocomplete(false) 534 - setSearchText(queryParam) 535 - } else { 536 - // If we just `setParams` and set `q` to an empty string, the URL still displays `q=`, which isn't pretty. 537 - // However, `.replace()` on native has a "push" animation that we don't want. So we need to handle these 538 - // differently. 539 - if (isWeb) { 540 - navigation.replace('Search', {}) 541 - } else { 542 - setSearchText('') 543 - navigation.setParams({q: ''}) 544 - } 545 - } 546 - }, [showAutocomplete, navigation, queryParam]) 538 + textInput.current?.blur() 539 + setShowAutocomplete(false) 540 + setSearchText(queryParam) 541 + }, [queryParam]) 547 542 548 543 const onChangeText = React.useCallback(async (text: string) => { 549 544 scrollToTopWeb() ··· 629 624 ) 630 625 } 631 626 627 + const showClearButton = showAutocomplete && searchText.length > 0 628 + const clearButtonStyle = useAnimatedStyle(() => ({ 629 + opacity: withSpring(showClearButton ? 1 : 0, { 630 + overshootClamping: true, 631 + duration: 50, 632 + }), 633 + })) 634 + 632 635 return ( 633 636 <View style={isWeb ? null : {flex: 1}}> 634 637 <CenteredView ··· 656 659 </Pressable> 657 660 )} 658 661 659 - <View 662 + <AnimatedPressable 663 + // This only exists only for extra hitslop so don't expose it to the a11y tree. 664 + accessible={false} 665 + focusable={false} 666 + // @ts-ignore web-only 667 + tabIndex={-1} 668 + layout={isNative ? LinearTransition.duration(200) : undefined} 660 669 style={[ 661 670 {backgroundColor: pal.colors.backgroundLight}, 662 671 styles.headerSearchContainer, 663 - ]}> 672 + isWeb && { 673 + // @ts-ignore web only 674 + cursor: 'default', 675 + }, 676 + ]} 677 + onPress={() => { 678 + textInput.current?.focus() 679 + }}> 664 680 <MagnifyingGlassIcon 665 681 style={[pal.icon, styles.headerSearchIcon]} 666 682 size={21} ··· 702 718 autoComplete="off" 703 719 autoCapitalize="none" 704 720 /> 705 - {showAutocomplete ? ( 706 - <Pressable 707 - testID="searchTextInputClearBtn" 708 - onPress={onPressClearQuery} 709 - accessibilityRole="button" 710 - accessibilityLabel={_(msg`Clear search query`)} 711 - accessibilityHint="" 712 - hitSlop={HITSLOP_10}> 713 - <FontAwesomeIcon 714 - icon="xmark" 715 - size={16} 716 - style={pal.textLight as FontAwesomeIconStyle} 717 - /> 718 - </Pressable> 719 - ) : undefined} 720 - </View> 721 - 722 - {(queryParam || showAutocomplete) && ( 723 - <View style={styles.headerCancelBtn}> 724 - <Pressable 721 + <AnimatedPressable 722 + layout={isNative ? LinearTransition.duration(200) : undefined} 723 + disabled={!showClearButton} 724 + style={clearButtonStyle} 725 + testID="searchTextInputClearBtn" 726 + onPress={onPressClearQuery} 727 + accessibilityRole="button" 728 + accessibilityLabel={_(msg`Clear search query`)} 729 + accessibilityHint="" 730 + hitSlop={HITSLOP_10}> 731 + <FontAwesomeIcon 732 + icon="xmark" 733 + size={16} 734 + style={pal.textLight as FontAwesomeIconStyle} 735 + /> 736 + </AnimatedPressable> 737 + </AnimatedPressable> 738 + {showAutocomplete && ( 739 + <View style={[styles.headerCancelBtn]}> 740 + <AnimatedPressable 741 + entering={isNative ? FadeIn.duration(300) : undefined} 742 + exiting={isNative ? FadeOut.duration(50) : undefined} 743 + key="cancel" 725 744 onPress={onPressCancelSearch} 726 745 accessibilityRole="button" 727 746 hitSlop={HITSLOP_10}> 728 - <Text style={[pal.text]}> 747 + <Text style={pal.text}> 729 748 <Trans>Cancel</Trans> 730 749 </Text> 731 - </Pressable> 750 + </AnimatedPressable> 732 751 </View> 733 752 )} 734 753 </CenteredView> ··· 880 899 }, 881 900 headerCancelBtn: { 882 901 paddingLeft: 10, 902 + alignSelf: 'center', 903 + zIndex: -1, 904 + elevation: -1, // For Android 883 905 }, 884 906 tabBarContainer: { 885 907 // @ts-ignore web only