Tags Input

Tags Input Components like github and youtube

Current Tags:

Tags WithEdit

Current Tags:

Usages

1
'use client';
2
import React, { useState, useRef, useEffect } from 'react';
3
4
interface TagsInputProps {
5
tags: string[];
6
setTags: React.Dispatch<React.SetStateAction<string[]>>;
7
editTag?: boolean;
8
}
9
10
export const TagsInput: React.FC<TagsInputProps> = ({
11
tags,
12
setTags,
13
editTag = true,
14
}) => {
15
const [input, setInput] = useState('');
16
const [editingIndex, setEditingIndex] = useState<number | null>(null);
17
const editInputRef = useRef<HTMLInputElement>(null);
18
19
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
20
const trimmedInput = input.trim();
21
22
if ((e.key === 'Enter' || e.key === ',') && trimmedInput) {
23
e.preventDefault();
24
if (editingIndex !== null) {
25
const updatedTags = [...tags];
26
updatedTags[editingIndex] = trimmedInput;
27
setTags(updatedTags);
28
setEditingIndex(null);
29
} else if (!tags.includes(trimmedInput)) {
30
setTags([...tags, trimmedInput]);
31
}
32
setInput('');
33
}
34
};
35
36
const handleRemoveTag = (tag: string) => {
37
setTags(tags.filter((t) => t !== tag));
38
if (editingIndex !== null) {
39
setEditingIndex(null);
40
}
41
};
42
43
const handleEditTag = (index: number) => {
44
if (editTag) {
45
setInput(tags[index]);
46
setEditingIndex(index);
47
setTimeout(() => editInputRef.current?.focus(), 0); // Focus on edit input
48
}
49
};
50
51
const handleBlur = () => {
52
if (editingIndex !== null) {
53
const updatedTags = [...tags];
54
const trimmedInput = input.trim();
55
if (trimmedInput) {
56
updatedTags[editingIndex] = trimmedInput;
57
} else {
58
updatedTags.splice(editingIndex, 1);
59
}
60
setTags(updatedTags);
61
setEditingIndex(null);
62
}
63
setInput('');
64
};
65
66
useEffect(() => {
67
// Resize the input width based on text content
68
if (editInputRef.current) {
69
editInputRef.current.style.width = `${input.length + 1}ch`;
70
}
71
}, [input]);
72
73
return (
74
<div className='flex flex-wrap items-center gap-2 p-2 border-2 rounded-md focus-within:border-blue-500 bg-background'>
75
{tags.map((tag, index) => (
76
<div key={tag} className='relative'>
77
{editTag && editingIndex === index ? (
78
<input
79
ref={editInputRef}
80
type='text'
81
value={input}
82
onChange={(e) => setInput(e.target.value)}
83
onKeyDown={handleAddTag}
84
onBlur={handleBlur}
85
className='px-2 py-1 text-sm border rounded outline-none'
86
placeholder='Edit tag...'
87
style={{ width: `${input.length + 1 * 1.2}px` }}
88
autoFocus
89
/>
90
) : (
91
<span
92
onClick={() => handleEditTag(index)}
93
className='flex items-center gap-2 px-1 pl-2 py-1 text-sm font-medium text-white bg-blue-500 rounded cursor-pointer hover:bg-blue-600'
94
>
95
{tag}
96
<button
97
onClick={(e) => {
98
e.stopPropagation();
99
handleRemoveTag(tag);
100
}}
101
className='text-white hover:text-gray-300 px-1 focus:outline-none bg-background rounded'
102
>
103
&times;
104
</button>
105
</span>
106
)}
107
</div>
108
))}
109
<input
110
type='text'
111
value={input}
112
onChange={(e) => setInput(e.target.value)}
113
onKeyDown={handleAddTag}
114
className={`flex-grow px-2 py-1 text-sm border-none outline-none bg-background rounded-md ${
115
editingIndex !== null ? 'opacity-0' : 'opacity-100'
116
}`}
117
placeholder='Add a tag...'
118
/>
119
</div>
120
);
121
};

Props

PropTypeDefaultDescription
tagsstring[][]Array of strings representing the tags to display in the input.
setTagsReact.Dispatch<React.SetStateAction<string[]>>-Function to update the tags array, typically used with a useState hook.
editTagbooleantrueOptional flag that enables or disables the ability to edit tags in the input compone