import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { MonitorShareIcon, PollIcon } from './icons'; interface Props { anchorRef: React.RefObject; open: boolean; onClose: () => void; onAttachFile: () => void; onCreatePoll: () => void; onCreateWhiteboard: () => void; onStartWatchTogether: () => void; onStartGame: () => void; canStartGame?: boolean; } export function ComposerActionsMenu({ anchorRef, open, onClose, onAttachFile, onCreatePoll, onCreateWhiteboard, onStartWatchTogether, onStartGame, canStartGame = true, }: Props) { const { t } = useTranslation(); const menuRef = useRef(null); const firstItemRef = useRef(null); // Auto-focus the first item when menu opens (a11y) + click-outside/Esc handlers useEffect(() => { if (!open) return; firstItemRef.current?.focus(); const onDocClick = (e: MouseEvent) => { const target = e.target as Node | null; if (!target) return; if (menuRef.current?.contains(target)) return; if (anchorRef.current?.contains(target)) return; onClose(); }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('mousedown', onDocClick); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDocClick); document.removeEventListener('keydown', onKey); }; }, [open, onClose, anchorRef]); if (!open) return null; // Each item: closes the menu, then runs the action. const items: Array<{ key: string; label: string; Icon: React.ComponentType>; action: () => void; disabled?: boolean; disabledTitle?: string; section: 'top' | 'activities'; }> = [ { key: 'attach', label: t('app:composer.menu.attach', { defaultValue: 'Bild / Datei' }), Icon: PaperclipIcon, action: onAttachFile, section: 'top', }, { key: 'poll', label: t('app:composer.menu.poll', { defaultValue: 'Umfrage' }), Icon: PollIcon, action: onCreatePoll, section: 'top', }, { key: 'whiteboard', label: t('app:composer.menu.whiteboard', { defaultValue: 'Whiteboard' }), Icon: MonitorShareIcon, action: onCreateWhiteboard, section: 'activities', }, { key: 'watch', label: t('app:composer.menu.watch', { defaultValue: 'Watch Together' }), Icon: PlayBoxIcon, action: onStartWatchTogether, section: 'activities', }, { key: 'game', label: t('app:composer.menu.game', { defaultValue: 'Spiel starten' }), Icon: GameIcon, action: onStartGame, disabled: !canStartGame, disabledTitle: t('app:composer.menu.game_dm_only', { defaultValue: 'Nur in 1:1-Chats', }), section: 'activities', }, ]; const handleItemClick = (item: (typeof items)[number]) => { if (item.disabled) return; onClose(); item.action(); }; const handleKeyDown = (e: React.KeyboardEvent) => { const focusable = menuRef.current?.querySelectorAll( 'button[role="menuitem"]:not([disabled])', ); if (!focusable || focusable.length === 0) return; const list = Array.from(focusable); const idx = list.findIndex((el) => el === document.activeElement); if (e.key === 'ArrowDown') { e.preventDefault(); list[(idx + 1) % list.length]?.focus(); } else if (e.key === 'ArrowUp') { e.preventDefault(); list[(idx - 1 + list.length) % list.length]?.focus(); } }; const topItems = items.filter((i) => i.section === 'top'); const activityItems = items.filter((i) => i.section === 'activities'); return (
{topItems.map((item, idx) => { const Icon = item.Icon; const isFirst = idx === 0; return ( ); })} ); } // --- Inline icons not in the central icons module ---------------------- function PaperclipIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( ); } function PlayBoxIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( ); } function GameIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( ); }