import React, { useState, useRef, useEffect, useCallback } from 'react' import { createPortal } from 'react-dom' import data from '@emoji-mart/data' import Picker from '@emoji-mart/react' /** * Emoji picker button for the forum rich-text editor. * Uses the same @emoji-mart/react picker as profile tweets / comments * so the UI is consistent across the whole site. * * The panel is rendered through a React portal so it escapes any * overflow-hidden containers (like the editor wrapper). */ export default function EmojiPicker({ onSelect, editor }) { const [open, setOpen] = useState(false) const [panelStyle, setPanelStyle] = useState({}) const panelRef = useRef(null) const buttonRef = useRef(null) // Position the portal panel relative to the trigger button useEffect(() => { if (!open || !buttonRef.current) return const rect = buttonRef.current.getBoundingClientRect() const panelWidth = 352 // emoji-mart default width const panelHeight = 435 // approximate picker height const spaceAbove = rect.top const openAbove = spaceAbove > panelHeight + 8 setPanelStyle({ position: 'fixed', zIndex: 9999, left: Math.max(8, Math.min(rect.right - panelWidth, window.innerWidth - panelWidth - 8)), ...(openAbove ? { bottom: window.innerHeight - rect.top + 6 } : { top: rect.bottom + 6 }), }) }, [open]) // Close on outside click useEffect(() => { if (!open) return const handler = (e) => { if (panelRef.current && !panelRef.current.contains(e.target) && buttonRef.current && !buttonRef.current.contains(e.target)) { setOpen(false) } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [open]) // Close on Escape useEffect(() => { if (!open) return const handler = (e) => { if (e.key === 'Escape') setOpen(false) } document.addEventListener('keydown', handler) return () => document.removeEventListener('keydown', handler) }, [open]) const handleSelect = useCallback((emoji) => { const native = emoji.native ?? '' onSelect?.(native) if (editor) { editor.chain().focus().insertContent(native).run() } setOpen(false) }, [onSelect, editor]) const panel = open ? createPortal(