Jofthomas's picture
Add demo files (#1)
d5c6d34 verified
raw
history blame
6.83 kB
import { useState, useRef, useEffect } from "react";
import { PROMPTS, THEME } from "../constants";
interface PromptInputProps {
onPromptChange: (prompt: string) => void;
defaultPrompt?: string;
}
export default function PromptInput({
onPromptChange,
defaultPrompt = PROMPTS.default,
}: PromptInputProps) {
const [prompt, setPrompt] = useState(defaultPrompt);
const [showSuggestions, setShowSuggestions] = useState(false);
const inputRef = useRef<HTMLTextAreaElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const resizeTextarea = () => {
if (inputRef.current) {
inputRef.current.style.height = "auto";
const newHeight = Math.min(inputRef.current.scrollHeight, 200);
inputRef.current.style.height = `${newHeight}px`;
}
};
useEffect(() => {
onPromptChange(prompt);
resizeTextarea();
}, [prompt, onPromptChange]);
const handleInputFocus = () => setShowSuggestions(true);
const handleInputClick = () => setShowSuggestions(true);
const handleInputBlur = (e: React.FocusEvent) => {
// Small delay to allow click events on suggestions to fire
requestAnimationFrame(() => {
if (
!e.relatedTarget ||
!containerRef.current?.contains(e.relatedTarget as Node)
) {
setShowSuggestions(false);
}
});
};
const handleSuggestionClick = (suggestion: string) => {
setPrompt(suggestion);
setShowSuggestions(false);
inputRef.current?.focus();
};
const clearInput = () => {
setPrompt("");
inputRef.current?.focus();
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setPrompt(e.target.value);
};
return (
<div
ref={containerRef}
className="w-full max-w-xl relative group font-sans"
>
{/* Suggestions Panel */}
<div
className={`absolute bottom-full left-0 right-0 mb-3 transition-all duration-300 ease-out transform origin-bottom ${
showSuggestions
? "opacity-100 translate-y-0 scale-100 pointer-events-auto"
: "opacity-0 translate-y-2 scale-95 pointer-events-none"
}`}
>
<div
className="bg-white rounded-lg shadow-xl border overflow-hidden"
style={{ borderColor: THEME.beigeDark }}
>
{/* Header */}
<div
className="border-b px-4 py-2 flex items-center space-x-2"
style={{
backgroundColor: THEME.beigeLight,
borderColor: THEME.beigeDark,
}}
>
<svg
className="w-3 h-3"
style={{ color: THEME.mistralOrange }}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
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"
/>
</svg>
<span className="text-xs font-bold uppercase tracking-wider text-gray-500">
Prompt Library
</span>
</div>
{/* List */}
<ul className="py-2">
{PROMPTS.suggestions.map((suggestion, index) => (
<li
key={index}
tabIndex={0}
onMouseDown={(e) => e.preventDefault()} // Prevent blur
onClick={() => handleSuggestionClick(suggestion)}
className="px-4 py-2.5 cursor-pointer flex items-start gap-3 transition-colors hover:bg-[var(--mistral-beige-light)] group/item"
>
<span
className="mt-1 opacity-0 group-hover/item:opacity-100 transition-opacity text-xs font-mono"
style={{ color: THEME.mistralOrange }}
>
{`>`}
</span>
<span className="text-sm text-gray-700 group-hover/item:text-black leading-snug">
{suggestion}
</span>
</li>
))}
</ul>
</div>
</div>
{/* Input Container */}
<div className="relative">
{/* Label Badge */}
<div className="absolute -top-3 left-4 z-10">
<span
className="border text-[10px] font-bold text-gray-500 uppercase tracking-widest px-2 py-0.5 rounded-sm"
style={{
backgroundColor: THEME.beigeLight,
borderColor: THEME.beigeDark,
}}
>
Prompt
</span>
</div>
<div
className={`
relative bg-white rounded-lg shadow-lg border transition-all duration-300
${showSuggestions ? "border-[var(--mistral-orange)] ring-1 ring-[var(--mistral-orange)]/20" : "border-[var(--mistral-beige-dark)] hover:border-[#D0C5A0]"}
`}
>
<div className="flex items-start p-1">
<textarea
ref={inputRef}
value={prompt}
onChange={handleInputChange}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
onClick={handleInputClick}
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"
style={{
minHeight: "60px",
maxHeight: "200px",
overflowY: "auto",
color: THEME.textBlack,
}}
placeholder={PROMPTS.placeholder}
rows={1}
/>
{/* Clear Button */}
{prompt && (
<button
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={clearInput}
className="absolute right-3 top-5 text-gray-300 hover:text-[var(--mistral-orange)] transition-colors p-1"
aria-label="Clear prompt"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
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"
clipRule="evenodd"
/>
</svg>
</button>
)}
</div>
</div>
</div>
</div>
);
}