krz/brand-bench
A dynamic brand documentation generator with Ollama integration.
clone: git clone https://gitbay.org/krz/brand-bench.git
main: src/components/TokenInput.tsx · raw
1import { useState, useRef, KeyboardEvent } from 'react';
2
3interface Props {
4 label: string;
5 values: string[];
6 onChange: (values: string[]) => void;
7 suggestions?: string[];
8 placeholder?: string;
9}
10
11export function TokenInput({ label, values, onChange, suggestions = [], placeholder = 'Type and press Enter' }: Props) {
12 const [draft, setDraft] = useState('');
13 const inputRef = useRef<HTMLInputElement>(null);
14
15 const add = (val: string) => {
16 const trimmed = val.trim().toLowerCase();
17 if (trimmed && !values.includes(trimmed)) {
18 onChange([...values, trimmed]);
19 }
20 setDraft('');
21 };
22
23 const remove = (val: string) => {
24 onChange(values.filter(v => v !== val));
25 };
26
27 const handleKey = (e: KeyboardEvent<HTMLInputElement>) => {
28 if ((e.key === 'Enter' || e.key === ',') && draft.trim()) {
29 e.preventDefault();
30 add(draft);
31 } else if (e.key === 'Backspace' && !draft && values.length > 0) {
32 remove(values[values.length - 1]);
33 }
34 };
35
36 const availableSuggestions = suggestions.filter(s => !values.includes(s));
37
38 return (
39 <div className="token-field">
40 <label className="field-label">{label}</label>
41 <div
42 className="token-container"
43 onClick={() => inputRef.current?.focus()}
44 >
45 {values.map(v => (
46 <span key={v} className="token">
47 {v}
48 <button
49 type="button"
50 className="token-remove"
51 onClick={e => { e.stopPropagation(); remove(v); }}
52 aria-label={`Remove ${v}`}
53 >
54 ×
55 </button>
56 </span>
57 ))}
58 <input
59 ref={inputRef}
60 className="token-input"
61 value={draft}
62 onChange={e => setDraft(e.target.value)}
63 onKeyDown={handleKey}
64 onBlur={() => { if (draft.trim()) add(draft); }}
65 placeholder={values.length === 0 ? placeholder : ''}
66 aria-label={label}
67 />
68 </div>
69 {availableSuggestions.length > 0 && (
70 <div className="token-suggestions">
71 {availableSuggestions.map(s => (
72 <button
73 key={s}
74 type="button"
75 className={`token-suggestion${values.includes(s) ? ' active' : ''}`}
76 onClick={() => add(s)}
77 >
78 {s}
79 </button>
80 ))}
81 </div>
82 )}
83 </div>
84 );
85}