import { useState, useEffect, useRef, useCallback } from "react"; import { EMOJI_CATEGORIES, getRecentEmojis, addRecentEmoji } from "./emojiData"; import type { Size2D } from "../../../../core/types"; interface BaseEmojiMenuProps { isOpen: boolean; onClose: () => void; onEmojiSelect: (emoji: string) => void; } interface StandaloneEmojiMenuProps extends BaseEmojiMenuProps { position: Size2D; mode: "standalone"; } interface IntegratedEmojiMenuProps extends BaseEmojiMenuProps { mode: "integrated"; } type EmojiMenuProps = StandaloneEmojiMenuProps | IntegratedEmojiMenuProps; export function EmojiMenu(props: EmojiMenuProps) { const { isOpen, onClose, onEmojiSelect, mode } = props; const position = mode === "standalone" ? props.position : undefined; const [activeCategory, setActiveCategory] = useState("recent"); const [recentEmojis, setRecentEmojis] = useState([]); const menuRef = useRef(null); const scrollRef = useRef(null); const categoryRefs = useRef>(new Map()); const tabsRef = useRef(null); const tabRefs = useRef>(new Map()); useEffect(() => { if (isOpen) { setRecentEmojis(getRecentEmojis()); } }, [isOpen]); const handleScroll = useCallback(() => { if (!scrollRef.current) return; // Find which category is currently visible for (const [categoryName, element] of categoryRefs.current) { if (element) { const rect = element.getBoundingClientRect(); const containerRect = scrollRef.current.getBoundingClientRect(); // Check if category header is in view if (rect.top <= containerRect.top + 50 && rect.bottom > containerRect.top + 50) { if (activeCategory !== categoryName) { setActiveCategory(categoryName); scrollTabIntoView(categoryName); } break; } } } }, [activeCategory]); function scrollToCategory(categoryName: string) { const element = categoryRefs.current.get(categoryName); if (element && scrollRef.current) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } function scrollTabIntoView(categoryName: string) { const tabElement = tabRefs.current.get(categoryName); if (tabElement && tabsRef.current) { const tabsRect = tabsRef.current.getBoundingClientRect(); const tabRect = tabElement.getBoundingClientRect(); // Check if tab is outside the visible area if (tabRect.left < tabsRect.left || tabRect.right > tabsRect.right) { tabElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } } } useEffect(() => { function handleClickOutside(event: MouseEvent) { if (menuRef.current && !menuRef.current.contains(event.target as Node)) { onClose(); } } function handleEscape(event: KeyboardEvent) { if (event.key === "Escape") { onClose(); } } if (isOpen) { document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", handleEscape); } return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleEscape); }; }, [isOpen, onClose]); function handleEmojiClick(emoji: string) { addRecentEmoji(emoji); onEmojiSelect(emoji); onClose(); }; return (
{EMOJI_CATEGORIES.map((category) => ( ))}
{EMOJI_CATEGORIES.map((category) => { const emojis = category.name === "recent" ? recentEmojis : category.emojis; return (
{ if (el) categoryRefs.current.set(category.name, el); }} className="emoji-category-section" >

{category.name.charAt(0).toUpperCase() + category.name.slice(1)}

{emojis.length > 0 ? (
{emojis.map((emoji, index) => ( ))}
) : (
No {category.name} emojis
)}
); })}
); }