diff --git a/src/app.jsx b/src/app.jsx
index 54dde201..b0b25df7 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -23,6 +23,7 @@ import Compose from './components/compose';
import ComposeButton from './components/compose-button';
import Drafts from './components/drafts';
import { ICONS } from './components/icon';
+import KeyboardShortcutsHelp from './components/keyboard-shortcuts-help';
import Loader from './components/loader';
import MediaModal from './components/media-modal';
import Modal from './components/modal';
@@ -192,7 +193,8 @@ function App() {
snapStates.showAccount ||
snapStates.showDrafts ||
snapStates.showMediaModal ||
- snapStates.showShortcutsSettings;
+ snapStates.showShortcutsSettings ||
+ snapStates.showKeyboardShortcutsHelp;
useEffect(() => {
if (!showModal) focusDeck();
}, [showModal]);
@@ -433,6 +435,7 @@ function App() {
+
>
);
}
diff --git a/src/components/icon.jsx b/src/components/icon.jsx
index 34ed5e06..f26091cd 100644
--- a/src/components/icon.jsx
+++ b/src/components/icon.jsx
@@ -97,6 +97,7 @@ export const ICONS = {
clipboard: () => import('@iconify-icons/mingcute/clipboard-line'),
'account-edit': () => import('@iconify-icons/mingcute/user-edit-line'),
'account-warning': () => import('@iconify-icons/mingcute/user-warning-line'),
+ keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),
};
function Icon({
diff --git a/src/components/keyboard-shortcuts-help.css b/src/components/keyboard-shortcuts-help.css
new file mode 100644
index 00000000..1dbe69ec
--- /dev/null
+++ b/src/components/keyboard-shortcuts-help.css
@@ -0,0 +1,22 @@
+#keyboard-shortcuts-help-container {
+ table {
+ th {
+ font-weight: normal;
+ text-align: start;
+ padding: 0.25em 0;
+ line-height: 1;
+ }
+ td {
+ padding: 0.25em 1em;
+ }
+ }
+
+ kbd {
+ border-radius: 4px;
+ display: inline-block;
+ padding: 0.3em;
+ line-height: 1;
+ border: 1px solid var(--outline-color);
+ background-color: var(--bg-faded-color);
+ }
+}
diff --git a/src/components/keyboard-shortcuts-help.jsx b/src/components/keyboard-shortcuts-help.jsx
new file mode 100644
index 00000000..0d876d9e
--- /dev/null
+++ b/src/components/keyboard-shortcuts-help.jsx
@@ -0,0 +1,136 @@
+import './keyboard-shortcuts-help.css';
+
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useSnapshot } from 'valtio';
+
+import states from '../utils/states';
+
+import Icon from './icon';
+import Modal from './modal';
+
+export default function KeyboardShortcutsHelp() {
+ const snapStates = useSnapshot(states);
+
+ function onClose() {
+ states.showKeyboardShortcutsHelp = false;
+ }
+
+ useHotkeys(
+ '?, shift+?',
+ (e) => {
+ console.log('help');
+ states.showKeyboardShortcutsHelp = true;
+ },
+ {
+ ignoreEventWhen: (e) => {
+ const hasModal = !!document.querySelector('#modal-container > *');
+ return hasModal;
+ },
+ },
+ );
+
+ const escRef = useHotkeys('esc', onClose, []);
+
+ return (
+ !!snapStates.showKeyboardShortcutsHelp && (
+ {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ }}
+ >
+
+
+
+
+
+ {[
+ {
+ action: 'Keyboard shortcuts help',
+ keys: ?,
+ },
+ {
+ action: 'Next post',
+ keys: j,
+ },
+ {
+ action: 'Previous post',
+ keys: k,
+ },
+ {
+ action: 'Skip carousel to next post',
+ keys: (
+ <>
+ Shift + j
+ >
+ ),
+ },
+ {
+ action: 'Skip carousel to previous post',
+ keys: (
+ <>
+ Shift + k
+ >
+ ),
+ },
+ {
+ action: 'Search',
+ keys: /,
+ },
+ {
+ action: 'Compose new post',
+ keys: c,
+ },
+ {
+ action: 'Send post',
+ keys: (
+ <>
+ Ctrl + Enter or ⌘ +{' '}
+ Enter
+ >
+ ),
+ },
+ {
+ action: 'Open post details',
+ keys: (
+ <>
+ Enter or o
+ >
+ ),
+ },
+ {
+ action: 'Toggle expanded/collapsed thread',
+ keys: x,
+ },
+ {
+ action: 'Close post or dialogs',
+ keys: (
+ <>
+ Esc or Backspace
+ >
+ ),
+ },
+ ].map(({ action, keys }) => (
+
+ {action} |
+ {keys} |
+
+ ))}
+
+
+
+
+ )
+ );
+}
diff --git a/src/components/nav-menu.jsx b/src/components/nav-menu.jsx
index 45e978ba..e92e96c1 100644
--- a/src/components/nav-menu.jsx
+++ b/src/components/nav-menu.jsx
@@ -204,6 +204,14 @@ function NavMenu(props) {
>
Accounts…
+