import React, { useState, useEffect, useRef, useCallback } from 'react'; import { api } from '../../api/client'; import type { GifResult } from '@backspace/shared'; interface GifPickerProps { onGifSelect: (url: string) => void; } export function GifPicker({ onGifSelect }: GifPickerProps) { const [query, setQuery] = useState(''); const [debouncedQuery, setDebouncedQuery] = useState(''); const [results, setResults] = useState([]); const [loading, setLoading] = useState(true); const [nextPos, setNextPos] = useState(''); const [loadingMore, setLoadingMore] = useState(false); const scrollRef = useRef(null); const debounceRef = useRef>(); // Debounce search query useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { setDebouncedQuery(query); }, 300); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [query]); // Fetch results when debounced query changes useEffect(() => { let cancelled = false; setLoading(true); setResults([]); setNextPos(''); const fetchGifs = async () => { try { const data = debouncedQuery.trim() ? await api.gif.search(debouncedQuery.trim(), 30) : await api.gif.trending(30); if (!cancelled) { setResults(data.results); setNextPos(data.next); setLoading(false); } } catch { if (!cancelled) setLoading(false); } }; fetchGifs(); return () => { cancelled = true; }; }, [debouncedQuery]); // Infinite scroll const handleScroll = useCallback(() => { const el = scrollRef.current; if (!el || loadingMore || !nextPos) return; if (el.scrollTop + el.clientHeight >= el.scrollHeight - 100) { setLoadingMore(true); const fetchMore = async () => { try { const data = debouncedQuery.trim() ? await api.gif.search(debouncedQuery.trim(), 30, nextPos) : await api.gif.trending(30, nextPos); setResults((prev) => [...prev, ...data.results]); setNextPos(data.next); } finally { setLoadingMore(false); } }; fetchMore(); } }, [loadingMore, nextPos, debouncedQuery]); // Prevent keyboard events from bubbling const handleKeyDown = (e: React.KeyboardEvent) => { e.stopPropagation(); }; return (
{/* Search */}
setQuery(e.target.value)} placeholder="Search GIFs" className="input-search w-full" autoFocus />
{/* Results grid */}
{loading ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : results.length === 0 ? (
{debouncedQuery.trim() ? 'No GIFs found' : 'No trending GIFs'}
) : (
{results.map((gif) => ( ))}
)} {loadingMore && (
)}
{/* Attribution */}
Powered by Klipy
); }