Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Improved search page (#7590)

* search input revamp

* fix web

* rm "useThemeNew"

* fix overlap during transition

* animate header properly

* reduce gap

* animate cancel button in

* don't move search bar when focused

* remove cancel button animation

authored by

Samuel Newman and committed by
GitHub
b37199a5 1a3ecdf6

+101 -93
+2 -1
src/components/Layout/Header/index.tsx
··· 1 1 import {createContext, useCallback, useContext} from 'react' 2 - import {GestureResponderEvent, View} from 'react-native' 2 + import {GestureResponderEvent, Keyboard, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' ··· 140 140 const {gtMobile} = useBreakpoints() 141 141 142 142 const onPress = useCallback(() => { 143 + Keyboard.dismiss() 143 144 setDrawerOpen(true) 144 145 }, [setDrawerOpen]) 145 146
+1 -1
src/components/forms/TextField.tsx
··· 226 226 <> 227 227 <Component 228 228 accessibilityHint={undefined} 229 + hitSlop={HITSLOP_20} 229 230 {...rest} 230 231 accessibilityLabel={label} 231 232 ref={refs} ··· 242 243 placeholder={placeholder || label} 243 244 placeholderTextColor={t.palette.contrast_500} 244 245 keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} 245 - hitSlop={HITSLOP_20} 246 246 style={flattened} 247 247 /> 248 248
+98 -91
src/view/screens/Search/Search.tsx
··· 1 - import React, {useCallback} from 'react' 1 + import React, {useCallback, useLayoutEffect} from 'react' 2 2 import { 3 3 ActivityIndicator, 4 4 Image, ··· 21 21 import {useFocusEffect, useNavigation} from '@react-navigation/native' 22 22 23 23 import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages' 24 - import {createHitslop} from '#/lib/constants' 24 + import {createHitslop, HITSLOP_20} from '#/lib/constants' 25 25 import {HITSLOP_10} from '#/lib/constants' 26 26 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 27 27 import {usePalette} from '#/lib/hooks/usePalette' ··· 46 46 import {useProfilesQuery} from '#/state/queries/profile' 47 47 import {useSearchPostsQuery} from '#/state/queries/search-posts' 48 48 import {useSession} from '#/state/session' 49 - import {useSetDrawerOpen} from '#/state/shell' 50 49 import {useSetMinimalShellMode} from '#/state/shell' 51 50 import {Pager} from '#/view/com/pager/Pager' 52 51 import {TabBar} from '#/view/com/pager/TabBar' ··· 58 57 import {Explore} from '#/view/screens/Search/Explore' 59 58 import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search' 60 59 import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils' 61 - import { 62 - atoms as a, 63 - tokens, 64 - useBreakpoints, 65 - useTheme as useThemeNew, 66 - web, 67 - } from '#/alf' 68 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 60 + import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf' 61 + import {Button, ButtonText} from '#/components/Button' 69 62 import * as FeedCard from '#/components/FeedCard' 70 63 import {SearchInput} from '#/components/forms/SearchInput' 71 64 import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' 72 - import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' 73 65 import * as Layout from '#/components/Layout' 74 66 import {account, useStorage} from '#/storage' 75 67 ··· 278 270 query: string 279 271 active: boolean 280 272 }): React.ReactNode => { 281 - const t = useThemeNew() 273 + const t = useTheme() 282 274 const {_} = useLingui() 283 275 284 276 const {data: results, isFetched} = usePopularFeedsSearch({ ··· 323 315 value: string 324 316 onChange(value: string): void 325 317 }) { 326 - const t = useThemeNew() 318 + const t = useTheme() 327 319 const {_} = useLingui() 328 320 const {appLanguage, contentLanguages} = useLanguagePrefs() 329 321 ··· 534 526 <Pager 535 527 onPageSelected={onPageSelected} 536 528 renderTabBar={props => ( 537 - <Layout.Center 538 - style={[ 539 - a.z_10, 540 - web([a.sticky]), 541 - {top: isWeb ? headerHeight : undefined}, 542 - ]}> 529 + <Layout.Center style={[a.z_10, web([a.sticky, {top: headerHeight}])]}> 543 530 <TabBar items={sections.map(section => section.title)} {...props} /> 544 531 </Layout.Center> 545 532 )} ··· 597 584 export function SearchScreen( 598 585 props: NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>, 599 586 ) { 600 - const t = useThemeNew() 587 + const t = useTheme() 601 588 const {gtMobile} = useBreakpoints() 602 589 const navigation = useNavigation<NavigationProp>() 603 590 const textInput = React.useRef<TextInput>(null) 604 591 const {_} = useLingui() 605 - const setDrawerOpen = useSetDrawerOpen() 606 592 const setMinimalShellMode = useSetMinimalShellMode() 607 593 const {currentAccount} = useSession() 608 594 ··· 630 616 631 617 const updateSearchHistory = useCallback( 632 618 async (item: string) => { 619 + if (!item) return 633 620 const newSearchHistory = [ 634 621 item, 635 622 ...termHistory.filter(search => search !== item), ··· 667 654 initialQuery: queryParam, 668 655 }) 669 656 const showFilters = Boolean(queryWithParams && !showAutocomplete) 670 - /* 671 - * Arbitrary sizing, so guess and check, used for sticky header alignment and 672 - * sizing. 673 - */ 674 - const headerHeight = 60 + (showFilters ? 40 : 0) 657 + 658 + // web only - measure header height for sticky positioning 659 + const [headerHeight, setHeaderHeight] = React.useState(0) 660 + const headerRef = React.useRef(null) 661 + useLayoutEffect(() => { 662 + if (isWeb) { 663 + if (!headerRef.current) return 664 + const measurement = (headerRef.current as Element).getBoundingClientRect() 665 + setHeaderHeight(measurement.height) 666 + } 667 + }, []) 675 668 676 669 useFocusEffect( 677 670 useNonReactiveCallback(() => { ··· 681 674 }), 682 675 ) 683 676 684 - const onPressMenu = React.useCallback(() => { 685 - textInput.current?.blur() 686 - setDrawerOpen(true) 687 - }, [setDrawerOpen]) 688 - 689 677 const onPressClearQuery = React.useCallback(() => { 690 678 scrollToTopWeb() 691 679 setSearchText('') ··· 789 777 return ( 790 778 <Layout.Screen testID="searchScreen"> 791 779 <View 780 + ref={headerRef} 781 + onLayout={evt => { 782 + if (isWeb) setHeaderHeight(evt.nativeEvent.layout.height) 783 + }} 792 784 style={[ 785 + a.relative, 786 + a.z_10, 793 787 web({ 794 - height: headerHeight, 795 788 position: 'sticky', 796 789 top: 0, 797 - zIndex: 1, 798 790 }), 799 791 ]}> 800 - <Layout.Center> 801 - <View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}> 802 - <View style={[a.flex_row, a.gap_sm]}> 803 - {!gtMobile && !showAutocomplete && ( 804 - <Button 805 - testID="viewHeaderBackOrMenuBtn" 806 - onPress={onPressMenu} 807 - hitSlop={HITSLOP_10} 808 - label={_(msg`Menu`)} 809 - accessibilityHint={_( 810 - msg`Provides access to navigation links and settings`, 811 - )} 812 - size="large" 813 - variant="solid" 814 - color="secondary" 815 - shape="square"> 816 - <ButtonIcon icon={Menu} size="lg" /> 817 - </Button> 818 - )} 819 - <View style={[a.flex_1]}> 820 - <SearchInput 821 - ref={textInput} 822 - value={searchText} 823 - onFocus={onSearchInputFocus} 824 - onChangeText={onChangeText} 825 - onClearText={onPressClearQuery} 826 - onSubmitEditing={onSubmit} 827 - /> 828 - </View> 829 - {showAutocomplete && ( 830 - <Button 831 - label={_(msg`Cancel search`)} 832 - size="large" 833 - variant="ghost" 834 - color="secondary" 835 - style={[a.px_sm]} 836 - onPress={onPressCancelSearch} 837 - hitSlop={HITSLOP_10}> 838 - <ButtonText> 839 - <Trans>Cancel</Trans> 840 - </ButtonText> 841 - </Button> 842 - )} 792 + <Layout.Center style={t.atoms.bg}> 793 + {!gtMobile && ( 794 + <View 795 + // HACK: shift up search input. we can't remove the top padding 796 + // on the search input because it messes up the layout animation 797 + // if we add it only when the header is hidden 798 + style={{marginBottom: tokens.space.sm * -1}}> 799 + <Layout.Header.Outer noBottomBorder> 800 + <Layout.Header.MenuButton /> 801 + <Layout.Header.Content 802 + align={showFilters ? 'left' : 'platform'}> 803 + <Layout.Header.TitleText> 804 + <Trans>Search</Trans> 805 + </Layout.Header.TitleText> 806 + </Layout.Header.Content> 807 + {showFilters ? ( 808 + <View style={[{minWidth: 140}]}> 809 + <SearchLanguageDropdown 810 + value={params.lang} 811 + onChange={params.setLang} 812 + /> 813 + </View> 814 + ) : ( 815 + <Layout.Header.Slot /> 816 + )} 817 + </Layout.Header.Outer> 843 818 </View> 844 - 845 - {showFilters && ( 846 - <View 847 - style={[ 848 - a.flex_row, 849 - a.align_center, 850 - a.justify_between, 851 - a.gap_sm, 852 - ]}> 853 - <View style={[{width: 140}]}> 854 - <SearchLanguageDropdown 855 - value={params.lang} 856 - onChange={params.setLang} 819 + )} 820 + <View style={[a.px_md, a.pt_sm, a.pb_sm, a.overflow_hidden]}> 821 + <View style={[a.gap_sm]}> 822 + <View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_xs]}> 823 + <View style={[a.flex_1]}> 824 + <SearchInput 825 + ref={textInput} 826 + value={searchText} 827 + onFocus={onSearchInputFocus} 828 + onChangeText={onChangeText} 829 + onClearText={onPressClearQuery} 830 + onSubmitEditing={onSubmit} 831 + placeholder={_(msg`Search for posts, users, or feeds`)} 832 + hitSlop={{...HITSLOP_20, top: 0}} 857 833 /> 858 834 </View> 835 + {showAutocomplete && ( 836 + <Button 837 + label={_(msg`Cancel search`)} 838 + size="large" 839 + variant="ghost" 840 + color="secondary" 841 + style={[a.px_sm]} 842 + onPress={onPressCancelSearch} 843 + hitSlop={HITSLOP_10}> 844 + <ButtonText> 845 + <Trans>Cancel</Trans> 846 + </ButtonText> 847 + </Button> 848 + )} 859 849 </View> 860 - )} 850 + 851 + {showFilters && gtMobile && ( 852 + <View 853 + style={[ 854 + a.flex_row, 855 + a.align_center, 856 + a.justify_between, 857 + a.gap_sm, 858 + ]}> 859 + <View style={[{width: 140}]}> 860 + <SearchLanguageDropdown 861 + value={params.lang} 862 + onChange={params.setLang} 863 + /> 864 + </View> 865 + </View> 866 + )} 867 + </View> 861 868 </View> 862 869 </Layout.Center> 863 870 </View>