Revise autocomplete usage
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user