krz/brand-bench
A dynamic brand documentation generator with Ollama integration.
clone: git clone https://gitbay.org/krz/brand-bench.git
main: src/components/InputPanel.tsx · raw
1import type { BrandInputs } from '../types';
2import { TokenInput } from './TokenInput';
3
4const TONE_SUGGESTIONS = ['minimal', 'technical', 'calm', 'bold', 'warm', 'dry', 'focused', 'sharp'];
5const AVOID_SUGGESTIONS = ['buzzwords', 'hype', 'startup language', 'passive voice', 'corporate tone', 'exclamation points', 'superlatives'];
6
7interface Props {
8 inputs: BrandInputs;
9 onChange: (inputs: BrandInputs) => void;
10 onGenerate: () => void;
11 isGenerating: boolean;
12 generateLabel?: string;
13}
14
15export function InputPanel({ inputs, onChange, onGenerate, isGenerating, generateLabel }: Props) {
16 const set = <K extends keyof BrandInputs>(key: K) =>
17 (value: BrandInputs[K]) => onChange({ ...inputs, [key]: value });
18
19 const handleKey = (e: React.KeyboardEvent) => {
20 if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
21 e.preventDefault();
22 onGenerate();
23 }
24 };
25
26 const canGenerate = inputs.name.trim().length > 0 && inputs.purpose.trim().length > 0;
27
28 return (
29 <div className="input-panel" onKeyDown={handleKey}>
30 <div className="input-section">
31 <div className="input-section-label">Project</div>
32 <div className="field">
33 <label className="field-label" htmlFor="f-name">Name</label>
34 <input
35 id="f-name"
36 className="field-input"
37 placeholder="e.g. Foundry"
38 value={inputs.name}
39 onChange={e => set('name')(e.target.value)}
40 />
41 </div>
42 <div className="field">
43 <label className="field-label" htmlFor="f-category">Category</label>
44 <input
45 id="f-category"
46 className="field-input"
47 placeholder="e.g. developer tool, design studio"
48 value={inputs.category}
49 onChange={e => set('category')(e.target.value)}
50 />
51 </div>
52 <div className="field">
53 <label className="field-label" htmlFor="f-purpose">One-line purpose</label>
54 <input
55 id="f-purpose"
56 className="field-input"
57 placeholder="e.g. deploy microservices without boilerplate"
58 value={inputs.purpose}
59 onChange={e => set('purpose')(e.target.value)}
60 />
61 </div>
62 <div className="field">
63 <label className="field-label" htmlFor="f-audience">Audience</label>
64 <input
65 id="f-audience"
66 className="field-input"
67 placeholder="e.g. backend engineers"
68 value={inputs.audience}
69 onChange={e => set('audience')(e.target.value)}
70 />
71 </div>
72 </div>
73
74 <div className="input-section">
75 <div className="input-section-label">Voice</div>
76 <div className="field">
77 <TokenInput
78 label="Tone attributes"
79 values={inputs.tone}
80 onChange={set('tone')}
81 suggestions={TONE_SUGGESTIONS}
82 placeholder="Add tone..."
83 />
84 </div>
85 <div className="field" style={{ marginTop: 8 }}>
86 <TokenInput
87 label="Avoid"
88 values={inputs.avoid}
89 onChange={set('avoid')}
90 suggestions={AVOID_SUGGESTIONS}
91 placeholder="Add avoid..."
92 />
93 </div>
94 </div>
95
96 <div className="input-section">
97 <div className="input-section-label">Notes</div>
98 <div className="field">
99 <label className="field-label sr-only" htmlFor="f-notes">Notes & constraints</label>
100 <textarea
101 id="f-notes"
102 className="field-textarea"
103 placeholder="Optional: constraints, context, inspirations, or anything the brand package should reflect."
104 value={inputs.notes}
105 onChange={e => set('notes')(e.target.value)}
106 rows={4}
107 />
108 </div>
109 </div>
110
111 <div className="generate-area">
112 <button
113 className={`btn-generate${isGenerating ? ' generating' : ''}`}
114 onClick={onGenerate}
115 disabled={!isGenerating && !canGenerate}
116 title={!canGenerate ? 'Enter a name and purpose to generate' : 'Generate brand package (⌘Enter)'}
117 >
118 {generateLabel ?? (isGenerating ? 'Generating…' : 'Generate brand package')}
119 </button>
120 {!canGenerate && (
121 <div style={{ marginTop: 7, fontSize: 11, color: 'var(--text-4)', textAlign: 'center' }}>
122 Name and purpose required
123 </div>
124 )}
125 {canGenerate && !isGenerating && (
126 <div style={{ marginTop: 7, fontSize: 11, color: 'var(--text-4)', textAlign: 'center' }}>
127 ⌘ Enter to generate
128 </div>
129 )}
130 </div>
131 </div>
132 );
133}