Toggle sidebar
- {keys.map((key) => (
+ {HOTKEYS.TOGGLE_SIDEBAR.display.map((key) => (
{key}
))}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx
index c1204cb51d1..d59d010c0d5 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx
@@ -1,6 +1,6 @@
import { Kbd, KbdGroup } from "@superset/ui/kbd";
import { HiMiniCommandLine } from "react-icons/hi2";
-import { formatKeysForDisplay, HOTKEYS } from "shared/hotkeys";
+import { HOTKEYS } from "shared/hotkeys";
const shortcuts = [HOTKEYS.NEW_TERMINAL, HOTKEYS.OPEN_IN_APP];
@@ -17,7 +17,7 @@ export function EmptyTabView() {
{shortcuts.map((shortcut) => (
- {formatKeysForDisplay(shortcut.keys).map((key) => (
+ {shortcut.display.map((key) => (
{key}
))}
diff --git a/apps/desktop/src/shared/hotkeys.ts b/apps/desktop/src/shared/hotkeys.ts
index 2093ee940e7..02d7a1cbbcf 100644
--- a/apps/desktop/src/shared/hotkeys.ts
+++ b/apps/desktop/src/shared/hotkeys.ts
@@ -3,6 +3,43 @@
* Used both for registering shortcuts and displaying in the hotkey modal.
*/
+import { PLATFORM } from "./constants";
+
+// Platform-specific modifier key symbols
+const MODIFIER_MAP = PLATFORM.IS_MAC
+ ? { meta: "⌘", ctrl: "⌃", alt: "⌥", shift: "⇧" }
+ : PLATFORM.IS_WINDOWS
+ ? { meta: "Win", ctrl: "Ctrl", alt: "Alt", shift: "Shift" }
+ : { meta: "Super", ctrl: "Ctrl", alt: "Alt", shift: "Shift" };
+
+const KEY_MAP: Record = {
+ ...MODIFIER_MAP,
+ enter: "↵",
+ backspace: "⌫",
+ delete: "⌦",
+ escape: "⎋",
+ tab: "⇥",
+ up: "↑",
+ down: "↓",
+ left: "←",
+ right: "→",
+ space: "␣",
+ slash: "/",
+};
+
+/** Format a key string for display (e.g., "meta+shift+d" -> ["⌘", "⇧", "D"]) */
+function formatKeys(keys: string): string[] {
+ return keys.split("+").map((key) => {
+ const lower = key.toLowerCase();
+ return KEY_MAP[lower] || key.toUpperCase();
+ });
+}
+
+/** Helper to define a hotkey with pre-computed display */
+function hotkey(def: T): T & { display: string[] } {
+ return { ...def, display: formatKeys(def.keys) };
+}
+
export type HotkeyCategory =
| "Workspace"
| "Layout"
@@ -10,7 +47,7 @@ export type HotkeyCategory =
| "Window"
| "Help";
-export interface HotkeyDefinition {
+interface HotkeyDefinition {
/** Key combination for react-hotkeys-hook (e.g., "meta+s") */
keys: string;
/** Human-readable label for display */
@@ -27,156 +64,158 @@ export interface HotkeyDefinition {
*/
export const HOTKEYS = {
// Workspace - switch with ⌘+1-9
- JUMP_TO_WORKSPACE_1: {
+ JUMP_TO_WORKSPACE_1: hotkey({
keys: "meta+1",
label: "Switch to Workspace 1",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_2: {
+ }),
+ JUMP_TO_WORKSPACE_2: hotkey({
keys: "meta+2",
label: "Switch to Workspace 2",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_3: {
+ }),
+ JUMP_TO_WORKSPACE_3: hotkey({
keys: "meta+3",
label: "Switch to Workspace 3",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_4: {
+ }),
+ JUMP_TO_WORKSPACE_4: hotkey({
keys: "meta+4",
label: "Switch to Workspace 4",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_5: {
+ }),
+ JUMP_TO_WORKSPACE_5: hotkey({
keys: "meta+5",
label: "Switch to Workspace 5",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_6: {
+ }),
+ JUMP_TO_WORKSPACE_6: hotkey({
keys: "meta+6",
label: "Switch to Workspace 6",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_7: {
+ }),
+ JUMP_TO_WORKSPACE_7: hotkey({
keys: "meta+7",
label: "Switch to Workspace 7",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_8: {
+ }),
+ JUMP_TO_WORKSPACE_8: hotkey({
keys: "meta+8",
label: "Switch to Workspace 8",
category: "Workspace",
- },
- JUMP_TO_WORKSPACE_9: {
+ }),
+ JUMP_TO_WORKSPACE_9: hotkey({
keys: "meta+9",
label: "Switch to Workspace 9",
category: "Workspace",
- },
- PREV_WORKSPACE: {
+ }),
+ PREV_WORKSPACE: hotkey({
keys: "meta+left",
label: "Previous Workspace",
category: "Workspace",
- },
- NEXT_WORKSPACE: {
+ }),
+ NEXT_WORKSPACE: hotkey({
keys: "meta+right",
label: "Next Workspace",
category: "Workspace",
- },
+ }),
// Layout
- TOGGLE_SIDEBAR: {
+ TOGGLE_SIDEBAR: hotkey({
keys: "meta+b",
label: "Toggle Sidebar",
category: "Layout",
- },
- SPLIT_RIGHT: {
+ }),
+ SPLIT_RIGHT: hotkey({
keys: "meta+d",
label: "Split Right",
category: "Layout",
description: "Split the current pane to the right",
- },
- SPLIT_DOWN: {
+ }),
+ SPLIT_DOWN: hotkey({
keys: "meta+shift+d",
label: "Split Down",
category: "Layout",
description: "Split the current pane downward",
- },
- SPLIT_AUTO: {
+ }),
+ SPLIT_AUTO: hotkey({
keys: "meta+e",
label: "Split Pane Auto",
category: "Layout",
description: "Split the current pane along its longer side",
- },
+ }),
// Terminal
- FIND_IN_TERMINAL: {
+ FIND_IN_TERMINAL: hotkey({
keys: "meta+f",
label: "Find in Terminal",
category: "Terminal",
description: "Search text in the active terminal",
- },
- NEW_TERMINAL: {
+ }),
+ NEW_TERMINAL: hotkey({
keys: "meta+t",
label: "New Terminal",
category: "Terminal",
- },
- CLOSE_TERMINAL: {
+ }),
+ CLOSE_TERMINAL: hotkey({
keys: "meta+w",
label: "Close Terminal",
category: "Terminal",
- },
- CLEAR_TERMINAL: {
+ }),
+ CLEAR_TERMINAL: hotkey({
keys: "meta+k",
label: "Clear Terminal",
category: "Terminal",
- },
- PREV_TERMINAL: {
+ }),
+ PREV_TERMINAL: hotkey({
keys: "meta+up",
label: "Previous Terminal",
category: "Terminal",
- },
- NEXT_TERMINAL: {
+ }),
+ NEXT_TERMINAL: hotkey({
keys: "meta+down",
label: "Next Terminal",
category: "Terminal",
- },
+ }),
// Window
- NEW_WINDOW: {
+ NEW_WINDOW: hotkey({
keys: "meta+shift+n",
label: "New Window",
category: "Window",
- },
- CLOSE_WINDOW: {
+ }),
+ CLOSE_WINDOW: hotkey({
keys: "meta+shift+w",
label: "Close Window",
category: "Window",
- },
- OPEN_IN_APP: {
+ }),
+ OPEN_IN_APP: hotkey({
keys: "meta+o",
label: "Open in App",
category: "Window",
description: "Open workspace in external app (Cursor, VS Code, etc.)",
- },
+ }),
// Help
- SHOW_HOTKEYS: {
+ SHOW_HOTKEYS: hotkey({
keys: "meta+slash",
label: "Show Keyboard Shortcuts",
category: "Help",
- },
-} as const satisfies Record;
+ }),
+} as const satisfies Record;
export type HotkeyId = keyof typeof HOTKEYS;
+export type HotkeyWithDisplay = HotkeyDefinition & { display: string[] };
+
/**
* Get all hotkeys grouped by category for display purposes.
*/
export function getHotkeysByCategory(): Record<
HotkeyCategory,
- HotkeyDefinition[]
+ HotkeyWithDisplay[]
> {
- const grouped: Record = {
+ const grouped: Record = {
Workspace: [],
Layout: [],
Terminal: [],
@@ -191,41 +230,10 @@ export function getHotkeysByCategory(): Record<
return grouped;
}
-/**
- * Format a key string for display (e.g., "meta+shift+d" -> ["⌘", "⇧", "D"])
- */
-export function formatKeysForDisplay(keys: string): string[] {
- const keyMap: Record = {
- meta: "⌘",
- ctrl: "⌃",
- alt: "⌥",
- shift: "⇧",
- enter: "↵",
- backspace: "⌫",
- delete: "⌦",
- escape: "⎋",
- tab: "⇥",
- up: "↑",
- down: "↓",
- left: "←",
- right: "→",
- space: "␣",
- slash: "/",
- };
-
- return keys.split("+").map((key) => {
- const lower = key.toLowerCase();
- return keyMap[lower] || key.toUpperCase();
- });
-}
-
/**
* Check if a keyboard event matches a hotkey string like "meta+shift+d"
*/
-export function matchesHotkey(
- event: KeyboardEvent,
- hotkeyString: string,
-): boolean {
+function matchesHotkey(event: KeyboardEvent, hotkeyString: string): boolean {
const parts = hotkeyString.toLowerCase().split("+");
const requiresMeta = parts.includes("meta");
@@ -274,7 +282,7 @@ export function matchesHotkey(
/**
* Find which hotkey ID matches the keyboard event, if any
*/
-export function findMatchingHotkey(event: KeyboardEvent): HotkeyId | null {
+function findMatchingHotkey(event: KeyboardEvent): HotkeyId | null {
for (const [id, hotkey] of Object.entries(HOTKEYS)) {
if (matchesHotkey(event, hotkey.keys)) {
return id as HotkeyId;