first pass at a tag input in composer

This commit is contained in:
Eric Bailey
2023-09-25 10:37:53 -05:00
parent b030d94a64
commit 47273cccb1
3 changed files with 140 additions and 0 deletions
+9
View File
@@ -48,6 +48,7 @@ import {LabelsBtn} from './labels/LabelsBtn'
import {SelectLangBtn} from './select-language/SelectLangBtn'
import {EmojiPickerButton} from './text-input/web/EmojiPicker.web'
import {insertMentionAt} from 'lib/strings/mention-manip'
import {TagInput} from './TagInput'
type Props = ComposerOpts & {
onClose: () => void
@@ -380,6 +381,14 @@ export const ComposePost = observer(function ComposePost({
<QuoteEmbed quote={quote} />
</View>
) : undefined}
<View
style={[
pal.border,
{borderTopWidth: 1, paddingVertical: 10, marginTop: 10},
]}>
<TagInput max={8} />
</View>
</ScrollView>
{!extLink && suggestedLinks.size > 0 ? (
<View style={s.mb5}>
+129
View File
@@ -0,0 +1,129 @@
import React from 'react'
import {
TextInput,
View,
StyleSheet,
NativeSyntheticEvent,
TextInputKeyPressEventData,
Pressable,
} from 'react-native'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from 'view/com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
function Tag({
tag,
removeTag,
}: {
tag: string
removeTag: (tag: string) => void
}) {
const pal = usePalette('default')
return (
<Pressable
accessibilityRole="button"
onPress={() => removeTag(tag)}
style={state => ({
opacity: state.hovered || state.pressed || state.focused ? 0.8 : 1,
outline: 0,
})}>
<Text type="xs-medium" style={[styles.tag, pal.viewLight, pal.textLight]}>
#{tag}
</Text>
</Pressable>
)
}
export function TagInput({max}: {max: number}) {
const pal = usePalette('default')
const input = React.useRef<TextInput>(null)
const [value, setValue] = React.useState('')
const [tags, setTags] = React.useState<string[]>([])
const onKeyPress = React.useCallback(
(e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
if (e.nativeEvent.key === 'Enter') {
const _tags = value.trim().split(' ').filter(Boolean)
if (_tags.length > 0) {
setTags(Array.from(new Set([...tags, ..._tags])).slice(0, max))
setValue('')
}
setTimeout(() => {
input.current?.focus()
}, 100)
} else if (e.nativeEvent.key === 'Backspace' && value === '') {
setTags(tags.slice(0, -1))
}
},
[max, value, tags, setValue, setTags],
)
const onChangeText = React.useCallback((value: string) => {
const sanitized = value.replace(/^#/, '')
setValue(sanitized)
}, [])
const removeTag = React.useCallback(
(tag: string) => {
setTags(tags.filter(t => t !== tag))
},
[tags, setTags],
)
return (
<View style={styles.outer}>
{!tags.length && (
<FontAwesomeIcon
icon="tags"
size={14}
style={pal.textLight as FontAwesomeIconStyle}
/>
)}
{tags.map(tag => (
<Tag key={tag} tag={tag} removeTag={removeTag} />
))}
{tags.length >= max ? null : (
<TextInput
ref={input}
value={value}
onKeyPress={onKeyPress}
onChangeText={onChangeText}
style={[styles.input, pal.textLight]}
placeholder="Add tags..."
autoCapitalize="none"
autoCorrect={false}
autoComplete="off"
accessible={true}
accessibilityLabel="Add tags to your post"
accessibilityHint={`You may add up to 8 tags to your post, including those used inline.`}
/>
)}
</View>
)
}
const styles = StyleSheet.create({
outer: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
gap: 8,
},
input: {
flexGrow: 1,
minWidth: 100,
fontSize: 13,
paddingVertical: 4,
},
tag: {
paddingVertical: 4,
paddingHorizontal: 8,
borderRadius: 4,
},
})
+2
View File
@@ -98,6 +98,7 @@ import {faUsersSlash} from '@fortawesome/free-solid-svg-icons/faUsersSlash'
import {faX} from '@fortawesome/free-solid-svg-icons/faX'
import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark'
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'
import {faTags} from '@fortawesome/free-solid-svg-icons/faTags'
export function setup() {
library.add(
@@ -199,5 +200,6 @@ export function setup() {
faX,
faXmark,
faChevronDown,
faTags,
)
}