import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from '@/hooks/useTranslation'; import { isMacPlatform } from '@/services/environment'; import { getShortcutsForDisplay } from '@/helpers/shortcuts'; import { formatKeyForDisplay } from '@/utils/shortcutKeys'; import Dialog from './Dialog'; import Link from './Link'; export const setShortcutsDialogVisible = (visible: boolean) => { const dialog = document.getElementById('shortcuts_help'); if (dialog) { const event = new CustomEvent('setDialogVisibility', { detail: { visible }, }); dialog.dispatchEvent(event); } }; export const KeyboardShortcutsHelp = () => { const _ = useTranslation(); const [isOpen, setIsOpen] = useState(false); const isMac = isMacPlatform(); const sections = useMemo(() => getShortcutsForDisplay(isMac), [isMac]); // Split sections into two balanced columns by item count const [leftColumn, rightColumn] = useMemo(() => { const totalItems = sections.reduce((sum, s) => sum + s.items.length + 1, 0); // +1 for header const left: typeof sections = []; const right: typeof sections = []; let leftCount = 0; for (const section of sections) { const sectionWeight = section.items.length + 1; if (leftCount <= totalItems / 2) { left.push(section); leftCount += sectionWeight; } else { right.push(section); } } return [left, right]; }, [sections]); const renderSection = useCallback( (section: (typeof sections)[number]) => (

{_(section.section)}

{section.items.map((item) => (
{_(item.description)}
{item.keys.map((key) => ( {formatKeyForDisplay(key, isMac)} ))}
))}
), [_, isMac], ); useEffect(() => { const handleCustomEvent = (event: CustomEvent) => { setIsOpen(event.detail.visible); }; const el = document.getElementById('shortcuts_help'); if (el) { el.addEventListener('setDialogVisibility', handleCustomEvent as EventListener); } return () => { if (el) { el.removeEventListener('setDialogVisibility', handleCustomEvent as EventListener); } }; }, []); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key !== '?') return; const activeElement = document.activeElement as HTMLElement; const isInteractive = activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable; if (isInteractive) return; event.preventDefault(); setIsOpen((prev) => !prev); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); const handleClose = () => { setIsOpen(false); // Move focus away from the dialog so the '?' key listener works immediately (document.activeElement as HTMLElement)?.blur(); }; return ( {isOpen && (
{leftColumn.map(renderSection)}
{rightColumn.map(renderSection)}
{_('View all keyboard shortcuts')}
)}
); };