From 47273cccb1b915cc18119d50cfa4531898efdd84 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 25 Sep 2023 10:37:53 -0500 Subject: [PATCH] first pass at a tag input in composer --- src/view/com/composer/Composer.tsx | 9 ++ src/view/com/composer/TagInput.tsx | 129 +++++++++++++++++++++++++++++ src/view/index.ts | 2 + 3 files changed, 140 insertions(+) create mode 100644 src/view/com/composer/TagInput.tsx diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 8629c4fcb3..e0d2858d06 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -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({ ) : undefined} + + + + {!extLink && suggestedLinks.size > 0 ? ( diff --git a/src/view/com/composer/TagInput.tsx b/src/view/com/composer/TagInput.tsx new file mode 100644 index 0000000000..ac374a4899 --- /dev/null +++ b/src/view/com/composer/TagInput.tsx @@ -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 ( + removeTag(tag)} + style={state => ({ + opacity: state.hovered || state.pressed || state.focused ? 0.8 : 1, + outline: 0, + })}> + + #{tag} + + + ) +} + +export function TagInput({max}: {max: number}) { + const pal = usePalette('default') + const input = React.useRef(null) + const [value, setValue] = React.useState('') + const [tags, setTags] = React.useState([]) + + const onKeyPress = React.useCallback( + (e: NativeSyntheticEvent) => { + 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 ( + + {!tags.length && ( + + )} + {tags.map(tag => ( + + ))} + {tags.length >= max ? null : ( + + )} + + ) +} + +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, + }, +}) diff --git a/src/view/index.ts b/src/view/index.ts index 1e6f274191..09177d0f75 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -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, ) }