import { useId, useRef, useState, type ClipboardEvent, type KeyboardEvent } from "react"; import { CircleAlertIcon, XIcon } from "lucide-react"; import { useFieldControlProps } from "./field"; import { cn } from "../lib/cn"; /* ---------------------------------------------------------------------------- EmailChipsInput - several email addresses in one field (Invite people). -------------------------------------------------------------------------- */ export interface EmailChip { value: string; problem?: string | undefined; } /** * Several email addresses as chips, inside a Field. Enter, comma, space and * paste make chips; Backspace on an empty field removes the last one. A chip * with a `problem` shows in danger with the problem read to screen readers. */ export function EmailChipsInput({ chips, onChange, placeholder, }: { chips: EmailChip[]; onChange: (values: string[]) => void; placeholder: string; }) { const field = useFieldControlProps(); const inputRef = useRef(null); const [draft, setDraft] = useState(""); const listId = useId(); const commit = (text: string) => { const parts = text .split(/[\s,;]+/) .map((part) => part.trim().toLowerCase()) .filter(Boolean); if (parts.length === 0) return; const values = chips.map((chip) => chip.value); onChange([...values, ...parts.filter((part) => !values.includes(part))]); setDraft(""); }; const onKeyDown = (event: KeyboardEvent) => { if ((event.key === "Enter" || event.key === "," || event.key === " ") && draft.trim()) { event.preventDefault(); commit(draft); } else if (event.key === "Backspace" && !draft && chips.length > 0) { onChange(chips.slice(0, -1).map((chip) => chip.value)); } }; const onPaste = (event: ClipboardEvent) => { const text = event.clipboardData.getData("text"); if (/[\s,;]/.test(text)) { event.preventDefault(); commit(text); } }; return (
inputRef.current?.focus()} className={cn( "flex min-h-9 w-full min-w-0 cursor-text flex-wrap items-center gap-1.5 rounded-md border bg-surface px-2 py-1.5 transition-colors duration-[120ms] hover:border-border-strong pointer-coarse:min-h-11", "has-[input:focus-visible]:border-brand has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-brand/55", field["aria-invalid"] ? "border-danger hover:border-danger" : "border-border", )} > {chips.length > 0 ? (
    {chips.map((chip) => (
  • {chip.problem ? (
  • ))}
) : null} 0 ? listId : null].filter(Boolean).join(" ") || undefined } onChange={(event) => setDraft(event.target.value)} onKeyDown={onKeyDown} onPaste={onPaste} onBlur={() => commit(draft)} className="h-6 min-w-40 flex-1 border-0 bg-transparent px-1 text-sm text-fg outline-hidden! placeholder:text-fg-subtle pointer-coarse:text-base" />
); }