kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Create TagInput component
rodzic
9610447a79
commit
c067dd7547
|
@ -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<ITagInput> = ({ 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 (
|
||||
<div className='mt-1 relative shadow-sm'>
|
||||
<HStack
|
||||
className='p-2 pb-0 text-gray-900 dark:text-gray-100 placeholder:text-gray-600 dark:placeholder:text-gray-600 block w-full sm:text-sm dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500 rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800'
|
||||
space={2}
|
||||
wrap
|
||||
>
|
||||
{tags.map((tag, i) => (
|
||||
<div className='mb-2'>
|
||||
<Tag tag={tag} onDelete={handleTagDelete} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
<input
|
||||
className='p-1 mb-2 h-8 flex-grow bg-transparent outline-none'
|
||||
value={input}
|
||||
placeholder={placeholder}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</HStack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagInput;
|
|
@ -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<ITag> = ({ tag, onDelete }) => {
|
||||
return (
|
||||
<div className='inline-flex p-1 rounded bg-primary-500 items-center whitespace-nowrap'>
|
||||
<Text theme='white'>{tag}</Text>
|
||||
|
||||
<IconButton
|
||||
iconClassName='w-4 h-4'
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
onClick={() => onDelete(tag)}
|
||||
transparent
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tag;
|
Ładowanie…
Reference in New Issue