import { useState, useRef, useEffect, useCallback } from 'react'; import { createPortal } from 'react-dom'; import Picker from '@emoji-mart/react'; import data from '@emoji-mart/data'; import { Search, TrendingUp, Loader2 } from 'lucide-react'; import { useLang } from '../lib/i18n'; interface TenorGif { id: string; media_formats?: { gif?: { url: string }; tinygif?: { url: string }; }; content_description?: string; } interface EmojiPickerProps { onSelect: (emoji: string) => void; onSelectGif?: (url: string, preview: string) => void; onClose: () => void; } const getTenorKey = () => localStorage.getItem('vortex_tenor_key') || ''; export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPickerProps) { const { lang, t } = useLang(); const [tab, setTab] = useState<'emoji' | 'gif'>('emoji'); const [gifQuery, setGifQuery] = useState(''); const [gifs, setGifs] = useState([]); const [gifLoading, setGifLoading] = useState(false); const [trendingGifs, setTrendingGifs] = useState([]); const gifSearchRef = useRef(null); const debounceRef = useRef>(undefined); // Load trending GIFs useEffect(() => { if (tab === 'gif' && getTenorKey() && trendingGifs.length === 0) { setGifLoading(true); fetch(`https://tenor.googleapis.com/v2/featured?key=${getTenorKey()}&limit=30&media_filter=gif,tinygif`) .then(r => r.json()) .then(d => { setTrendingGifs(d.results || []); setGifLoading(false); }) .catch(() => setGifLoading(false)); } }, [tab]); const searchGifs = useCallback((q: string) => { if (!getTenorKey() || !q.trim()) { setGifs([]); return; } setGifLoading(true); fetch(`https://tenor.googleapis.com/v2/search?key=${getTenorKey()}&q=${encodeURIComponent(q)}&limit=30&media_filter=gif,tinygif`) .then(r => r.json()) .then(d => { setGifs(d.results || []); setGifLoading(false); }) .catch(() => setGifLoading(false)); }, []); const handleGifSearch = (q: string) => { setGifQuery(q); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => searchGifs(q), 400); }; const pickGif = (gif: TenorGif) => { const url = gif.media_formats?.gif?.url || gif.media_formats?.tinygif?.url || ''; const preview = gif.media_formats?.tinygif?.url || url; if (onSelectGif && url) { onSelectGif(url, preview); } }; const displayGifs = gifQuery.trim() ? gifs : trendingGifs; const anchorRef = useRef(null); const [pos, setPos] = useState<{ top: number; left: number } | null>(null); useEffect(() => { const update = () => { const el = anchorRef.current?.parentElement; if (!el) return; const rect = el.getBoundingClientRect(); const w = tab === 'gif' ? 360 : 352; let left = rect.right - w; if (left < 8) left = 8; setPos({ top: rect.top - 8, left }); }; update(); window.addEventListener('resize', update); return () => window.removeEventListener('resize', update); }, [tab]); const pickerWidth = tab === 'gif' ? 360 : 352; return ( <>
{createPortal( <>
{/* Tabs */}
{(getTenorKey() || onSelectGif) && ( )}
{/* Emoji tab */} {tab === 'emoji' && ( onSelect(e.native)} theme="dark" locale={lang === 'ru' ? 'ru' : 'en'} set="native" previewPosition="none" skinTonePosition="search" perLine={9} emojiSize={28} emojiButtonSize={36} maxFrequentRows={2} navPosition="bottom" dynamicWidth={false} /> )} {/* GIF tab */} {tab === 'gif' && (
{!getTenorKey() ? (

{t('tenorKeyRequired')}

{t('openConsoleRun')}

localStorage.setItem('vortex_tenor_key', 'YOUR_KEY')
) : ( <>
handleGifSearch(e.target.value)} placeholder={t('searchGifs')} className="w-full pl-8 pr-3 py-2 rounded-lg bg-surface-tertiary/80 text-sm text-white placeholder-zinc-500 border border-border/30 focus:border-accent/50 outline-none transition-colors" />
{!gifQuery.trim() && !gifLoading && (
{t('trending')}
)}
{gifLoading ? (
) : displayGifs.length === 0 ? (

{gifQuery ? t('nothingFound') : ''}

) : (
{displayGifs.map((gif) => ( ))}
)}
)}
)}
, document.body )} ); }