Wire autocomplete into GifPickerHeader

Adds `autocomplete` prop (GifAutocompleteState) to GifPickerHeader,
restructures layout so suggestions render below the search bar, and
wires up keyboard navigation + ARIA attributes for the combobox pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vineyardbovines
2026-04-14 14:19:23 -04:00
parent a5766e8426
commit aa576a1b28
@@ -9,34 +9,33 @@ import * as TextField from '#/components/forms/TextField'
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {
GIF_AUTOCOMPLETE_LISTBOX_ID,
GifAutocompleteSuggestions,
suggestionItemId,
} from '#/features/gifPicker/components/GifAutocompleteSuggestions'
import {type GifAutocompleteState} from '#/features/gifPicker/hooks/useGifAutocomplete'
export function GifPickerHeader({ export function GifPickerHeader({
inputRef, inputRef,
onChangeText, onChangeText,
onClose, onClose,
onEscape, onEscape,
autocomplete,
}: { }: {
inputRef: Ref<TextInput> inputRef: Ref<TextInput>
onChangeText: (text: string) => void onChangeText: (text: string) => void
onClose: () => void onClose: () => void
onEscape: () => void onEscape: () => void
autocomplete: GifAutocompleteState
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
return ( return (
<View <View style={[native(a.pt_4xl), a.relative, a.mb_lg, a.pb_sm, t.atoms.bg]}>
style={[ <View style={[a.flex_row, a.align_center, !gtMobile && web(a.gap_md)]}>
native(a.pt_4xl),
a.relative,
a.mb_lg,
a.flex_row,
a.align_center,
!gtMobile && web(a.gap_md),
a.pb_sm,
t.atoms.bg,
]}>
{!gtMobile && IS_WEB && ( {!gtMobile && IS_WEB && (
<Button <Button
size="small" size="small"
@@ -61,13 +60,36 @@ export function GifPickerHeader({
maxLength={50} maxLength={50}
onKeyPress={({nativeEvent}) => { onKeyPress={({nativeEvent}) => {
if (nativeEvent.key === 'Escape') { if (nativeEvent.key === 'Escape') {
if (!autocomplete.handleKeyDown('Escape')) {
onEscape() onEscape()
} }
} else {
autocomplete.handleKeyDown(nativeEvent.key)
}
}} }}
// @ts-ignore web-only ARIA props
role={autocomplete.isVisible ? 'combobox' : undefined}
aria-controls={
autocomplete.isVisible ? GIF_AUTOCOMPLETE_LISTBOX_ID : undefined
}
aria-expanded={autocomplete.isVisible}
aria-autocomplete={autocomplete.isVisible ? 'list' : undefined}
aria-activedescendant={
autocomplete.isVisible && autocomplete.activeIndex >= 0
? suggestionItemId(autocomplete.activeIndex)
: undefined
}
/> />
</TextField.Root> </TextField.Root>
</View>
{/* future: tabs (Trending / Recents / Categories) render here */} {autocomplete.isVisible && (
<GifAutocompleteSuggestions
suggestions={autocomplete.suggestions}
activeIndex={autocomplete.activeIndex}
onSelect={autocomplete.selectSuggestion}
/>
)}
</View> </View>
) )
} }