From c067dd7547a6ca7ed7f2a7766f6e24221ca1aaf8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 11 Sep 2022 15:47:50 -0500 Subject: [PATCH] Create TagInput component --- .../components/ui/tag-input/tag-input.tsx | 70 +++++++++++++++++++ app/soapbox/components/ui/tag-input/tag.tsx | 29 ++++++++ 2 files changed, 99 insertions(+) create mode 100644 app/soapbox/components/ui/tag-input/tag-input.tsx create mode 100644 app/soapbox/components/ui/tag-input/tag.tsx diff --git a/app/soapbox/components/ui/tag-input/tag-input.tsx b/app/soapbox/components/ui/tag-input/tag-input.tsx new file mode 100644 index 000000000..b692e82e9 --- /dev/null +++ b/app/soapbox/components/ui/tag-input/tag-input.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; + +import HStack from '../hstack/hstack'; + +import Tag from './tag'; + +interface ITagInput { + tags: string[], + onChange: (tags: string[]) => void, + placeholder?: string, +} + +/** Manage a list of tags. */ +// https://blog.logrocket.com/building-a-tag-input-field-component-for-react/ +const TagInput: React.FC = ({ tags, onChange, placeholder }) => { + const [input, setInput] = useState(''); + + const handleTagDelete = (tag: string) => { + onChange(tags.filter(item => item !== tag)); + }; + + const handleKeyDown: React.KeyboardEventHandler = (e) => { + const { key } = e; + const trimmedInput = input.trim(); + + if (key === 'Tab') { + e.preventDefault(); + } + + if ([',', 'Tab', 'Enter'].includes(key) && trimmedInput.length && !tags.includes(trimmedInput)) { + e.preventDefault(); + onChange([...tags, trimmedInput]); + setInput(''); + } + + if (key === 'Backspace' && !input.length && tags.length) { + e.preventDefault(); + const tagsCopy = [...tags]; + tagsCopy.pop(); + + onChange(tagsCopy); + } + }; + + return ( +
+ + {tags.map((tag, i) => ( +
+ +
+ ))} + + setInput(e.target.value)} + onKeyDown={handleKeyDown} + /> +
+
+ ); +}; + +export default TagInput; \ No newline at end of file diff --git a/app/soapbox/components/ui/tag-input/tag.tsx b/app/soapbox/components/ui/tag-input/tag.tsx new file mode 100644 index 000000000..d47b1d73b --- /dev/null +++ b/app/soapbox/components/ui/tag-input/tag.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import IconButton from '../icon-button/icon-button'; +import Text from '../text/text'; + +interface ITag { + /** Name of the tag. */ + tag: string, + /** Callback when the X icon is pressed. */ + onDelete: (tag: string) => void, +} + +/** A single editable Tag (used by TagInput). */ +const Tag: React.FC = ({ tag, onDelete }) => { + return ( +
+ {tag} + + onDelete(tag)} + transparent + /> +
+ ); +}; + +export default Tag; \ No newline at end of file