From 83da55c32bd4521cef2efc7db1ad20ea71f5a221 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 9 Jul 2026 12:36:37 +0300 Subject: [PATCH] Fix search typeahead performance with absolute overlay (#11103) Co-authored-by: Claude Fable 5 --- src/screens/Search/Shell.tsx | 121 +++++++++++------- .../Search/components/SearchHistory.tsx | 68 ++++++---- 2 files changed, 121 insertions(+), 68 deletions(-) diff --git a/src/screens/Search/Shell.tsx b/src/screens/Search/Shell.tsx index 749819d589..fa10ce7e0c 100644 --- a/src/screens/Search/Shell.tsx +++ b/src/screens/Search/Shell.tsx @@ -13,6 +13,11 @@ import { View, type ViewStyle, } from 'react-native' +import Animated, { + Easing, + FadeInDown, + FadeOutDown, +} from 'react-native-reanimated' import {setStringAsync} from 'expo-clipboard' import {Trans, useLingui} from '@lingui/react/macro' import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native' @@ -41,7 +46,15 @@ import { withoutFilterParams, } from '#/screens/Search/searchParams' import {makeSearchQuery} from '#/screens/Search/utils' -import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf' +import { + atoms as a, + native, + platform, + tokens, + useBreakpoints, + useTheme, + web, +} from '#/alf' import {useAutocomplete} from '#/components/Autocomplete' import {Button, ButtonIcon} from '#/components/Button' import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' @@ -387,13 +400,14 @@ export function SearchScreenShell({ const handleProfileClick = useCallback( (profile: bsky.profile.AnyProfileView) => { + updateSearchText('') unstableCacheProfileView(queryClient, profile) // Slight delay to avoid updating during push nav animation. setTimeout(() => { updateProfileHistory(profile) }, 400) }, - [updateProfileHistory, queryClient], + [updateProfileHistory, queryClient, updateSearchText], ) /** @@ -468,6 +482,17 @@ export function SearchScreenShell({ } }, [setShowAutocomplete]) + const onSearchInputBlur = useCallback(() => { + /* + * Bind autocomplete visibility to focus state on native. On web this + * doesn't work because of focus management, which would render the + * autocomplete results uninteractable. + */ + if (IS_NATIVE) { + setShowAutocomplete(false) + } + }, []) + const focusSearchInput = useCallback( (tab?: TabParam) => { textInput.current?.focus() @@ -597,6 +622,7 @@ export function SearchScreenShell({ ref={textInput} value={searchText} onFocus={onSearchInputFocus} + onBlur={onSearchInputBlur} onChangeText={onChangeText} onClearText={onPressClearQuery} onSubmitEditing={onSubmit('typed')} @@ -643,52 +669,57 @@ export function SearchScreenShell({ - - {searchText.length > 0 && IS_NATIVE ? ( - - ) : ( - - accountHistory.includes(p.did), - ) ?? [] - } - onItemClick={handleHistoryItemClick} - onProfileClick={handleProfileClick} - onRemoveItemClick={deleteSearchHistoryItem} - onRemoveProfileClick={deleteProfileHistoryItem} + + + + + + {showAutocomplete && !fixedParams && ( + + {searchText.length > 0 && IS_NATIVE ? ( + + ) : ( + + accountHistory.includes(p.did), + ) ?? [] + } + onItemClick={handleHistoryItemClick} + onProfileClick={handleProfileClick} + onRemoveItemClick={deleteSearchHistoryItem} + onRemoveProfileClick={deleteProfileHistoryItem} + /> + )} + )} - - - ) } diff --git a/src/screens/Search/components/SearchHistory.tsx b/src/screens/Search/components/SearchHistory.tsx index 91d3264307..31186726b2 100644 --- a/src/screens/Search/components/SearchHistory.tsx +++ b/src/screens/Search/components/SearchHistory.tsx @@ -1,8 +1,8 @@ -import {Pressable, ScrollView, View} from 'react-native' +import {ScrollView, View} from 'react-native' import {moderateProfile, type ModerationOpts} from '@atproto/api' import {Plural, Trans, useLingui} from '@lingui/react/macro' -import {createHitslop, HITSLOP_10} from '#/lib/constants' +import {createHitslop} from '#/lib/constants' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' @@ -89,7 +89,7 @@ export function SearchHistory({ )} {searchHistory.length > 0 && ( - + {searchHistory.slice(0, 5).map((historyItem, index) => { const {q, filters} = parseHistoryEntry(historyItem) const filterCount = countActiveFilters(filters) @@ -132,38 +132,60 @@ function SearchHistoryItem({ const {t: l} = useLingui() return ( - - - - {q} - - {filterCount > 0 ? ( + +