let's just ALF the hell out of it

This commit is contained in:
Samuel Newman
2025-07-07 18:27:29 +03:00
parent 9eb2f88651
commit a2f1db3027
2 changed files with 99 additions and 54 deletions
+22 -4
View File
@@ -1,5 +1,10 @@
import React from 'react'
import {type TextInput, View} from 'react-native'
import {forwardRef} from 'react'
import {
type NativeSyntheticEvent,
type TextInput,
type TextInputKeyPressEventData,
View,
} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -17,14 +22,26 @@ type SearchInputProps = Omit<TextField.InputProps, 'label'> & {
* Called when the user presses the (X) button
*/
onClearText?: () => void
/**
* Called when the user presses the Escape key
*/
onEscape?: () => void
}
export const SearchInput = React.forwardRef<TextInput, SearchInputProps>(
function SearchInput({value, label, onClearText, ...rest}, ref) {
export const SearchInput = forwardRef<TextInput, SearchInputProps>(
function SearchInput({value, label, onClearText, onEscape, ...rest}, ref) {
const t = useTheme()
const {_} = useLingui()
const showClear = value && value.length > 0
const onKeyPress = (
evt: NativeSyntheticEvent<TextInputKeyPressEventData>,
) => {
if (evt.nativeEvent.key === 'Escape') {
onEscape?.()
}
}
return (
<View style={[a.w_full, a.relative]}>
<TextField.Root>
@@ -42,6 +59,7 @@ export const SearchInput = React.forwardRef<TextInput, SearchInputProps>(
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
onKeyPress={onKeyPress}
style={[
showClear
? {
+77 -50
View File
@@ -1,14 +1,15 @@
import {memo, useCallback, useDeferredValue, useState} from 'react'
import React from 'react'
import {memo, useDeferredValue, useRef, useState} from 'react'
import {
ActivityIndicator,
type StyleProp,
type TextInput,
View,
type ViewStyle,
} from 'react-native'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native'
import type React from 'react'
import {type NavigationProp} from '#/lib/routes/types'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -16,6 +17,7 @@ import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard'
import {atoms as a, useTheme} from '#/alf'
import {SearchInput} from '#/components/forms/SearchInput'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
@@ -55,7 +57,9 @@ export function DesktopSearch() {
const {_} = useLingui()
const t = useTheme()
const navigation = useNavigation<NavigationProp>()
const [isActive, setIsActive] = useState(false)
const searchInputRef = useRef<TextInput>(null)
const [isFocused, setIsFocused] = useState(false)
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const {data: autocompleteData, isFetching} = useActorAutocompleteQuery(
@@ -65,68 +69,91 @@ export function DesktopSearch() {
const moderationOpts = useModerationOpts()
const onChangeText = useCallback((text: string) => {
const onChangeText = (text: string) => {
setQuery(text)
setIsActive(text.length > 0)
}, [])
}
const onPressCancelSearch = useCallback(() => {
const onPressCancelSearch = () => {
setQuery('')
setIsActive(false)
}, [setQuery])
}
const onSubmit = useCallback(() => {
setIsActive(false)
const onEscape = () => {
setQuery('')
searchInputRef.current?.blur()
}
const onSubmit = () => {
if (!deferredQuery.length) return
navigation.dispatch(StackActions.push('Search', {q: deferredQuery}))
}, [deferredQuery, navigation])
const onSearchProfileCardPress = React.useCallback(() => {
setQuery('')
setIsActive(false)
}, [])
}
const onSearchProfileCardPress = () => {
setQuery('')
}
return (
<View style={[a.w_full]}>
<View style={[a.w_full, a.z_10]}>
<SearchInput
ref={searchInputRef}
value={query}
onChangeText={onChangeText}
onClearText={onPressCancelSearch}
onEscape={onEscape}
onSubmitEditing={onSubmit}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
{deferredQuery !== '' && isActive && moderationOpts && (
<View
style={[
t.atoms.bg,
t.atoms.border_contrast_low,
a.mt_sm,
a.w_full,
a.border,
a.rounded_sm,
a.overflow_hidden,
a.absolute,
]}>
<SearchLinkCard
label={_(msg`Search for "${deferredQuery}"`)}
to={{screen: 'Search', params: {q: deferredQuery}}}
{(deferredQuery !== '' || isFocused) && moderationOpts && (
<View style={[a.w_full]}>
<View
style={[
((autocompleteData?.length ?? 0) > 0 || isFetching) && a.border_b,
]}
/>
{isFetching && !autocompleteData?.length ? (
<View style={[a.p_md]}>
<ActivityIndicator />
</View>
) : (
autocompleteData?.map(item => (
<SearchProfileCard
key={item.did}
profile={item}
moderationOpts={moderationOpts}
onPress={onSearchProfileCardPress}
/>
))
)}
t.atoms.bg,
t.atoms.border_contrast_low,
a.w_full,
a.border,
a.mt_sm,
a.rounded_sm,
a.overflow_hidden,
a.absolute,
a.shadow_lg,
a.zoom_fade_in,
]}>
{deferredQuery.length === 0 ? (
<View style={[a.py_xl, a.gap_sm, a.align_center]}>
<SearchIcon size="2xl" style={[t.atoms.text_contrast_low]} />
<Text
style={[a.text_sm, t.atoms.text_contrast_low, a.text_center]}>
<Trans>Start typing to search</Trans>
</Text>
</View>
) : (
<>
<SearchLinkCard
label={_(msg`Search for "${deferredQuery}"`)}
to={{screen: 'Search', params: {q: deferredQuery}}}
style={[
((autocompleteData?.length ?? 0) > 0 || isFetching) &&
a.border_b,
]}
/>
{isFetching && !autocompleteData?.length ? (
<View style={[a.p_md]}>
<ActivityIndicator />
</View>
) : (
autocompleteData?.map(item => (
<SearchProfileCard
key={item.did}
profile={item}
moderationOpts={moderationOpts}
onPress={onSearchProfileCardPress}
/>
))
)}
</>
)}
</View>
</View>
)}
</View>