Abdullahrasheed45 commited on
Commit
ffa32af
·
verified ·
1 Parent(s): 04168d9

Create PromptInput.tsx

Browse files
Files changed (1) hide show
  1. src/components/PromptInput.tsx +194 -0
src/components/PromptInput.tsx ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { PROMPTS, THEME } from "../constants";
3
+
4
+ interface PromptInputProps {
5
+ onPromptChange: (prompt: string) => void;
6
+ defaultPrompt?: string;
7
+ }
8
+
9
+ export default function PromptInput({
10
+ onPromptChange,
11
+ defaultPrompt = PROMPTS.default,
12
+ }: PromptInputProps) {
13
+ const [prompt, setPrompt] = useState(defaultPrompt);
14
+ const [showSuggestions, setShowSuggestions] = useState(false);
15
+ const inputRef = useRef<HTMLTextAreaElement>(null);
16
+ const containerRef = useRef<HTMLDivElement>(null);
17
+
18
+ const resizeTextarea = () => {
19
+ if (inputRef.current) {
20
+ inputRef.current.style.height = "auto";
21
+ const newHeight = Math.min(inputRef.current.scrollHeight, 200);
22
+ inputRef.current.style.height = `${newHeight}px`;
23
+ }
24
+ };
25
+
26
+ useEffect(() => {
27
+ onPromptChange(prompt);
28
+ resizeTextarea();
29
+ }, [prompt, onPromptChange]);
30
+
31
+ const handleInputFocus = () => setShowSuggestions(true);
32
+ const handleInputClick = () => setShowSuggestions(true);
33
+
34
+ const handleInputBlur = (e: React.FocusEvent) => {
35
+ // Small delay to allow click events on suggestions to fire
36
+ requestAnimationFrame(() => {
37
+ if (
38
+ !e.relatedTarget ||
39
+ !containerRef.current?.contains(e.relatedTarget as Node)
40
+ ) {
41
+ setShowSuggestions(false);
42
+ }
43
+ });
44
+ };
45
+
46
+ const handleSuggestionClick = (suggestion: string) => {
47
+ setPrompt(suggestion);
48
+ setShowSuggestions(false);
49
+ inputRef.current?.focus();
50
+ };
51
+
52
+ const clearInput = () => {
53
+ setPrompt("");
54
+ inputRef.current?.focus();
55
+ };
56
+
57
+ const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
58
+ setPrompt(e.target.value);
59
+ };
60
+
61
+ return (
62
+ <div
63
+ ref={containerRef}
64
+ className="w-full max-w-xl relative group font-sans"
65
+ >
66
+ {/* Suggestions Panel */}
67
+ <div
68
+ className={`absolute bottom-full left-0 right-0 mb-3 transition-all duration-300 ease-out transform origin-bottom ${
69
+ showSuggestions
70
+ ? "opacity-100 translate-y-0 scale-100 pointer-events-auto"
71
+ : "opacity-0 translate-y-2 scale-95 pointer-events-none"
72
+ }`}
73
+ >
74
+ <div
75
+ className="bg-white rounded-lg shadow-xl border overflow-hidden"
76
+ style={{ borderColor: THEME.beigeDark }}
77
+ >
78
+ {/* Header */}
79
+ <div
80
+ className="border-b px-4 py-2 flex items-center space-x-2"
81
+ style={{
82
+ backgroundColor: THEME.beigeLight,
83
+ borderColor: THEME.beigeDark,
84
+ }}
85
+ >
86
+ <svg
87
+ className="w-3 h-3"
88
+ style={{ color: THEME.mistralOrange }}
89
+ fill="none"
90
+ viewBox="0 0 24 24"
91
+ stroke="currentColor"
92
+ >
93
+ <path
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ strokeWidth={2}
97
+ d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
98
+ />
99
+ </svg>
100
+ <span className="text-xs font-bold uppercase tracking-wider text-gray-500">
101
+ Prompt Library
102
+ </span>
103
+ </div>
104
+ {/* List */}
105
+ <ul className="py-2">
106
+ {PROMPTS.suggestions.map((suggestion, index) => (
107
+ <li
108
+ key={index}
109
+ tabIndex={0}
110
+ onMouseDown={(e) => e.preventDefault()} // Prevent blur
111
+ onClick={() => handleSuggestionClick(suggestion)}
112
+ className="px-4 py-2.5 cursor-pointer flex items-start gap-3 transition-colors hover:bg-[var(--mistral-beige-light)] group/item"
113
+ >
114
+ <span
115
+ className="mt-1 opacity-0 group-hover/item:opacity-100 transition-opacity text-xs font-mono"
116
+ style={{ color: THEME.mistralOrange }}
117
+ >
118
+ {`>`}
119
+ </span>
120
+ <span className="text-sm text-gray-700 group-hover/item:text-black leading-snug">
121
+ {suggestion}
122
+ </span>
123
+ </li>
124
+ ))}
125
+ </ul>
126
+ </div>
127
+ </div>
128
+ {/* Input Container */}
129
+ <div className="relative">
130
+ {/* Label Badge */}
131
+ <div className="absolute -top-3 left-4 z-10">
132
+ <span
133
+ className="border text-[10px] font-bold text-gray-500 uppercase tracking-widest px-2 py-0.5 rounded-sm"
134
+ style={{
135
+ backgroundColor: THEME.beigeLight,
136
+ borderColor: THEME.beigeDark,
137
+ }}
138
+ >
139
+ Prompt
140
+ </span>
141
+ </div>
142
+ <div
143
+ className={`
144
+ relative bg-white rounded-lg shadow-lg border transition-all duration-300
145
+ ${showSuggestions ? "border-[var(--mistral-orange)] ring-1 ring-[var(--mistral-orange)]/20" : "border-[var(--mistral-beige-dark)] hover:border-[#D0C5A0]"}
146
+ `}
147
+ >
148
+ <div className="flex items-start p-1">
149
+ <textarea
150
+ ref={inputRef}
151
+ value={prompt}
152
+ onChange={handleInputChange}
153
+ onFocus={handleInputFocus}
154
+ onBlur={handleInputBlur}
155
+ onClick={handleInputClick}
156
+ className="w-full py-4 pl-5 pr-10 bg-transparent text-lg md:text-xl font-mono resize-none focus:outline-none placeholder:text-gray-300 leading-relaxed"
157
+ style={{
158
+ minHeight: "60px",
159
+ maxHeight: "200px",
160
+ overflowY: "auto",
161
+ color: THEME.textBlack,
162
+ }}
163
+ placeholder={PROMPTS.placeholder}
164
+ rows={1}
165
+ />
166
+ {/* Clear Button */}
167
+ {prompt && (
168
+ <button
169
+ type="button"
170
+ onMouseDown={(e) => e.preventDefault()}
171
+ onClick={clearInput}
172
+ className="absolute right-3 top-5 text-gray-300 hover:text-[var(--mistral-orange)] transition-colors p-1"
173
+ aria-label="Clear prompt"
174
+ >
175
+ <svg
176
+ xmlns="http://www.w3.org/2000/svg"
177
+ className="h-6 w-6"
178
+ viewBox="0 0 20 20"
179
+ fill="currentColor"
180
+ >
181
+ <path
182
+ fillRule="evenodd"
183
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
184
+ clipRule="evenodd"
185
+ />
186
+ </svg>
187
+ </button>
188
+ )}
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ );
194
+ }