From a6954ac66d7339cb17d61c79eca91f5f1630926b Mon Sep 17 00:00:00 2001
From: Max Patiiuk <max@patii.uk>
Date: Sun, 15 Dec 2024 14:28:19 -0800
Subject: [PATCH] feat(KeyboardShortcuts): make logic more robust

The original keyboard shortcuts handling code was used as a basis for
https://github.com/specify/specify7/pull/5097, but then, on that PR I
made logic more robust and added features.

Now backporting the code from that PR back into calendar-plus.

Fixes #255
---
 .../components/Atoms/Internationalization.tsx |   7 +
 src/src/components/Atoms/index.tsx            |   6 +
 src/src/components/Contexts/Contexts.tsx      |   5 +-
 .../components/Contexts/KeyboardContext.tsx   | 122 -----------
 src/src/components/Core/App.tsx               |  21 +-
 .../components/Core/__tests__/App.test.tsx    |   2 +-
 .../DebugOverlay/DevModeConsoleOverlay.tsx    |   2 +-
 src/src/components/EventsStore/index.ts       |   3 +-
 .../KeyboardShortcuts/Shortcuts.tsx           | 168 +++++++++++++++
 .../components/KeyboardShortcuts/config.ts    | 196 +++++++++++++++++
 .../components/KeyboardShortcuts/context.tsx  | 198 ++++++++++++++++++
 .../components/KeyboardShortcuts/hooks.tsx    | 116 ++++++++++
 src/src/components/KeyboardShortcuts/utils.ts |  74 +++++++
 .../components/Molecules/KeyboardShortcut.tsx | 197 -----------------
 .../components/Preferences/definitions.tsx    |  67 +++---
 src/src/localization/preferences.tsx          |  37 +++-
 16 files changed, 851 insertions(+), 370 deletions(-)
 delete mode 100644 src/src/components/Contexts/KeyboardContext.tsx
 create mode 100644 src/src/components/KeyboardShortcuts/Shortcuts.tsx
 create mode 100644 src/src/components/KeyboardShortcuts/config.ts
 create mode 100644 src/src/components/KeyboardShortcuts/context.tsx
 create mode 100644 src/src/components/KeyboardShortcuts/hooks.tsx
 create mode 100644 src/src/components/KeyboardShortcuts/utils.ts
 delete mode 100644 src/src/components/Molecules/KeyboardShortcut.tsx

diff --git a/src/src/components/Atoms/Internationalization.tsx b/src/src/components/Atoms/Internationalization.tsx
index 2466193..2740fe0 100644
--- a/src/src/components/Atoms/Internationalization.tsx
+++ b/src/src/components/Atoms/Internationalization.tsx
@@ -122,6 +122,13 @@ const numberFormatter = new Intl.NumberFormat(LANGUAGE);
 export const formatNumber = (number: number): string =>
   numberFormatter.format(number);
 
+const disjunctionFormatter = new Intl.ListFormat(LANGUAGE, {
+  style: 'long',
+  type: 'disjunction',
+});
+export const formatDisjunction = (list: RA<string>): string =>
+  disjunctionFormatter.format(list);
+
 /* eslint-disable @typescript-eslint/no-magic-numbers */
 export const MILLISECONDS = 1;
 export const SECOND = 1000 * MILLISECONDS;
diff --git a/src/src/components/Atoms/index.tsx b/src/src/components/Atoms/index.tsx
index bffed35..4cfdb28 100644
--- a/src/src/components/Atoms/index.tsx
+++ b/src/src/components/Atoms/index.tsx
@@ -234,3 +234,9 @@ export const Widget = wrap(
   'section',
   'flex flex-col gap-2 rounded bg-white dark:bg-neutral-800',
 );
+
+export const Key = wrap(
+  'Key',
+  'kbd',
+  'bg-gray-200 border-1 dark:border-none dark:bg-neutral-700 rounded-sm mx-1 p-0.5 text-xl',
+);
diff --git a/src/src/components/Contexts/Contexts.tsx b/src/src/components/Contexts/Contexts.tsx
index 5899442..75bb9aa 100644
--- a/src/src/components/Contexts/Contexts.tsx
+++ b/src/src/components/Contexts/Contexts.tsx
@@ -8,7 +8,6 @@ import { PreferencesProvider } from '../Preferences/Context';
 import { AuthenticationProvider } from './AuthContext';
 import { CalendarsSpy } from './CalendarsContext';
 import { TrackCurrentView } from './CurrentViewContext';
-import { KeyboardListener } from './KeyboardContext';
 import { SettingsProvider } from './SettingsContext';
 import { VersionsContextProvider } from './VersionsContext';
 import { output } from '../Errors/exceptions';
@@ -60,9 +59,7 @@ export function Contexts({
               <SettingsProvider>
                 <TrackCurrentView>
                   <VersionsContextProvider>
-                    <KeyboardListener>
-                      <CalendarsSpy>{children}</CalendarsSpy>
-                    </KeyboardListener>
+                    <CalendarsSpy>{children}</CalendarsSpy>
                   </VersionsContextProvider>
                 </TrackCurrentView>
               </SettingsProvider>
diff --git a/src/src/components/Contexts/KeyboardContext.tsx b/src/src/components/Contexts/KeyboardContext.tsx
deleted file mode 100644
index c1a9721..0000000
--- a/src/src/components/Contexts/KeyboardContext.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react';
-
-import { listen } from '../../utils/events';
-import type { RA } from '../../utils/types';
-import { sortFunction } from '../../utils/utils';
-import type {
-  KeyboardShortcut,
-  KeyboardShortcuts,
-} from '../Molecules/KeyboardShortcut';
-import {
-  modifierKeyNames,
-  platform,
-  resolveModifiers,
-} from '../Molecules/KeyboardShortcut';
-import { output } from '../Errors/exceptions';
-
-/**
- * Allows to register a key listener
- */
-export const KeyboardContext = React.createContext<
-  (shortcut: KeyboardShortcuts, callback: () => void) => () => void
->(() => output.throw('KeyboardListener is not initialized'));
-KeyboardContext.displayName = 'KeyboardContext';
-
-export function KeyboardListener({
-  children,
-}: {
-  readonly children: JSX.Element;
-}): JSX.Element {
-  const listenersRef = React.useRef<
-    RA<{
-      readonly shortcuts: RA<KeyboardShortcut>;
-      readonly callback: () => void;
-    }>
-  >([]);
-
-  const handleKeyboardShortcut = React.useCallback(
-    (shortcut: KeyboardShortcuts, callback: () => void) => {
-      const shortcuts: RA<KeyboardShortcut> = shortcut[platform] ?? [];
-      if (shortcuts.length === 0) return () => undefined;
-      const entry = { shortcuts, callback };
-      listenersRef.current = [...listenersRef.current, entry];
-      return () => {
-        listenersRef.current = listenersRef.current.filter(
-          (listener) => listener !== entry,
-        );
-      };
-    },
-    [],
-  );
-
-  const pressedKeys = React.useRef<KeyboardShortcut['keys']>([]);
-
-  React.useEffect(
-    () =>
-      listen(document, 'keydown', (event) => {
-        if (event.key === undefined) return;
-        const key =
-          event.key.length === 1 ? event.key.toUpperCase() : event.key;
-        if (modifierKeyNames.has(event.key)) return;
-        pressedKeys.current = Array.from(
-          new Set([...pressedKeys.current, key]),
-        ).sort(sortFunction((key) => key));
-        checkListeners(resolveModifiers(event));
-      }),
-    [],
-  );
-
-  React.useEffect(
-    () =>
-      listen(document, 'keyup', (event) => {
-        if (event.key === undefined) return;
-        const pressedKey =
-          event.key.length === 1 ? event.key.toUpperCase() : event.key;
-        pressedKeys.current = pressedKeys.current
-          .filter((key) => key !== pressedKey)
-          .sort(sortFunction((key) => key));
-        checkListeners(resolveModifiers(event));
-      }),
-    [],
-  );
-
-  const checkListeners = React.useCallback(
-    (modifiers: KeyboardShortcut['modifiers']) => {
-      let isEntering: boolean | undefined = undefined;
-
-      function isInputting(): boolean {
-        isEntering ??= isInInput();
-        return isEntering;
-      }
-
-      listenersRef.current
-        .filter((listener) =>
-          listener.shortcuts.some((shortcut) => {
-            if (
-              shortcut.modifiers.join(',') !== modifiers.join(',') ||
-              shortcut.keys.join(',') !== pressedKeys.current.join(',')
-            )
-              return false;
-            // Ignore single key shortcuts when in an input field
-            return !(
-              shortcut.modifiers.filter((modifier) => modifier !== 'shift')
-                .length === 0 && isInputting()
-            );
-          }),
-        )
-        .forEach((listener) => listener.callback());
-    },
-    [],
-  );
-
-  return (
-    <KeyboardContext.Provider value={handleKeyboardShortcut}>
-      {children}
-    </KeyboardContext.Provider>
-  );
-}
-
-const isInInput = (): boolean =>
-  document.activeElement?.tagName === 'INPUT' ||
-  document.activeElement?.tagName === 'TEXTAREA' ||
-  document.activeElement?.getAttribute('role') === 'textbox';
diff --git a/src/src/components/Core/App.tsx b/src/src/components/Core/App.tsx
index c2bba65..aaad6a1 100644
--- a/src/src/components/Core/App.tsx
+++ b/src/src/components/Core/App.tsx
@@ -6,7 +6,6 @@ import { Button } from '../Atoms';
 import { AuthContext } from '../Contexts/AuthContext';
 import { CalendarsContext } from '../Contexts/CalendarsContext';
 import { CurrentViewContext } from '../Contexts/CurrentViewContext';
-import { KeyboardContext } from '../Contexts/KeyboardContext';
 import { Dashboard } from '../Dashboard';
 import { useEvents } from '../EventsStore';
 import { OverlayPortal } from '../Molecules/Portal';
@@ -16,12 +15,12 @@ import { CondenseInterface } from '../PowerTools/CondenseInterface';
 import { GhostEvents } from '../PowerTools/GhostEvents';
 import { HideEditAll } from '../PowerTools/HideEditAll';
 import { PreferencesPage } from '../Preferences';
-import { usePref } from '../Preferences/usePref';
 import { FirstAuthScreen } from './FirstAuthScreen';
 import { useStorage } from '../../hooks/useStorage';
 import { DevModeConsoleOverlay } from '../DebugOverlay/DevModeConsoleOverlay';
 import { domReadingEligibleViews } from '../DomReading';
 import { ThemeDetector } from '../Contexts/ThemeColor';
+import { useKeyboardShortcut } from '../KeyboardShortcuts/hooks';
 
 /**
  * Entrypoint react component for the extension
@@ -32,15 +31,15 @@ export function App(): JSX.Element | null {
   );
   const isOpen = state !== 'closed';
 
-  const [openOverlayShortcut] = usePref('feature', 'openOverlayShortcut');
-  const [closeOverlayShortcut] = usePref('feature', 'closeOverlayShortcut');
-  const handleKeyboardShortcut = React.useContext(KeyboardContext);
-  React.useEffect(
-    () =>
-      isOpen
-        ? handleKeyboardShortcut(closeOverlayShortcut, () => setState('closed'))
-        : handleKeyboardShortcut(openOverlayShortcut, () => setState('main')),
-    [isOpen, handleKeyboardShortcut, closeOverlayShortcut, openOverlayShortcut],
+  useKeyboardShortcut(
+    'feature',
+    'openOverlayShortcut',
+    isOpen ? undefined : () => setState('main'),
+  );
+  useKeyboardShortcut(
+    'feature',
+    'closeOverlayShortcut',
+    isOpen ? () => setState('closed') : undefined,
   );
 
   const [domReadingEnabled, setDomReadingEnabled] = React.useState(true);
diff --git a/src/src/components/Core/__tests__/App.test.tsx b/src/src/components/Core/__tests__/App.test.tsx
index a96e294..b0dbe24 100644
--- a/src/src/components/Core/__tests__/App.test.tsx
+++ b/src/src/components/Core/__tests__/App.test.tsx
@@ -6,7 +6,7 @@ import { CurrentViewContext } from '../../Contexts/CurrentViewContext';
 import { testTime } from '../../../tests/helpers';
 import { act } from '@testing-library/react';
 import { VersionsContextProvider } from '../../Contexts/VersionsContext';
-import { KeyboardListener } from '../../Contexts/KeyboardContext';
+import { KeyboardListener } from '../../KeyboardShortcuts/context';
 
 test('does not render until current date is extracted', () =>
   act(() => {
diff --git a/src/src/components/DebugOverlay/DevModeConsoleOverlay.tsx b/src/src/components/DebugOverlay/DevModeConsoleOverlay.tsx
index a780bbc..5aa9b03 100644
--- a/src/src/components/DebugOverlay/DevModeConsoleOverlay.tsx
+++ b/src/src/components/DebugOverlay/DevModeConsoleOverlay.tsx
@@ -28,7 +28,7 @@ export function DevModeConsoleOverlay() {
             type === 'log'
               ? 'bg-white dark:bg-black'
               : type === 'warn'
-                ? 'bg-orange-300 dark:bg-orange-800'
+                ? 'bg-orange-300 dark:bg-orange-600'
                 : 'bg-red-400 dark:bg-red-700'
           }
         >
diff --git a/src/src/components/EventsStore/index.ts b/src/src/components/EventsStore/index.ts
index a0863d6..c539f2e 100644
--- a/src/src/components/EventsStore/index.ts
+++ b/src/src/components/EventsStore/index.ts
@@ -6,6 +6,7 @@ import { formatUrl } from '../../utils/queryString';
 import type { IR, R, RA, WritableArray } from '../../utils/types';
 import { findLastIndex, group, sortFunction } from '../../utils/utils';
 import {
+  formatDisjunction,
   HOUR,
   MILLISECONDS_IN_DAY,
   MINUTE,
@@ -293,7 +294,7 @@ function readDom({
     ([calendarId]) => !knownIds.has(calendarId),
   );
   if (unknownCalendarId) {
-    return `Incorrectly retrieved event calendar id as "${unknownCalendarId[0]}" (calendar by such ID does not exist). Known calendar IDs: ${Array.from(knownIds).join(', ')}`;
+    return `Incorrectly retrieved event calendar id as "${unknownCalendarId[0]}" (calendar by such ID does not exist). Known calendar IDs: ${formatDisjunction(Array.from(knownIds))}`;
   }
 
   return allDurations;
diff --git a/src/src/components/KeyboardShortcuts/Shortcuts.tsx b/src/src/components/KeyboardShortcuts/Shortcuts.tsx
new file mode 100644
index 0000000..2ae2551
--- /dev/null
+++ b/src/src/components/KeyboardShortcuts/Shortcuts.tsx
@@ -0,0 +1,168 @@
+/**
+ * Logic for setting and listening to keyboard shortcuts
+ */
+
+import React from 'react';
+
+import { useTriggerState } from '../../hooks/useTriggerState';
+import { commonText } from '../../localization/common';
+import { preferencesText } from '../../localization/preferences';
+import type { RA } from '../../utils/types';
+import { removeItem, replaceItem, replaceKey } from '../../utils/utils';
+import { Button, Key } from '../Atoms';
+import type { PreferenceRenderer } from '../Preferences/definitions';
+import { keyboardPlatform, type KeyboardShortcuts } from './config';
+import { keyJoinSymbol, setKeyboardEventInterceptor } from './context';
+import {
+  resolvePlatformShortcuts,
+  localizeKeyboardShortcut,
+  localizedKeyJoinSymbol,
+} from './utils';
+import { useLegacyKeyboardShortcutHandler } from './hooks';
+
+export const SetKeyboardShortcuts: PreferenceRenderer<KeyboardShortcuts> = ({
+  value: rawValue,
+  onChange: handleChange,
+  definition: { defaultValue },
+}) => {
+  const value = useLegacyKeyboardShortcutHandler(
+    rawValue,
+    handleChange,
+    defaultValue,
+  );
+
+  const [editingIndex, setEditingIndex] = React.useState<number | false>(false);
+  const isEditing = typeof editingIndex === 'number';
+  const shortcuts = resolvePlatformShortcuts(value) ?? [];
+  const setShortcuts = (shortcuts: RA<string>): void =>
+    handleChange(replaceKey(value, keyboardPlatform, shortcuts));
+
+  // Do not allow saving an empty shortcut
+  const hasEmptyShortcut = !isEditing && shortcuts.includes('');
+  React.useEffect(() => {
+    if (hasEmptyShortcut)
+      setShortcuts(shortcuts.filter((shortcut) => shortcut !== ''));
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [hasEmptyShortcut]);
+
+  return (
+    <div className="flex flex-col gap-2">
+      {shortcuts.map((shortcut, index) => (
+        <EditKeyboardShortcut
+          key={index}
+          shortcut={shortcut}
+          onEditStart={
+            editingIndex === false
+              ? (): void => setEditingIndex(index)
+              : undefined
+          }
+          onSave={
+            editingIndex === index
+              ? (shortcut): void => {
+                  setShortcuts(
+                    shortcut === undefined
+                      ? removeItem(shortcuts, index)
+                      : replaceItem(shortcuts, index, shortcut),
+                  );
+                  setEditingIndex(false);
+                }
+              : undefined
+          }
+        />
+      ))}
+      <div className="flex items-end gap-2">
+        {!isEditing && (
+          <Button.Default
+            onClick={(): void => {
+              setShortcuts([...shortcuts, '']);
+              setEditingIndex(shortcuts.length);
+            }}
+          >
+            {commonText('add')}
+          </Button.Default>
+        )}
+      </div>
+    </div>
+  );
+};
+
+function EditKeyboardShortcut({
+  shortcut,
+  onSave: handleSave,
+  onEditStart: handleEditStart,
+}: {
+  readonly shortcut: string;
+  readonly onSave: ((shortcut: string | undefined) => void) | undefined;
+  readonly onEditStart: (() => void) | undefined;
+}): JSX.Element {
+  const [localState, setLocalState] = useTriggerState(shortcut);
+  const parts = localState.length === 0 ? [] : localState.split(keyJoinSymbol);
+  const isEditing = typeof handleSave === 'function';
+
+  React.useEffect(() => {
+    if (isEditing) {
+      // Allows user to press Enter to finish setting keyboard shortcut.
+      saveButtonRef.current?.focus();
+      setLocalState('');
+      return setKeyboardEventInterceptor(setLocalState);
+    }
+    return undefined;
+  }, [isEditing, setLocalState]);
+
+  const isEmpty = parts.length === 0;
+  const activeValue = React.useRef(localState);
+  activeValue.current = isEmpty ? shortcut : localState;
+
+  const saveButtonRef = React.useRef<HTMLButtonElement>(null);
+
+  const localizedParts = React.useMemo(
+    () => localizeKeyboardShortcut(localState).split(localizedKeyJoinSymbol),
+    [localState],
+  );
+
+  return (
+    <div className="flex gap-2">
+      <div
+        aria-atomic
+        aria-live={isEditing ? 'polite' : undefined}
+        className="flex flex-1 flex-wrap content-center items-center gap-1"
+      >
+        {isEmpty ? (
+          isEditing ? (
+            preferencesText('pressKeys')
+          ) : (
+            preferencesText('noKeyAssigned')
+          )
+        ) : (
+          <kbd className="contents">
+            {localizedParts.map((key, index) => (
+              <Key className="mx-0" key={index}>
+                {key}
+              </Key>
+            ))}
+          </kbd>
+        )}
+      </div>
+      {isEditing && (
+        <Button.Default onClick={(): void => handleSave(undefined)}>
+          {commonText('remove')}
+        </Button.Default>
+      )}
+      <Button.Default
+        forwardRef={saveButtonRef}
+        onClick={
+          isEditing
+            ? (): void =>
+                handleSave(
+                  activeValue.current.length === 0
+                    ? shortcut
+                    : activeValue.current,
+                )
+            : handleEditStart
+        }
+      >
+        {isEditing ? commonText('save') : commonText('edit')}
+      </Button.Default>
+    </div>
+  );
+}
diff --git a/src/src/components/KeyboardShortcuts/config.ts b/src/src/components/KeyboardShortcuts/config.ts
new file mode 100644
index 0000000..278c7bc
--- /dev/null
+++ b/src/src/components/KeyboardShortcuts/config.ts
@@ -0,0 +1,196 @@
+import { preferencesText } from '../../localization/preferences';
+import type { IR, RA, RR } from '../../utils/types';
+
+/**
+ * Because operating systems, browsers and browser extensions define many
+ * keyboard shortcuts, many of which differ between operating systems, the set
+ * of free keyboard shortcuts is quite small so it's difficult to have one
+ * shortcut that works on all 3 platforms.
+ *
+ * To provide flexibility, without complicating the UI for people who only use
+ * Specify on a single platform, we do the following:
+ * - UI allows you to set keyboard shortcuts for the current platform only
+ * - If you set keyboard shortcut on any platform, that shortcut is used on all
+ *   platforms, unless you explicitly edited the shortcut on the other platform
+ * - If keyboard shortcut was not explicitly set, the default shortcut, if any
+ *   will be used
+ */
+export type KeyboardShortcuts = Partial<
+  RR<KeyboardPlatform, RA<string> | undefined>
+>;
+
+export type LegacyKeyboardShortcuts = Partial<
+  RR<KeyboardPlatform, RA<LegacyKeyboardShortcut>>
+>;
+export type LegacyKeyboardShortcut = {
+  readonly modifiers: RA<'alt' | 'ctrl' | 'meta' | 'shift'>;
+  readonly keys: RA<string>;
+};
+
+type KeyboardPlatform = 'mac' | 'other' | 'windows';
+export const keyboardPlatform: KeyboardPlatform =
+  process.env.NODE_ENV === 'test'
+    ? 'other'
+    : navigator.platform.toLowerCase().includes('mac') ||
+        // Check for iphone || ipad || ipod
+        navigator.platform.toLowerCase().includes('ip')
+      ? 'mac'
+      : navigator.platform.toLowerCase().includes('win')
+        ? 'windows'
+        : 'other';
+
+export const modifierKeys = ['Alt', 'Ctrl', 'Meta', 'Shift'] as const;
+export type ModifierKey = (typeof modifierKeys)[number];
+export const allModifierKeys = new Set([
+  ...modifierKeys,
+  'AltGraph',
+  'CapsLock',
+  'MetaLeft',
+  'MetaRight',
+  'ShiftLeft',
+  'ShiftRight',
+  'ControlLeft',
+  'ControlRight',
+  'AltLeft',
+  'AltRight',
+]);
+
+export const keyboardModifierLocalization: RR<ModifierKey, string> = {
+  Alt:
+    keyboardPlatform === 'mac'
+      ? preferencesText('macOption')
+      : preferencesText('alt'),
+  Ctrl:
+    keyboardPlatform === 'mac'
+      ? preferencesText('macControl')
+      : preferencesText('ctrl'),
+  // This key should never appear in non-mac platforms
+  Meta: preferencesText('macMeta'),
+  Shift:
+    keyboardPlatform === 'mac'
+      ? preferencesText('macShift')
+      : preferencesText('shift'),
+};
+
+/**
+ * Do not allow binding a keyboard shortcut that includes only one of these
+ * keys, without any modifier.
+ *
+ * For example, do not allow binding keyboard shortcuts to Tab key. That key is
+ * important for accessibility and for keyboard navigation. Without it
+ * you won't be able to tab your way to the "Save" button to save the
+ * keyboard shortcut)
+ */
+export const specialKeyboardKeys = new Set([
+  'Enter',
+  'Tab',
+  'Space',
+  'Escape',
+  'Backspace',
+]);
+
+/**
+ * Because we are listening to key codes that correspond to US English letters,
+ * we should show keys in the UI in US English to avoid confusion.
+ * (otherwise Cmd+O is ambiguous as it's not clear if it refers to English O or
+ * local language О).
+ * See https://github.com/specify/specify7/issues/1746#issuecomment-2227113839
+ *
+ * For some keys, it is less confusing to see a symbol (like arrow keys), rather
+ * than 'ArrowUp', thus symbols are used for those keys.
+ * See http://xahlee.info/comp/unicode_computing_symbols.html
+ *
+ * Try not to define keyboard shortcuts for keys that may be in a different
+ * place in other keyboard layouts (the positions of special symbols wary a lot)
+ *
+ * See full list of key codes:
+ * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
+ */
+export const keyLocalizations: IR<string> = {
+  ArrowDown: '↓',
+  ArrowLeft: '←',
+  ArrowRight: '→',
+  ArrowUp: '↑',
+  Backquote: '`',
+  Backslash: '\\',
+  Backspace: '⌫',
+  BracketLeft: '[',
+  BracketRight: ']',
+  Comma: ',',
+  Digit0: '0',
+  Digit1: '1',
+  Digit2: '2',
+  Digit3: '3',
+  Digit4: '4',
+  Digit5: '5',
+  Digit6: '6',
+  Digit7: '7',
+  Digit8: '8',
+  Digit9: '9',
+  // "return" key is used over Enter on macOS keyboards
+  Enter: keyboardPlatform === 'mac' ? '↵' : 'Enter',
+  Equal: '=',
+  KeyA: 'a',
+  KeyB: 'b',
+  KeyC: 'c',
+  KeyD: 'd',
+  KeyE: 'e',
+  KeyF: 'f',
+  KeyG: 'g',
+  KeyH: 'h',
+  KeyI: 'i',
+  KeyJ: 'j',
+  KeyK: 'k',
+  KeyL: 'l',
+  KeyM: 'm',
+  KeyN: 'n',
+  KeyO: 'o',
+  KeyP: 'p',
+  KeyQ: 'q',
+  KeyR: 'r',
+  KeyS: 's',
+  KeyT: 't',
+  KeyU: 'u',
+  KeyV: 'v',
+  KeyW: 'w',
+  KeyX: 'x',
+  KeyY: 'y',
+  KeyZ: 'z',
+  Minus: '-',
+  Period: '.',
+  Quote: "'",
+  Semicolon: ';',
+  Slash: '/',
+};
+
+/**
+ * Like keyLocalizations, but applied if keyboard shortcut involves Shift key
+ */
+export const shiftKeyLocalizations: Record<string, string> = {
+  KeyA: 'A',
+  KeyB: 'B',
+  KeyC: 'C',
+  KeyD: 'D',
+  KeyE: 'E',
+  KeyF: 'F',
+  KeyG: 'G',
+  KeyH: 'H',
+  KeyI: 'I',
+  KeyJ: 'J',
+  KeyK: 'K',
+  KeyL: 'L',
+  KeyM: 'M',
+  KeyN: 'N',
+  KeyO: 'O',
+  KeyP: 'P',
+  KeyQ: 'Q',
+  KeyR: 'R',
+  KeyS: 'S',
+  KeyT: 'T',
+  KeyU: 'U',
+  KeyV: 'V',
+  KeyW: 'W',
+  KeyX: 'X',
+  KeyY: 'Y',
+  KeyZ: 'Z',
+};
diff --git a/src/src/components/KeyboardShortcuts/context.tsx b/src/src/components/KeyboardShortcuts/context.tsx
new file mode 100644
index 0000000..15fc041
--- /dev/null
+++ b/src/src/components/KeyboardShortcuts/context.tsx
@@ -0,0 +1,198 @@
+/**
+ * Allows to register a key listener
+ */
+
+import type { RA, WritableArray } from '../../utils/types';
+import {
+  allModifierKeys,
+  specialKeyboardKeys,
+  type KeyboardShortcuts,
+  type ModifierKey,
+} from './config';
+import { resolvePlatformShortcuts } from './utils';
+
+/**
+ * To keep the event listener as fast as possible, we are not looping though all
+ * set keyboard shortcuts and checking if any matches the set value - instead,
+ * the registered shortcuts are stored in this hashmap, making it very easy
+ * to check if a listener for current key combination exists.
+ *
+ * At the same time, some of our UI can be nested (imagine the record selector
+ * in a record set listening for next/previous record keyboard shortcut, and
+ * then the user opens a modal that also listens for the same shortcut) - to
+ * have things work correctly, we store listeners as a stack, with the most
+ * recently added listener (last one) being the active one.
+ */
+const listeners = new Map<string, WritableArray<() => void>>();
+
+/**
+ * When setting a keyboard shortcut in user preferences, we want to:
+ * - Prevent any other shortcut from reacting
+ * - Read what keys were pressed
+ */
+let interceptor: ((keys: string) => void) | undefined;
+export function setKeyboardEventInterceptor(
+  callback: typeof interceptor,
+): () => void {
+  interceptor = callback;
+  return (): void => {
+    if (interceptor === callback) interceptor = undefined;
+  };
+}
+
+export function bindKeyboardShortcut(
+  shortcut: KeyboardShortcuts,
+  callback: () => void,
+): () => void {
+  const shortcuts = resolvePlatformShortcuts(shortcut) ?? [];
+  shortcuts.forEach((string) => {
+    const shortcutListeners = listeners.get(string);
+    if (shortcutListeners === undefined) listeners.set(string, [callback]);
+    else shortcutListeners.push(callback);
+  });
+  return () =>
+    shortcuts.forEach((string) => {
+      const activeListeners = listeners.get(string)!;
+      const lastIndex = activeListeners.lastIndexOf(callback);
+      if (lastIndex !== -1) activeListeners.splice(lastIndex, 1);
+      if (activeListeners.length === 0) listeners.delete(string);
+    });
+}
+
+/**
+ * Assumes keys and modifiers are sorted
+ */
+const keysToString = (modifiers: RA<ModifierKey>, keys: RA<string>): string =>
+  [...modifiers, ...keys].join(keyJoinSymbol);
+export const keyJoinSymbol = '+';
+
+// eslint-disable-next-line functional/prefer-readonly-type
+const pressedKeys: string[] = [];
+
+// Keep this code fast as it's in the hot path
+document.addEventListener('keydown', (event) => {
+  if (shouldIgnoreKeyPress(event)) return;
+
+  const modifiers = resolveModifiers(event);
+  const isEntering = isInInput(event);
+  const isPrintable = isPrintableModifier(modifiers);
+  // Ignore shortcuts that result in printed characters when in an input field
+  const ignore = isPrintable && isEntering;
+  if (ignore) return;
+  if (modifiers.length === 0 && specialKeyboardKeys.has(event.code)) return;
+
+  if (!pressedKeys.includes(event.code)) {
+    pressedKeys.push(event.code);
+    pressedKeys.sort();
+  }
+
+  const keyString = keysToString(modifiers, pressedKeys);
+  const handler = interceptor ?? listeners.get(keyString)?.at(-1);
+  if (typeof handler === 'function') {
+    handler(keyString);
+    /*
+     * Do this only after calling the handler, so that if handler throws an
+     * exception, the event can still be handled normally by the browser
+     */
+    event.preventDefault();
+    event.stopPropagation();
+  }
+
+  /**
+   * For key combinations involving arrows, the keyup is not fired reliably
+   * on macOS (i.e for Cmd+Shift+ArrowUp), thus we need to clear the pressed
+   * keys. This means you can't have a shortcut like Cmd+Shift+KeyQ+ArrowUp
+   */
+  if (keyString.includes('Arrow')) pressedKeys.length = 0;
+});
+
+function shouldIgnoreKeyPress(event: KeyboardEvent): boolean {
+  const code = event.code;
+
+  if (event.isComposing || event.repeat) return true;
+
+  // See https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
+  if (
+    code === 'Dead' ||
+    code === 'Unidentified' ||
+    code === 'Unidentified' ||
+    code === ''
+  )
+    return true;
+
+  // Do not allow binding a key shortcut directly to a modifier key
+  return allModifierKeys.has(event.code);
+}
+
+export const resolveModifiers = (event: KeyboardEvent): RA<ModifierKey> =>
+  Object.entries({
+    // This order is important - keep it alphabetical
+    Alt: event.altKey,
+    Ctrl: event.ctrlKey,
+    Meta: event.metaKey,
+    Shift: event.shiftKey,
+  })
+    .filter(([_, isPressed]) => isPressed)
+    .map(([modifier]) => modifier);
+
+function isInInput(event: KeyboardEvent): boolean {
+  // Check if the event target is an editable element.
+  const target = event.target as HTMLElement;
+  return (
+    target.tagName === 'INPUT' ||
+    target.tagName === 'TEXTAREA' ||
+    target.isContentEditable
+  );
+}
+
+/**
+ * On all platforms, shift key + letter produces a printable character (i.e shift+a = A)
+ *
+ * On mac, option (alt) key is used for producing printable characters too, but
+ * according to ChatGPT, in browser applications it is expected that keyboard
+ * shortcuts take precedence over printing characters.
+ */
+function isPrintableModifier(modifiers: RA<ModifierKey>): boolean {
+  if (modifiers.length === 0) return true;
+
+  if (modifiers.length === 1) return modifiers[0] === 'Shift';
+
+  return false;
+}
+
+document.addEventListener(
+  'keyup',
+  (event) => {
+    const index = pressedKeys.indexOf(event.code);
+    if (index !== -1) pressedKeys.splice(index, 1);
+
+    /*
+     * If un-pressed any modifier, consider current shortcut finished.
+     *
+     * This is a workaround for an issue on macOS where in a shortcut like
+     * Cmd+Shift+ArrowUp, the keyup even is fired for Cmd and Shift, but not
+     * for ArrowUp
+     */
+    const isModifier = allModifierKeys.has(event.code);
+    if (isModifier) pressedKeys.length = 0;
+  },
+  { passive: true },
+);
+
+/**
+ * While key up should normally catch key release, that may not always be the
+ * case:
+ * - If key up occurred outside the browser window
+ * - If key up occurred inside of browser devtools
+ * - If key up was intercepted by something else (i.e browser extension)
+ */
+window.addEventListener(
+  'blur',
+  () => {
+    pressedKeys.length = 0;
+  },
+  { passive: true },
+);
+document.addEventListener('visibilitychange', () => {
+  if (document.hidden) pressedKeys.length = 0;
+});
diff --git a/src/src/components/KeyboardShortcuts/hooks.tsx b/src/src/components/KeyboardShortcuts/hooks.tsx
new file mode 100644
index 0000000..3f80107
--- /dev/null
+++ b/src/src/components/KeyboardShortcuts/hooks.tsx
@@ -0,0 +1,116 @@
+import React from 'react';
+import type { KeyboardShortcuts, LegacyKeyboardShortcuts } from './config';
+import { bindKeyboardShortcut } from './context';
+import { resolvePlatformShortcuts, localizeKeyboardShortcut } from './utils';
+import type {
+  preferenceDefinitions,
+  Preferences,
+} from '../Preferences/definitions';
+import { usePref } from '../Preferences/usePref';
+import { formatDisjunction } from '../Atoms/Internationalization';
+import { output } from '../Errors/exceptions';
+
+/**
+ * React Hook for reacting to keyboard shortcuts user pressed for a given
+ * action.
+ *
+ * The hook also returns a localized string representing the keyboard
+ * shortcut - this string can be displayed in UI tooltips.
+ */
+export function useKeyboardShortcut<
+  CATEGORY extends keyof Preferences,
+  ITEM extends CATEGORY extends keyof typeof preferenceDefinitions
+    ? string & keyof Preferences[CATEGORY]['items']
+    : never,
+>(category: CATEGORY, item: ITEM, callback: (() => void) | undefined): string {
+  const [currentShortcuts, setShortcuts] = usePref(category, item);
+
+  const resolvedShortcut = useLegacyKeyboardShortcutHandler(
+    currentShortcuts,
+    setShortcuts,
+    undefined,
+  );
+
+  const hasCallback = typeof callback === 'function';
+
+  // eslint-disable-next-line react-hooks/rules-of-hooks
+  useManualKeyboardShortcut(resolvedShortcut, callback);
+
+  // eslint-disable-next-line react-hooks/rules-of-hooks
+  const localizedShortcut = useKeyboardShortcutLabel(resolvedShortcut);
+
+  return hasCallback ? localizedShortcut : '';
+}
+
+function useManualKeyboardShortcut(
+  shortcuts: KeyboardShortcuts | undefined,
+  callback: (() => void) | undefined,
+): void {
+  const callbackRef = React.useRef(callback);
+  callbackRef.current = callback;
+  const hasCallback = typeof callback === 'function';
+  React.useEffect(
+    () =>
+      typeof shortcuts === 'object' && hasCallback
+        ? bindKeyboardShortcut(shortcuts, () => callbackRef.current?.())
+        : undefined,
+    [hasCallback, shortcuts],
+  );
+}
+
+/**
+ * Provides a localized keyboard shortcut string, which can be used in the UI
+ * in the "title" attribute.
+ */
+function useKeyboardShortcutLabel(
+  shortcuts: KeyboardShortcuts | undefined,
+): string {
+  return React.useMemo(() => {
+    const platformShortcuts =
+      shortcuts === undefined ? [] : resolvePlatformShortcuts(shortcuts) ?? [];
+    return platformShortcuts.length > 0
+      ? ` (${formatDisjunction(
+          platformShortcuts.map(localizeKeyboardShortcut),
+        )})`
+      : '';
+  }, [shortcuts]);
+}
+
+export function useLegacyKeyboardShortcutHandler<T>(
+  currentShortcuts: T,
+  setShortcuts: (value: T) => void,
+  defaultValue: T,
+): T {
+  // Migrate from the legacy keyboard shortcut format
+  const ambiguousShortcuts = currentShortcuts as
+    | KeyboardShortcuts
+    | undefined
+    | LegacyKeyboardShortcuts;
+  const isLegacyShortcut = React.useMemo(
+    () =>
+      Object.values(ambiguousShortcuts ?? {}).some((entry) => {
+        const typedEntries = entry as
+          | LegacyKeyboardShortcuts[keyof LegacyKeyboardShortcuts]
+          | KeyboardShortcuts[keyof KeyboardShortcuts];
+        return typedEntries?.some((shortcut) => typeof shortcut !== 'string');
+      }),
+    [ambiguousShortcuts],
+  );
+  const resolvedShortcut = isLegacyShortcut ? defaultValue : currentShortcuts;
+  React.useEffect(() => {
+    if (!isLegacyShortcut) return;
+    /**
+     * Main change is that before we were using event.key. Now we use
+     * event.code. I could do a best effort mapping between the two, but I don't
+     * want to risk leaving things in a broken state. Given that I only had 2
+     * keyboard shortcuts before, seems worth the tradeoff to just unset them.
+     */
+    output.warn(
+      `Unsetting legacy shortcut as shortcut storage format changed: `,
+      currentShortcuts,
+    );
+    setShortcuts(defaultValue);
+  }, [currentShortcuts, isLegacyShortcut, defaultValue]);
+
+  return resolvedShortcut;
+}
diff --git a/src/src/components/KeyboardShortcuts/utils.ts b/src/src/components/KeyboardShortcuts/utils.ts
new file mode 100644
index 0000000..905a02f
--- /dev/null
+++ b/src/src/components/KeyboardShortcuts/utils.ts
@@ -0,0 +1,74 @@
+import type { RA, WritableArray } from '../../utils/types';
+import type { KeyboardShortcuts, ModifierKey } from './config';
+import {
+  keyboardModifierLocalization,
+  keyboardPlatform,
+  keyLocalizations,
+  shiftKeyLocalizations,
+} from './config';
+import { keyJoinSymbol } from './context';
+
+export const localizedKeyJoinSymbol = ' + ';
+export function localizeKeyboardShortcut(shortcut: string): string {
+  const parts = shortcut.split(keyJoinSymbol);
+  const hasShift = parts.includes('Shift');
+
+  const modifiers: WritableArray<string> = [];
+  const nonModifiers: WritableArray<string> = [];
+  // eslint-disable-next-line functional/no-loop-statement
+  for (const key of parts) {
+    const localizedModifier = keyboardModifierLocalization[key as ModifierKey];
+    if (typeof localizedModifier === 'string')
+      modifiers.push(localizedModifier);
+    else {
+      nonModifiers.push(
+        (hasShift ? shiftKeyLocalizations[key] : undefined) ??
+          keyLocalizations[key] ??
+          key,
+      );
+    }
+  }
+
+  // If there is only one non-modifier key, then join the keys without separator
+  const resolved =
+    keyboardPlatform === 'mac' && nonModifiers.length === 1
+      ? `${modifiers.join('')}${nonModifiers[0]}`
+      : [...modifiers, ...nonModifiers].join(localizedKeyJoinSymbol);
+
+  return resolved;
+}
+
+/**
+ * If there is a keyboard shortcut defined for current system, use it
+ * (also, if current system explicitly has empty array of shortcuts, use it).
+ *
+ * Otherwise, use the keyboard shortcut from one of the other platforms if set,
+ * but change meta to ctrl and vice versa as necessary.
+ */
+export function resolvePlatformShortcuts(
+  shortcut: KeyboardShortcuts,
+): RA<string> | undefined {
+  if (keyboardPlatform in shortcut) return shortcut[keyboardPlatform];
+  else if ('other' in shortcut)
+    return keyboardPlatform === 'windows'
+      ? shortcut.other
+      : shortcut.other?.map(replaceCtrlWithMeta);
+  else if ('windows' in shortcut)
+    return keyboardPlatform === 'other'
+      ? shortcut.other
+      : shortcut.other?.map(replaceCtrlWithMeta);
+  else if ('mac' in shortcut) return shortcut.other?.map(replaceMetaWithCtrl);
+  else return undefined;
+}
+
+const replaceCtrlWithMeta = (shortcut: string): string =>
+  shortcut
+    .split(keyJoinSymbol)
+    .map((key) => (key === 'Ctrl' ? 'Meta' : key))
+    .join(keyJoinSymbol);
+
+const replaceMetaWithCtrl = (shortcut: string): string =>
+  shortcut
+    .split(keyJoinSymbol)
+    .map((key) => (key === 'Meta' ? 'Ctrl' : key))
+    .join(keyJoinSymbol);
diff --git a/src/src/components/Molecules/KeyboardShortcut.tsx b/src/src/components/Molecules/KeyboardShortcut.tsx
deleted file mode 100644
index 7334584..0000000
--- a/src/src/components/Molecules/KeyboardShortcut.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * Logic for setting and listening to keyboard shortcuts
- */
-
-import React from 'react';
-
-import { useTriggerState } from '../../hooks/useTriggerState';
-import { commonText } from '../../localization/common';
-import { preferencesText } from '../../localization/preferences';
-import { listen } from '../../utils/events';
-import type { RA, RR } from '../../utils/types';
-import {
-  removeItem,
-  replaceItem,
-  replaceKey,
-  sortFunction,
-} from '../../utils/utils';
-import { Button } from '../Atoms';
-import type { PreferenceRenderer } from '../Preferences/definitions';
-
-const modifierLocalization = {
-  alt: preferencesText('alt'),
-  ctrl: preferencesText('ctrl'),
-  meta: preferencesText('meta'),
-  shift: preferencesText('shift'),
-};
-
-export type KeyboardShortcuts = Partial<RR<Platform, RA<KeyboardShortcut>>>;
-
-export type Platform = 'linux' | 'macOS' | 'windows';
-
-export type KeyboardShortcut = {
-  readonly modifiers: RA<keyof typeof modifierLocalization>;
-  readonly keys: RA<string>;
-};
-
-export const modifierKeyNames = new Set(['Alt', 'Control', 'Meta', 'Shift']);
-
-export const SetKeyboardShortcuts: PreferenceRenderer<KeyboardShortcuts> = ({
-  value,
-  onChange: handleChange,
-}) => {
-  const [editingIndex, setEditingIndex] = React.useState<number | false>(false);
-  const isEditing = typeof editingIndex === 'number';
-  const shortcuts = value[platform] ?? [];
-  const setShortcuts = (shortcuts: RA<KeyboardShortcut>): void =>
-    handleChange(
-      replaceKey(
-        value,
-        platform,
-        shortcuts.length === 0 ? undefined : shortcuts,
-      ),
-    );
-
-  return (
-    <div className="flex flex-col gap-2">
-      {shortcuts.map((shortcut, index) => (
-        <SetKeyboardShortcut
-          key={index}
-          shortcut={shortcut}
-          onEdit={
-            editingIndex === false
-              ? (): void => setEditingIndex(index)
-              : undefined
-          }
-          onSave={
-            editingIndex === index
-              ? (shortcut): void => {
-                  setShortcuts(
-                    shortcut === undefined
-                      ? removeItem(shortcuts, index)
-                      : replaceItem(shortcuts, index, shortcut),
-                  );
-                  setEditingIndex(false);
-                }
-              : undefined
-          }
-        />
-      ))}
-      <div className="flex items-end gap-2">
-        {!isEditing && (
-          <Button.Default
-            onClick={(): void => {
-              setEditingIndex(shortcuts.length);
-              setShortcuts([...shortcuts, { modifiers: [], keys: [] }]);
-            }}
-          >
-            {commonText('add')}
-          </Button.Default>
-        )}
-      </div>
-    </div>
-  );
-};
-
-function SetKeyboardShortcut({
-  shortcut,
-  onSave: handleSave,
-  onEdit: handleEdit,
-}: {
-  readonly shortcut: KeyboardShortcut;
-  readonly onSave:
-    | ((shortcut: KeyboardShortcut | undefined) => void)
-    | undefined;
-  readonly onEdit: (() => void) | undefined;
-}): JSX.Element {
-  const [localState, setLocalState] = useTriggerState(shortcut);
-  const { modifiers, keys } = localState;
-  const isEditing = typeof handleSave === 'function';
-
-  React.useEffect(() => {
-    if (isEditing) {
-      setLocalState({ modifiers: [], keys: [] });
-      return listen(
-        document,
-        'keydown',
-        (event) => {
-          const key =
-            event.key.length === 1 ? event.key.toUpperCase() : event.key;
-          if (modifierKeyNames.has(event.key)) return;
-          const modifiers = resolveModifiers(event);
-          setLocalState((localState) => ({
-            modifiers: Array.from(
-              new Set([...localState.modifiers, ...modifiers]),
-            ).sort(sortFunction((key) => key)),
-            keys: Array.from(new Set([...localState.keys, key])).sort(
-              sortFunction((key) => key),
-            ),
-          }));
-          event.preventDefault();
-          event.stopPropagation();
-        },
-        { capture: true },
-      );
-    }
-    return undefined;
-  }, [isEditing, setLocalState]);
-
-  const isEmpty = modifiers.length === 0 && keys.length === 0;
-  return (
-    <div className="flex gap-2">
-      <div
-        aria-atomic
-        aria-live={isEditing ? 'polite' : undefined}
-        className="flex flex-1 flex-wrap items-center gap-2"
-      >
-        {isEditing && isEmpty ? preferencesText('pressKeys') : undefined}
-        {modifiers.map((modifier) => (
-          <Key key={modifier} label={modifierLocalization[modifier]} />
-        ))}
-        {keys.map((key) => (
-          <Key key={key} label={key} />
-        ))}
-      </div>
-      {isEditing && (
-        <Button.Default onClick={(): void => handleSave(undefined)}>
-          {commonText('remove')}
-        </Button.Default>
-      )}
-      <Button.Default
-        aria-pressed={isEditing ? true : undefined}
-        onClick={
-          isEditing
-            ? (): void => handleSave(isEmpty ? { ...shortcut } : localState)
-            : handleEdit
-        }
-      >
-        {isEditing ? commonText('save') : commonText('edit')}
-      </Button.Default>
-    </div>
-  );
-}
-
-export const resolveModifiers = (
-  event: KeyboardEvent,
-): RA<keyof typeof modifierLocalization> =>
-  Object.entries({
-    alt: event.altKey,
-    ctrl: event.ctrlKey,
-    meta: event.metaKey,
-    shift: event.shiftKey,
-  })
-    .filter(([_, isPressed]) => isPressed)
-    .map(([modifier]) => modifier)
-    .sort(sortFunction((modifier) => modifier));
-
-function Key({ label }: { readonly label: string }): JSX.Element {
-  return <span className="rounded bg-gray-200 dark:bg-black p-2">{label}</span>;
-}
-
-export const platform: Platform =
-  navigator.platform.toLowerCase().includes('mac') ||
-  navigator.platform.toLowerCase().includes('ip')
-    ? 'macOS'
-    : navigator.platform.toLowerCase().includes('win')
-      ? 'windows'
-      : 'linux';
diff --git a/src/src/components/Preferences/definitions.tsx b/src/src/components/Preferences/definitions.tsx
index 014e4e4..be47e5d 100644
--- a/src/src/components/Preferences/definitions.tsx
+++ b/src/src/components/Preferences/definitions.tsx
@@ -6,8 +6,8 @@ import { commonText } from '../../localization/common';
 import { preferencesText } from '../../localization/preferences';
 import type { IR } from '../../utils/types';
 import { ensure } from '../../utils/types';
-import type { KeyboardShortcuts } from '../Molecules/KeyboardShortcut';
-import { SetKeyboardShortcuts } from '../Molecules/KeyboardShortcut';
+import type { KeyboardShortcuts } from '../KeyboardShortcuts/config';
+import { SetKeyboardShortcuts } from '../KeyboardShortcuts/Shortcuts';
 import { BooleanPref, pickListPref, rangePref } from './Renderers';
 
 /**
@@ -33,10 +33,31 @@ export type PreferenceRenderer<VALUE> = (props: {
 /**
  * This is used to enforce the same generic value be used inside a PreferenceItem
  */
-const defineItem = <VALUE,>(
+const definePref = <VALUE,>(
   definition: PreferenceItem<VALUE>,
 ): PreferenceItem<VALUE> => definition;
 
+const defineKeyboardShortcut = (
+  title: string,
+  /**
+   * If defined a keyboard shortcut for one platform, it will be automatically
+   * transformed (`ctrl -> cmd`) for the other platforms.
+   *
+   * Thus, you should define keyboard shortcuts for the "other" platform only,
+   * unless you actually want to use different keyboard shortcuts on different
+   * systems.
+   */
+  defaultValue: KeyboardShortcuts | string,
+): PreferenceItem<KeyboardShortcuts> =>
+  definePref<KeyboardShortcuts>({
+    title,
+    defaultValue:
+      typeof defaultValue === 'string'
+        ? { other: [defaultValue] }
+        : defaultValue,
+    renderer: SetKeyboardShortcuts,
+  });
+
 export type GenericPreferencesCategories = IR<{
   readonly title: string;
   readonly description?: string;
@@ -54,7 +75,7 @@ export const preferenceDefinitions = {
   behavior: {
     title: preferencesText('behavior'),
     items: {
-      ignoreAllDayEvents: defineItem<boolean>({
+      ignoreAllDayEvents: definePref<boolean>({
         title: preferencesText('ignoreAllDayEvents'),
         renderer: BooleanPref,
         defaultValue: true,
@@ -64,25 +85,15 @@ export const preferenceDefinitions = {
   feature: {
     title: preferencesText('features'),
     items: {
-      openOverlayShortcut: defineItem<KeyboardShortcuts>({
-        title: preferencesText('openOverlayShortcut'),
-        renderer: SetKeyboardShortcuts,
-        defaultValue: {
-          linux: [{ modifiers: [], keys: ['`'] }],
-          macOS: [{ modifiers: [], keys: ['`'] }],
-          windows: [{ modifiers: [], keys: ['`'] }],
-        },
-      }),
-      closeOverlayShortcut: defineItem<KeyboardShortcuts>({
-        title: preferencesText('closeOverlayShortcut'),
-        renderer: SetKeyboardShortcuts,
-        defaultValue: {
-          linux: [{ modifiers: [], keys: ['`'] }],
-          macOS: [{ modifiers: [], keys: ['`'] }],
-          windows: [{ modifiers: [], keys: ['`'] }],
-        },
-      }),
-      ghostEventShortcut: defineItem<'cmd' | 'ctrl' | 'none' | 'shift'>({
+      openOverlayShortcut: defineKeyboardShortcut(
+        preferencesText('openOverlayShortcut'),
+        'Backquote',
+      ),
+      closeOverlayShortcut: defineKeyboardShortcut(
+        preferencesText('closeOverlayShortcut'),
+        'Backquote',
+      ),
+      ghostEventShortcut: definePref<'cmd' | 'ctrl' | 'none' | 'shift'>({
         title: preferencesText('ghostEvent'),
         description: preferencesText('ghostEventDescription'),
         renderer: pickListPref<'cmd' | 'ctrl' | 'none' | 'shift'>(
@@ -90,12 +101,12 @@ export const preferenceDefinitions = {
         ),
         defaultValue: 'shift' as const,
       }),
-      ghostEventOpacity: defineItem<number>({
+      ghostEventOpacity: definePref<number>({
         title: preferencesText('ghostedEventOpacity'),
         renderer: rangePref({ min: 0, max: 100, step: 1 }),
         defaultValue: 30,
       }),
-      condenseInterface: defineItem<boolean>({
+      condenseInterface: definePref<boolean>({
         title: preferencesText('condenseInterface'),
         renderer: BooleanPref,
         defaultValue: false,
@@ -105,13 +116,13 @@ export const preferenceDefinitions = {
   recurringEvents: {
     title: preferencesText('recurringEvents'),
     items: {
-      hideEditAll: defineItem<boolean>({
+      hideEditAll: definePref<boolean>({
         title: preferencesText('hideEditAll'),
         description: preferencesText('hideEditAllDescription'),
         renderer: BooleanPref,
         defaultValue: false,
       }),
-      lessInvasiveDialog: defineItem<boolean>({
+      lessInvasiveDialog: definePref<boolean>({
         title: preferencesText('lessInvasiveDialog'),
         description: preferencesText('lessInvasiveDialogDescription'),
         renderer: BooleanPref,
@@ -122,7 +133,7 @@ export const preferenceDefinitions = {
   export: {
     title: commonText('dataExport'),
     items: {
-      format: defineItem<'csv' | 'json' | 'tsv'>({
+      format: definePref<'csv' | 'json' | 'tsv'>({
         title: preferencesText('exportFormat'),
         renderer: pickListPref<'csv' | 'json' | 'tsv'>([
           { value: 'json', title: preferencesText('json') },
diff --git a/src/src/localization/preferences.tsx b/src/src/localization/preferences.tsx
index 3150a2d..36d04ad 100644
--- a/src/src/localization/preferences.tsx
+++ b/src/src/localization/preferences.tsx
@@ -22,12 +22,8 @@ export const preferencesText = createDictionary({
   ignoreAllDayEvents: { 'en-us': 'Ignore All-Day Events' },
   openOverlayShortcut: { 'en-us': 'Open Overlay Shortcut' },
   closeOverlayShortcut: { 'en-us': 'Close Overlay Shortcut' },
-  ctrl: { 'en-us': 'Ctrl' },
-  cmd: { 'en-us': 'Cmd' },
-  shift: { 'en-us': 'Shift' },
-  alt: { 'en-us': 'Alt' },
-  meta: { 'en-us': 'Cmd' },
   pressKeys: { 'en-us': 'Press some keys...' },
+  noKeyAssigned: { 'en-us': 'No key assigned' },
   recurringEvents: { 'en-us': 'Recurring Events' },
   lessInvasiveDialog: {
     'en-us': 'Less Invasive "Edit recurring event" Dialog',
@@ -58,4 +54,35 @@ export const preferencesText = createDictionary({
   condenseInterface: {
     'en-us': 'Condense Interface',
   },
+  alt: {
+    comment: 'Alt key on the keyboard',
+    'en-us': 'Alt',
+  },
+  macOption: {
+    comment: 'Option key on the macOS keyboard',
+    'en-us': '⌥',
+  },
+  ctrl: {
+    comment: 'Ctrl key on the keyboard',
+    'en-us': 'Ctrl',
+  },
+  macControl: {
+    comment: 'Control key on the macOS keyboard',
+    'en-us': '⌃',
+  },
+  macMeta: {
+    comment: 'Meta/Command key on the macOS keyboard',
+    'en-us': '⌘',
+  },
+  macShift: {
+    comment: 'Shift key on the macOS keyboard',
+    'en-us': '⇧',
+  },
+  shift: {
+    comment: 'Shift key on the keyboard',
+    'en-us': 'Shift',
+  },
+  keyboardShortcuts: {
+    'en-us': 'Keyboard Shortcuts',
+  },
 });