Revise autocomplete usage

This commit is contained in:
Eric Bailey
2024-10-22 16:09:36 -05:00
parent 505c2f2f9d
commit 830b560b89
2 changed files with 30 additions and 53 deletions
@@ -12,10 +12,9 @@ import {TextInputFocusEventData} from 'react-native'
import {Pin} from 'pind' import {Pin} from 'pind'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session' import {useTagAutocomplete} from '#/view/com/composer/text-input/tagsAutocompleteState'
import {tagAutocompleteModel} from '#/view/com/composer/text-input/tagsAutocompleteState'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonProps,ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonProps, ButtonText} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -57,32 +56,23 @@ export function OutlineTags({
onChangeTags: (tags: string[]) => void onChangeTags: (tags: string[]) => void
}) { }) {
const t = useTheme() const t = useTheme()
const {currentAccount} = useSession()
const dropdown = React.useRef<HTMLDivElement>(null) const dropdown = React.useRef<HTMLDivElement>(null)
const input = React.useRef<HTMLInputElement>(null) const input = React.useRef<HTMLInputElement>(null)
const inputWidth = input.current const inputWidth = input.current
? input.current.getBoundingClientRect().width ? input.current.getBoundingClientRect().width
: 200 : 200
const model = React.useMemo( const {query, suggestions, setQuery, saveRecentTag} = useTagAutocomplete()
() => tagAutocompleteModel({currentDid: currentAccount?.did!}),
[currentAccount],
)
const containerRef = React.useRef<HTMLDivElement>(null) const containerRef = React.useRef<HTMLDivElement>(null)
const [value, setValue] = React.useState('')
const [tags, setTags] = React.useState<string[]>(initialTags) const [tags, setTags] = React.useState<string[]>(initialTags)
const [dropdownItems, setDropdownItems] = React.useState<
{value: string; label: string}[]
>([])
const [selectedItemIndex, setSelectedItemIndex] = React.useState(0) const [selectedItemIndex, setSelectedItemIndex] = React.useState(0)
const dropdownIsActive = Boolean(value.length) const dropdownIsActive = Boolean(query.length)
const closeDropdownAndReset = React.useCallback(() => { const closeDropdownAndReset = React.useCallback(() => {
setValue('') setQuery('')
setSelectedItemIndex(0) setSelectedItemIndex(0)
setDropdownItems([]) }, [setQuery, setSelectedItemIndex])
}, [setSelectedItemIndex, setDropdownItems])
const addTags = React.useCallback( const addTags = React.useCallback(
(_tags: string[]) => { (_tags: string[]) => {
@@ -109,29 +99,29 @@ export function OutlineTags({
addTags(Array.from(new Set([...tags, tag])).slice(0, max)) addTags(Array.from(new Set([...tags, tag])).slice(0, max))
} }
model.save(tag) saveRecentTag(tag)
setValue('') setQuery('')
input.current?.focus() input.current?.focus()
closeDropdownAndReset() closeDropdownAndReset()
}, },
[max, tags, closeDropdownAndReset, setValue, addTags, model], [max, tags, closeDropdownAndReset, setQuery, addTags, saveRecentTag],
) )
const onSubmitEditing = React.useCallback(() => { const onSubmitEditing = React.useCallback(() => {
const item = dropdownItems[selectedItemIndex] const item = suggestions[selectedItemIndex]
addTagAndReset(item?.value || value) addTagAndReset(item?.value || query)
}, [value, dropdownItems, selectedItemIndex, addTagAndReset]) }, [query, suggestions, selectedItemIndex, addTagAndReset])
const onKeyPress = React.useCallback( const onKeyPress = React.useCallback(
(e: NativeSyntheticEvent<TextInputKeyPressEventData>) => { (e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
const {key} = e.nativeEvent const {key} = e.nativeEvent
if (key === 'Backspace' && value === '') { if (key === 'Backspace' && query === '') {
addTags(tags.slice(0, -1)) addTags(tags.slice(0, -1))
closeDropdownAndReset() closeDropdownAndReset()
} else if (key === ' ') { } else if (key === ' ') {
e.preventDefault() // prevents an additional space on web e.preventDefault() // prevents an additional space on web
addTagAndReset(value) addTagAndReset(query)
} }
if (dropdownIsActive) { if (dropdownIsActive) {
@@ -140,12 +130,11 @@ export function OutlineTags({
} else if (key === 'ArrowUp') { } else if (key === 'ArrowUp') {
e.preventDefault() e.preventDefault()
setSelectedItemIndex( setSelectedItemIndex(
(selectedItemIndex + dropdownItems.length - 1) % (selectedItemIndex + suggestions.length - 1) % suggestions.length,
dropdownItems.length,
) )
} else if (key === 'ArrowDown') { } else if (key === 'ArrowDown') {
e.preventDefault() e.preventDefault()
setSelectedItemIndex((selectedItemIndex + 1) % dropdownItems.length) setSelectedItemIndex((selectedItemIndex + 1) % suggestions.length)
} else if ( } else if (
isWeb && isWeb &&
key === 'Tab' && key === 'Tab' &&
@@ -158,11 +147,11 @@ export function OutlineTags({
} }
}, },
[ [
value, query,
tags, tags,
dropdownIsActive, dropdownIsActive,
selectedItemIndex, selectedItemIndex,
dropdownItems.length, suggestions.length,
closeDropdownAndReset, closeDropdownAndReset,
setSelectedItemIndex, setSelectedItemIndex,
addTags, addTags,
@@ -171,32 +160,19 @@ export function OutlineTags({
], ],
) )
const search = React.useCallback(
async (value: string) => {
await model.search(value)
setDropdownItems(
model.suggestions.map(s => ({
value: s.value,
label: s.value,
})),
)
},
[model, setDropdownItems],
)
const onChangeText = React.useCallback( const onChangeText = React.useCallback(
async (value: string) => { async (value: string) => {
const tag = sanitizeHashtagOnChange(value) const tag = sanitizeHashtagOnChange(value)
setValue(tag) setQuery(tag)
if (tag.length) { if (tag.length) {
search(tag) setQuery(tag)
} else { } else {
closeDropdownAndReset() setSelectedItemIndex(0)
} }
}, },
[setValue, closeDropdownAndReset, search], [setSelectedItemIndex, setQuery],
) )
const onBlur = React.useCallback( const onBlur = React.useCallback(
@@ -208,10 +184,10 @@ export function OutlineTags({
!tags.length && !tags.length &&
(!target || !target.id.includes('tag_autocomplete_option')) (!target || !target.id.includes('tag_autocomplete_option'))
) { ) {
setValue('') setQuery('')
} }
}, },
[tags, setValue], [tags, setQuery],
) )
React.useEffect(() => { React.useEffect(() => {
@@ -257,7 +233,7 @@ export function OutlineTags({
aria-controls="tags-autocomplete-dropdown" aria-controls="tags-autocomplete-dropdown"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-expanded={dropdownIsActive} aria-expanded={dropdownIsActive}
value={value} value={query}
onBlur={onBlur} onBlur={onBlur}
onKeyPress={onKeyPress} onKeyPress={onKeyPress}
onSubmitEditing={onSubmitEditing} onSubmitEditing={onSubmitEditing}
@@ -283,7 +259,7 @@ export function OutlineTags({
</View> </View>
<Pin <Pin
pinned={Boolean(value.length)} pinned={Boolean(query.length)}
to={input} to={input}
at="bottomLeft" at="bottomLeft"
from="topLeft" from="topLeft"
@@ -293,9 +269,9 @@ export function OutlineTags({
style={[t.atoms.bg, t.atoms.border_contrast_low, styles.dropdown]} style={[t.atoms.bg, t.atoms.border_contrast_low, styles.dropdown]}
role={'listbox' as any} role={'listbox' as any}
id="tags-autocomplete-dropdown"> id="tags-autocomplete-dropdown">
{dropdownItems.map((item, index) => { {suggestions.map((item, index) => {
const isFirst = index === 0 const isFirst = index === 0
const isLast = index === dropdownItems.length - 1 const isLast = index === suggestions.length - 1
return ( return (
<Pressable <Pressable
id={`tag_autocomplete_option_${item.value}`} id={`tag_autocomplete_option_${item.value}`}
@@ -319,7 +295,7 @@ export function OutlineTags({
? styles.lastResult ? styles.lastResult
: undefined, : undefined,
]}> ]}>
<Text numberOfLines={1}>{item.label}</Text> <Text numberOfLines={1}>{item.value}</Text>
</Pressable> </Pressable>
) )
})} })}
@@ -66,6 +66,7 @@ export function useTagAutocomplete() {
}, [currentAccount, query, searchSuggestions]) }, [currentAccount, query, searchSuggestions])
return { return {
query,
suggestions, suggestions,
setQuery: onSetQuery, setQuery: onSetQuery,
saveRecentTag, saveRecentTag,