import { useEffect, useRef, type RefObject } from 'react'; import { useTranslation } from 'react-i18next'; import { POPULAR_EMOJIS, QUICK_REACTIONS } from '@/lib/chat'; interface EmojiPickerProps { open: boolean; onClose: () => void; onPick: (emoji: string) => void; /** Якорь: клик по нему не считается кликом «вне пикера». */ anchorRef?: RefObject; /** Куда раскрывается панель: под якорем или над ним. */ placement?: 'top' | 'bottom'; className?: string; } /** * Выпадающий пикер эмодзи: ряд быстрых реакций и сетка популярных. * Закрывается по Escape и клику вне панели; фокус остаётся в пределах * tab-порядка страницы. */ export function EmojiPicker({ open, onClose, onPick, anchorRef, placement = 'bottom', className = '', }: EmojiPickerProps) { const { t } = useTranslation(); const panelRef = useRef(null); const onCloseRef = useRef(onClose); useEffect(() => { onCloseRef.current = onClose; }, [onClose]); useEffect(() => { if (!open) { return; } const onPointerDown = (event: Event): void => { const target = event.target; if (!(target instanceof Node)) { return; } if (panelRef.current?.contains(target) === true) { return; } if (anchorRef?.current?.contains(target) === true) { return; } onCloseRef.current(); }; const onKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') { onCloseRef.current(); } }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [open, anchorRef]); if (!open) { return null; } return (
{QUICK_REACTIONS.map((emoji) => ( ))}
{POPULAR_EMOJIS.map((emoji) => ( ))}
); }