import React, { Suspense, useEffect, useRef, useState } from 'react'; import { X, Trash2, Loader2 } from 'lucide-react'; import type { EmojiClickData } from 'emoji-picker-react'; import { Categories, EmojiStyle, Theme, SuggestionMode } from 'emoji-picker-react'; import { useKeyboardInset } from '../../lib/useKeyboardInset'; // Lazy-load emoji-picker-react so the main bundle size stays small const EmojiPicker = React.lazy(() => import('emoji-picker-react')); // The library's own "recently used" category (stored by it) is the only recent list; a second bar // would cost height on phones. Category names in German. const CATEGORIES = [ { category: Categories.SUGGESTED, name: 'Zuletzt verwendet' }, { category: Categories.SMILEYS_PEOPLE, name: 'Smileys & Personen' }, { category: Categories.ANIMALS_NATURE, name: 'Tiere & Natur' }, { category: Categories.FOOD_DRINK, name: 'Essen & Trinken' }, { category: Categories.TRAVEL_PLACES, name: 'Reisen & Orte' }, { category: Categories.ACTIVITIES, name: 'Aktivitäten' }, { category: Categories.OBJECTS, name: 'Objekte' }, { category: Categories.SYMBOLS, name: 'Symbole' }, { category: Categories.FLAGS, name: 'Flaggen' }, ]; interface EmojiPickerModalProps { currentIcon?: string; onSelectEmoji: (emoji: string) => void; onClose: () => void; } export const EmojiPickerModal: React.FC = ({ currentIcon, onSelectEmoji, onClose, }) => { const { inset, visibleHeight } = useKeyboardInset(); const isPhone = typeof window !== 'undefined' && window.innerWidth < 640; // On phones the picker shrinks with the keyboard so search field and results stay visible const pickerHeight = isPhone ? Math.max(200, Math.min(350, visibleHeight - 110)) : 350; const [isDark, setIsDark] = useState(() => typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true ); const containerRef = useRef(null); // Sync theme changes useEffect(() => { const observer = new MutationObserver(() => { setIsDark(document.documentElement.classList.contains('dark')); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'], }); return () => observer.disconnect(); }, []); // Close on Escape or click outside (desktop popover) useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } }; const handleClickOutside = (e: MouseEvent) => { if ( containerRef.current && !containerRef.current.contains(e.target as Node) ) { onClose(); } }; window.addEventListener('keydown', handleKeyDown); document.addEventListener('mousedown', handleClickOutside); return () => { window.removeEventListener('keydown', handleKeyDown); document.removeEventListener('mousedown', handleClickOutside); }; }, [onClose]); const handlePick = (emoji: string) => { onSelectEmoji(emoji); }; const handlePickerEmojiClick = (emojiData: EmojiClickData) => { handlePick(emojiData.emoji); }; return ( <> {/* Mobile Backdrop (< 640px) */}