From a2f1db3027eb481c1b15d226ae25f24e50a3b0b9 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 7 Jul 2025 18:27:29 +0300 Subject: [PATCH] let's just ALF the hell out of it --- src/components/forms/SearchInput.tsx | 26 +++++- src/view/shell/desktop/Search.tsx | 127 ++++++++++++++++----------- 2 files changed, 99 insertions(+), 54 deletions(-) diff --git a/src/components/forms/SearchInput.tsx b/src/components/forms/SearchInput.tsx index 9a8c31549b..dca647366a 100644 --- a/src/components/forms/SearchInput.tsx +++ b/src/components/forms/SearchInput.tsx @@ -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 & { * 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( - function SearchInput({value, label, onClearText, ...rest}, ref) { +export const SearchInput = forwardRef( + function SearchInput({value, label, onClearText, onEscape, ...rest}, ref) { const t = useTheme() const {_} = useLingui() const showClear = value && value.length > 0 + const onKeyPress = ( + evt: NativeSyntheticEvent, + ) => { + if (evt.nativeEvent.key === 'Escape') { + onEscape?.() + } + } + return ( @@ -42,6 +59,7 @@ export const SearchInput = React.forwardRef( autoCorrect={false} autoComplete="off" autoCapitalize="none" + onKeyPress={onKeyPress} style={[ showClear ? { diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 937b5132bc..93d753cd05 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -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() - const [isActive, setIsActive] = useState(false) + + const searchInputRef = useRef(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 ( - + setIsFocused(true)} + onBlur={() => setIsFocused(false)} /> - {deferredQuery !== '' && isActive && moderationOpts && ( - - + 0 || isFetching) && a.border_b, - ]} - /> - {isFetching && !autocompleteData?.length ? ( - - - - ) : ( - autocompleteData?.map(item => ( - - )) - )} + 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 ? ( + + + + Start typing to search + + + ) : ( + <> + 0 || isFetching) && + a.border_b, + ]} + /> + {isFetching && !autocompleteData?.length ? ( + + + + ) : ( + autocompleteData?.map(item => ( + + )) + )} + + )} + )}