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:
@@ -9,65 +9,87 @@ 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),
|
{!gtMobile && IS_WEB && (
|
||||||
a.relative,
|
<Button
|
||||||
a.mb_lg,
|
size="small"
|
||||||
a.flex_row,
|
variant="ghost"
|
||||||
a.align_center,
|
color="secondary"
|
||||||
!gtMobile && web(a.gap_md),
|
shape="round"
|
||||||
a.pb_sm,
|
onPress={onClose}
|
||||||
t.atoms.bg,
|
label={_(msg`Close GIF dialog`)}>
|
||||||
]}>
|
<ButtonIcon icon={Arrow} size="md" />
|
||||||
{!gtMobile && IS_WEB && (
|
</Button>
|
||||||
<Button
|
)}
|
||||||
size="small"
|
|
||||||
variant="ghost"
|
|
||||||
color="secondary"
|
|
||||||
shape="round"
|
|
||||||
onPress={onClose}
|
|
||||||
label={_(msg`Close GIF dialog`)}>
|
|
||||||
<ButtonIcon icon={Arrow} size="md" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}>
|
<TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}>
|
||||||
<TextField.Icon icon={Search} />
|
<TextField.Icon icon={Search} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
label={_(msg`Search GIFs`)}
|
label={_(msg`Search GIFs`)}
|
||||||
placeholder={_(msg`Search GIFs`)}
|
placeholder={_(msg`Search GIFs`)}
|
||||||
onChangeText={onChangeText}
|
onChangeText={onChangeText}
|
||||||
returnKeyType="search"
|
returnKeyType="search"
|
||||||
clearButtonMode="while-editing"
|
clearButtonMode="while-editing"
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
onKeyPress={({nativeEvent}) => {
|
onKeyPress={({nativeEvent}) => {
|
||||||
if (nativeEvent.key === 'Escape') {
|
if (nativeEvent.key === 'Escape') {
|
||||||
onEscape()
|
if (!autocomplete.handleKeyDown('Escape')) {
|
||||||
|
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}
|
||||||
</TextField.Root>
|
aria-activedescendant={
|
||||||
|
autocomplete.isVisible && autocomplete.activeIndex >= 0
|
||||||
|
? suggestionItemId(autocomplete.activeIndex)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* future: tabs (Trending / Recents / Categories) render here */}
|
{autocomplete.isVisible && (
|
||||||
|
<GifAutocompleteSuggestions
|
||||||
|
suggestions={autocomplete.suggestions}
|
||||||
|
activeIndex={autocomplete.activeIndex}
|
||||||
|
onSelect={autocomplete.selectSuggestion}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user