Fix search typeahead performance with absolute overlay

Instead of toggling display:none/flex on the typeahead and content panels
(which causes heavy unmount/remount cycles), the content (SearchScreenInner)
now stays always mounted and the typeahead overlay is absolutely positioned
on top with conditional rendering.

Added reanimated FadeInUp/FadeOutDown animations (native only via native()
wrapper) for a smooth transition. Added accessibility props
(accessibilityViewIsModal, aria-modal, accessibilityRole) so screen readers
properly treat the overlay as a modal list.

https://claude.ai/code/session_01BhE3QBg6XZwXTwgTbQP9L9
This commit is contained in:
Claude
2026-03-22 19:31:35 +00:00
committed by Samuel Newman
parent f1764f1d61
commit 09833572be
+23 -21
View File
@@ -12,6 +12,7 @@ import {
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import Animated, {FadeInUp, FadeOutDown} from 'react-native-reanimated'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native' import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -32,7 +33,7 @@ import {
type Params, type Params,
parseSearchQuery, parseSearchQuery,
} from '#/screens/Search/utils' } from '#/screens/Search/utils'
import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, native, tokens, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {SearchInput} from '#/components/forms/SearchInput' import {SearchInput} from '#/components/forms/SearchInput'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
@@ -418,11 +419,25 @@ export function SearchScreenShell({
</Layout.Center> </Layout.Center>
</View> </View>
<View <View style={[a.flex_1, a.relative]}>
style={{ <SearchScreenInner
display: showAutocomplete && !fixedParams ? 'flex' : 'none', key={params.lang}
flex: 1, activeTab={activeTab}
}}> setActiveTab={setActiveTab}
query={query}
queryWithParams={queryWithParams}
headerHeight={headerHeight}
focusSearchInput={focusSearchInput}
/>
{showAutocomplete && !fixedParams && (
<Animated.View
entering={native(FadeInUp.duration(200))}
exiting={native(FadeOutDown.duration(150))}
style={[a.absolute, a.inset_0, t.atoms.bg]}
accessibilityViewIsModal
accessibilityRole="list"
aria-modal>
{searchText.length > 0 ? ( {searchText.length > 0 ? (
<AutocompleteResults <AutocompleteResults
isAutocompleteFetching={isAutocompleteFetching} isAutocompleteFetching={isAutocompleteFetching}
@@ -442,21 +457,8 @@ export function SearchScreenShell({
onRemoveProfileClick={deleteProfileHistoryItem} onRemoveProfileClick={deleteProfileHistoryItem}
/> />
)} )}
</View> </Animated.View>
<View )}
style={{
display: showAutocomplete ? 'none' : 'flex',
flex: 1,
}}>
<SearchScreenInner
key={params.lang}
activeTab={activeTab}
setActiveTab={setActiveTab}
query={query}
queryWithParams={queryWithParams}
headerHeight={headerHeight}
focusSearchInput={focusSearchInput}
/>
</View> </View>
</Layout.Screen> </Layout.Screen>
) )