defer and ALF sidebar search

This commit is contained in:
Samuel Newman
2025-07-07 18:06:58 +03:00
parent f80479744e
commit 9eb2f88651
+58 -91
View File
@@ -1,8 +1,8 @@
import {memo, useCallback, useDeferredValue, useState} from 'react'
import React from 'react' import React from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
StyleSheet, type StyleProp,
TouchableOpacity,
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
@@ -10,95 +10,76 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {Link} from '#/view/com/util/Link'
import {Text} from '#/view/com/util/text/Text'
import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard' import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard'
import {atoms as a} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {SearchInput} from '#/components/forms/SearchInput' import {SearchInput} from '#/components/forms/SearchInput'
import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
let SearchLinkCard = ({ let SearchLinkCard = ({
label, label,
to, to,
onPress,
style, style,
}: { }: {
label: string label: string
to?: string to: LinkProps['to']
onPress?: () => void style?: StyleProp<ViewStyle>
style?: ViewStyle
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const t = useTheme()
const inner = (
<View
style={[pal.border, {paddingVertical: 16, paddingHorizontal: 12}, style]}>
<Text type="md" style={[pal.text]}>
{label}
</Text>
</View>
)
if (onPress) {
return (
<TouchableOpacity
onPress={onPress}
accessibilityLabel={label}
accessibilityHint="">
{inner}
</TouchableOpacity>
)
}
return ( return (
<Link href={to} asAnchor anchorNoUnderline> <Link to={to} label={label}>
<View {({focused, hovered, pressed}) => (
style={[ <View
pal.border, style={[
{paddingVertical: 16, paddingHorizontal: 12}, a.w_full,
style, t.atoms.border_contrast_low,
]}> a.py_lg,
<Text type="md" style={[pal.text]}> a.px_md,
{label} (focused || hovered || pressed) && t.atoms.bg_contrast_25,
</Text> style,
</View> ]}>
<Text style={[a.text_sm, a.leading_snug]}>{label}</Text>
</View>
)}
</Link> </Link>
) )
} }
SearchLinkCard = React.memo(SearchLinkCard) SearchLinkCard = memo(SearchLinkCard)
export {SearchLinkCard} export {SearchLinkCard}
export function DesktopSearch() { export function DesktopSearch() {
const {_} = useLingui() const {_} = useLingui()
const pal = usePalette('default') const t = useTheme()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const [isActive, setIsActive] = React.useState<boolean>(false) const [isActive, setIsActive] = useState(false)
const [query, setQuery] = React.useState<string>('') const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const {data: autocompleteData, isFetching} = useActorAutocompleteQuery( const {data: autocompleteData, isFetching} = useActorAutocompleteQuery(
query, deferredQuery,
true, true,
) )
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const onChangeText = React.useCallback((text: string) => { const onChangeText = useCallback((text: string) => {
setQuery(text) setQuery(text)
setIsActive(text.length > 0) setIsActive(text.length > 0)
}, []) }, [])
const onPressCancelSearch = React.useCallback(() => { const onPressCancelSearch = useCallback(() => {
setQuery('') setQuery('')
setIsActive(false) setIsActive(false)
}, [setQuery]) }, [setQuery])
const onSubmit = React.useCallback(() => { const onSubmit = useCallback(() => {
setIsActive(false) setIsActive(false)
if (!query.length) return if (!deferredQuery.length) return
navigation.dispatch(StackActions.push('Search', {q: query})) navigation.dispatch(StackActions.push('Search', {q: deferredQuery}))
}, [query, navigation]) }, [deferredQuery, navigation])
const onSearchProfileCardPress = React.useCallback(() => { const onSearchProfileCardPress = React.useCallback(() => {
setQuery('') setQuery('')
@@ -106,62 +87,48 @@ export function DesktopSearch() {
}, []) }, [])
return ( return (
<View style={[styles.container, pal.view]}> <View style={[a.w_full]}>
<SearchInput <SearchInput
value={query} value={query}
onChangeText={onChangeText} onChangeText={onChangeText}
onClearText={onPressCancelSearch} onClearText={onPressCancelSearch}
onSubmitEditing={onSubmit} onSubmitEditing={onSubmit}
/> />
{query !== '' && isActive && moderationOpts && ( {deferredQuery !== '' && isActive && moderationOpts && (
<View <View
style={[ style={[
pal.view, t.atoms.bg,
pal.borderDark, t.atoms.border_contrast_low,
styles.resultsContainer, a.mt_sm,
a.w_full,
a.border,
a.rounded_sm,
a.overflow_hidden, a.overflow_hidden,
a.absolute,
]}> ]}>
<SearchLinkCard
label={_(msg`Search for "${deferredQuery}"`)}
to={{screen: 'Search', params: {q: deferredQuery}}}
style={[
((autocompleteData?.length ?? 0) > 0 || isFetching) && a.border_b,
]}
/>
{isFetching && !autocompleteData?.length ? ( {isFetching && !autocompleteData?.length ? (
<View style={{padding: 8}}> <View style={[a.p_md]}>
<ActivityIndicator /> <ActivityIndicator />
</View> </View>
) : ( ) : (
<> autocompleteData?.map(item => (
<SearchLinkCard <SearchProfileCard
label={_(msg`Search for "${query}"`)} key={item.did}
to={`/search?q=${encodeURIComponent(query)}`} profile={item}
style={ moderationOpts={moderationOpts}
(autocompleteData?.length ?? 0) > 0 onPress={onSearchProfileCardPress}
? {borderBottomWidth: 1}
: undefined
}
/> />
{autocompleteData?.map(item => ( ))
<SearchProfileCard
key={item.did}
profile={item}
moderationOpts={moderationOpts}
onPress={onSearchProfileCardPress}
/>
))}
</>
)} )}
</View> </View>
)} )}
</View> </View>
) )
} }
const styles = StyleSheet.create({
container: {
position: 'relative',
width: '100%',
},
resultsContainer: {
marginTop: 10,
flexDirection: 'column',
width: '100%',
borderWidth: 1,
borderRadius: 6,
},
})