feat: add email tag input functionality to HITL edit panel

- Updated the HITL edit panel to support multiple email inputs using a tag input component.
- Modified the ExtraField type to include "emails" as a valid type.
- Enhanced the Gmail draft creation process to utilize the new email input format for "To", "CC", and "BCC" fields.
This commit is contained in:
Anish Sarkar 2026-03-20 22:46:49 +05:30
parent 282e913c50
commit b4309f13be
5 changed files with 105 additions and 16 deletions

View file

@ -1,8 +1,9 @@
"use client";
import { TagInput, type Tag as TagType } from "emblor";
import { useAtomValue, useSetAtom } from "jotai";
import { XIcon } from "lucide-react";
import { useCallback, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import {
closeHitlEditPanelAtom,
hitlEditPanelAtom,
@ -16,6 +17,86 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useMediaQuery } from "@/hooks/use-media-query";
function parseEmailsToTags(value: string): TagType[] {
if (!value.trim()) return [];
return value
.split(",")
.map((s) => s.trim())
.filter(Boolean)
.map((email, i) => ({ id: `${Date.now()}-${i}`, text: email }));
}
function tagsToEmailString(tags: TagType[]): string {
return tags.map((t) => t.text).join(", ");
}
function EmailsTagField({
id,
value,
onChange,
placeholder,
}: {
id: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
}) {
const [tags, setTags] = useState<TagType[]>(() => parseEmailsToTags(value));
const [activeTagIndex, setActiveTagIndex] = useState<number | null>(null);
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
onChange(tagsToEmailString(tags));
}, [tags, onChange]);
const handleSetTags = useCallback(
(newTags: TagType[] | ((prev: TagType[]) => TagType[])) => {
setTags((prev) =>
typeof newTags === "function" ? newTags(prev) : newTags
);
},
[]
);
const handleAddTag = useCallback(
(text: string) => {
const trimmed = text.trim();
if (!trimmed) return;
if (tags.some((tag) => tag.text === trimmed)) return;
const newTag: TagType = { id: Date.now().toString(), text: trimmed };
setTags((prev) => [...prev, newTag]);
},
[tags]
);
return (
<TagInput
id={id}
tags={tags}
setTags={handleSetTags}
placeholder={placeholder ?? "Add email"}
onAddTag={handleAddTag}
styleClasses={{
inlineTagsContainer:
"border border-input rounded-md bg-transparent shadow-xs transition-[color,box-shadow] outline-none focus-within:border-ring p-1 gap-1",
input:
"w-full min-w-[80px] focus-visible:outline-none shadow-none px-2 h-7 text-foreground placeholder:text-muted-foreground bg-transparent text-sm md:text-sm",
tag: {
body: "h-7 relative bg-accent dark:bg-muted/60 border-0 hover:bg-accent/80 dark:hover:bg-muted rounded-md font-medium text-xs text-foreground/80 ps-2 pe-7 flex",
closeButton:
"absolute -inset-y-px -end-px p-0 rounded-e-md flex size-7 transition-colors outline-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 text-foreground hover:text-foreground",
},
}}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
/>
);
}
export function HitlEditPanelContent({
title: initialTitle,
content: initialContent,
@ -85,7 +166,14 @@ export function HitlEditPanelContent({
<Label htmlFor={`extra-field-${field.key}`} className="text-xs font-medium text-muted-foreground">
{field.label}
</Label>
{field.type === "textarea" ? (
{field.type === "emails" ? (
<EmailsTagField
id={`extra-field-${field.key}`}
value={extraFieldValues[field.key] ?? ""}
onChange={(v) => handleExtraFieldChange(field.key, v)}
placeholder={`Add ${field.label.toLowerCase()}`}
/>
) : field.type === "textarea" ? (
<Textarea
id={`extra-field-${field.key}`}
value={extraFieldValues[field.key] ?? ""}