spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/components/chat/search-bar.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { useState, FormEvent } from "react";18import { Send, Loader2 } from "lucide-react";19import type { ClaudeModel } from "@/hooks/useChatSession";2021interface SearchBarProps {22 onSearch: (query: string, imageData?: string, imageMimeType?: string) => void;23 isLoading?: boolean;24 placeholder?: string;25 selectedModel?: ClaudeModel;26 onModelChange?: (model: ClaudeModel) => void;27}2829export function SearchBar({ onSearch, isLoading = false, placeholder = "Ask anything..." }: SearchBarProps) {30 const [query, setQuery] = useState("");31 const [isFocused, setIsFocused] = useState(false);3233 const handleSubmit = (e: FormEvent) => {34 e.preventDefault();35 if (query.trim() && !isLoading) {36 onSearch(query.trim());37 setQuery("");38 }39 };4041 const canSubmit = Boolean(query.trim()) && !isLoading;4243 return (44 <div className="w-full" data-testid="search-bar-container">45 <form onSubmit={handleSubmit} className="relative" data-testid="form-search">46 <div47 className={`relative flex items-center gap-3 px-4 py-3 bg-card border rounded-xl transition-colors ${48 isFocused49 ? "border-primary ring-2 ring-primary/10"50 : "border-border hover:border-primary/40"51 }`}52 data-testid="search-input-wrapper"53 >54 <input55 type="text"56 value={query}57 onChange={(e) => setQuery(e.target.value)}58 onFocus={() => setIsFocused(true)}59 onBlur={() => setIsFocused(false)}60 placeholder={placeholder}61 disabled={isLoading}62 data-testid="input-search-query"63 className="flex-1 min-w-0 bg-transparent border-none outline-none text-base font-normal text-foreground placeholder:text-muted-foreground disabled:opacity-50 disabled:cursor-not-allowed"64 />6566 <button67 type="submit"68 disabled={!canSubmit}69 className={`flex-shrink-0 p-2.5 rounded-lg transition-colors ${70 canSubmit71 ? "bg-primary text-primary-foreground hover:bg-primary/90"72 : "bg-muted text-muted-foreground cursor-not-allowed"73 }`}74 >75 {isLoading ? (76 <Loader2 className="w-4 h-4 animate-spin" data-testid="icon-search-loading" />77 ) : (78 <Send className="w-4 h-4" />79 )}80 </button>81 </div>82 </form>83 </div>84 );85}86