Tag Field
An input component that allows users to enter, display, and manage multiple tags or keywords dynamically.
An input component that allows users to enter, display, and manage multiple tags or keywords dynamically.
Follow these simple steps to add the Tag Field component to your project:
The tag-field uses the badge and input component**.**
Create a new file: components/ui/tag-field.tsx
and copy the code below:
"use client";
import React, { useState, useRef, useEffect } from "react";
import { Badge } from "./badge";
import { XIcon } from "lucide-react";
import { Input } from "./input";
interface TagsInputProps {
tags: string[];
setTags: React.Dispatch<React.SetStateAction<string[]>>;
editTag?: boolean;
}
export const TagsInput: React.FC<TagsInputProps> = ({
tags,
setTags,
editTag = true,
}) => {
const [input, setInput] = useState("");
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const editInputRef = useRef<HTMLInputElement>(null);
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
const trimmedInput = input.trim();
if ((e.key === "Enter" || e.key === ",") && trimmedInput) {
e.preventDefault();
if (editingIndex !== null) {
const updatedTags = [...tags];
updatedTags[editingIndex] = trimmedInput;
setTags(updatedTags);
setEditingIndex(null);
} else if (!tags.includes(trimmedInput)) {
setTags([...tags, trimmedInput]);
}
setInput("");
}
};
const handleRemoveTag = (tag: string) => {
setTags(tags.filter((t) => t !== tag));
if (editingIndex !== null) {
setEditingIndex(null);
}
};
const handleEditTag = (index: number) => {
if (editTag) {
setInput(tags[index]);
setEditingIndex(index);
setTimeout(() => editInputRef.current?.focus(), 0); // Focus on edit input
}
};
const handleBlur = () => {
if (editingIndex !== null) {
const updatedTags = [...tags];
const trimmedInput = input.trim();
if (trimmedInput) {
updatedTags[editingIndex] = trimmedInput;
} else {
updatedTags.splice(editingIndex, 1);
}
setTags(updatedTags);
setEditingIndex(null);
}
setInput("");
};
useEffect(() => {
// Resize the input width based on text content
if (editInputRef.current) {
editInputRef.current.style.width = `${input.length + 1}ch`;
}
}, [input]);
return (
<div className="bg-background flex items-center gap-2 border dark:border-neutral-700 rounded-md px-2">
{tags.map((tag, index) => (
<div key={tag} className="relative">
{editTag && editingIndex === index ? (
<Input
ref={editInputRef}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleAddTag}
onBlur={handleBlur}
className="w-full rounded border-none px-2 text-sm shadow-none outline-0 transition-none focus:border-none focus:ring-0 focus:outline-0 focus-visible:ring-0"
placeholder="Edit tag..."
style={{ width: `${input.length + 1 * 1.2}px` }}
autoFocus
/>
) : (
<Badge onClick={() => handleEditTag(index)}>
{tag}
<button
className="focus-visible:border-ring focus-visible:ring-ring/50 text-primary-foreground/60 hover:text-primary-foreground -my-px -ms-px -me-1.5 inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-[inherit] p-0 transition-[color,box-shadow] outline-none focus-visible:ring-[3px]"
onClick={(e) => {
e.stopPropagation();
handleRemoveTag(tag);
}}
>
<XIcon size={12} aria-hidden="true" />
</button>
</Badge>
// <span
// onClick={() => handleEditTag(index)}
// className="flex cursor-pointer items-center gap-2 rounded bg-blue-500 px-1 py-1 pl-2 text-sm font-medium text-white hover:bg-blue-600"
// >
// {tag}
// <button
// onClick={(e) => {
// e.stopPropagation();
// handleRemoveTag(tag);
// }}
// className="bg-background rounded px-1 text-white hover:text-gray-300 focus:outline-none"
// >
// ×
// </button>
// </span>
)}
</div>
))}
<Input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleAddTag}
className={`w-full rounded border-none px-2 text-sm shadow-none outline-0 transition-none focus:border-none focus:ring-0 focus:outline-0 focus-visible:ring-0 ${
editingIndex !== null ? "opacity-0" : "opacity-100"
}`}
placeholder="Add a tag..."
/>
</div>
);
};
Adjust the import paths in both files according to your project's structure.
Current Tags: