theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

SearchBar.tsx (2094B)


      1 import { useState, useCallback, useRef, useEffect } from "react";
      2 
      3 interface SearchBarProps {
      4   onSearch: (query: string) => void;
      5   isSearching: boolean;
      6 }
      7 
      8 export function SearchBar({ onSearch, isSearching }: SearchBarProps) {
      9   const [query, setQuery] = useState("");
     10   const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
     11   const inputRef = useRef<HTMLInputElement>(null);
     12 
     13   const debouncedSearch = useCallback(
     14     (value: string) => {
     15       if (debounceRef.current) clearTimeout(debounceRef.current);
     16       debounceRef.current = setTimeout(() => {
     17         onSearch(value.trim());
     18       }, 300);
     19     },
     20     [onSearch],
     21   );
     22 
     23   const handleChange = useCallback(
     24     (e: React.ChangeEvent<HTMLInputElement>) => {
     25       const value = e.target.value;
     26       setQuery(value);
     27       debouncedSearch(value);
     28     },
     29     [debouncedSearch],
     30   );
     31 
     32   const handleClear = useCallback(() => {
     33     setQuery("");
     34     onSearch("");
     35     inputRef.current?.focus();
     36   }, [onSearch]);
     37 
     38   useEffect(() => {
     39     return () => {
     40       if (debounceRef.current) clearTimeout(debounceRef.current);
     41     };
     42   }, []);
     43 
     44   return (
     45     <div className="relative">
     46       <input
     47         ref={inputRef}
     48         type="text"
     49         value={query}
     50         onChange={handleChange}
     51         placeholder="Search messages... (Ctrl+K)"
     52         aria-label="Search messages"
     53         className="w-full rounded-lg border border-gray-700 bg-gray-900 px-4 py-2 pl-10 text-sm text-gray-100 placeholder-gray-500 focus:border-gray-500 focus:outline-none"
     54       />
     55       <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">
     56         🔍
     57       </span>
     58       {query && (
     59         <button
     60           type="button"
     61           onClick={handleClear}
     62           className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300"
     63           aria-label="Clear search"
     64         >
     65     66         </button>
     67       )}
     68       {isSearching && (
     69         <span className="absolute right-8 top-1/2 -translate-y-1/2 text-xs text-gray-500">
     70           Searching...
     71         </span>
     72       )}
     73     </div>
     74   );
     75 }