feat: update the shortcuts in the desktop app for macOS (#96)
* feat(ui): update keyboard shortcuts to use platform-appropriate modifier keys * fix(macos): update keyboard shortcuts to use Cmd modifier Change Command Palette shortcut from Ctrl+X to Cmd+K Update all menu shortcuts to use Cmd instead of Ctrl on macOS * Fix linter issue
This commit is contained in:
@@ -12,7 +12,7 @@ import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useSessionStore } from '@/stores/useSessionStore';
|
||||
import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, getModifierLabel, hasModifier } from '@/lib/utils';
|
||||
import { useDiffFileCount } from '@/components/views/DiffView';
|
||||
|
||||
interface TabConfig {
|
||||
@@ -277,7 +277,7 @@ export const Header: React.FC = () => {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
|
||||
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
|
||||
if (hasModifier(e) && !e.shiftKey && !e.altKey) {
|
||||
const num = parseInt(e.key, 10);
|
||||
if (num >= 1 && num <= tabs.length) {
|
||||
e.preventDefault();
|
||||
@@ -410,7 +410,7 @@ export const Header: React.FC = () => {
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Command Palette (Ctrl+X)</p>
|
||||
<p>Command Palette ({getModifierLabel()}+K)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip delayDuration={500}>
|
||||
@@ -425,7 +425,7 @@ export const Header: React.FC = () => {
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Keyboard Shortcuts (Ctrl+H)</p>
|
||||
<p>Keyboard Shortcuts ({getModifierLabel()}+H)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import type { ThemeMode } from '@/types/theme';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useMessageQueueStore } from '@/stores/messageQueueStore';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, getModifierLabel } from '@/lib/utils';
|
||||
import { ButtonSmall } from '@/components/ui/button-small';
|
||||
import { NumberInput } from '@/components/ui/number-input';
|
||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||
@@ -355,8 +355,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</label>
|
||||
<p className="typography-meta text-muted-foreground pl-5">
|
||||
{queueModeEnabled
|
||||
? 'Enter queues messages, Ctrl+Enter sends immediately.'
|
||||
: 'Enter sends immediately, Ctrl+Enter queues messages.'}
|
||||
? `Enter queues messages, ${getModifierLabel()}+Enter sends immediately.`
|
||||
: `Enter sends immediately, ${getModifierLabel()}+Enter queues messages.`}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { RiAddLine, RiChatAi3Line, RiCheckLine, RiCodeLine, RiComputerLine, RiGitBranchLine, RiLayoutLeftLine, RiMoonLine, RiQuestionLine, RiRestartLine, RiSettings3Line, RiSunLine, RiTerminalBoxLine } from '@remixicon/react';
|
||||
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import { getModifierLabel } from '@/lib/utils';
|
||||
|
||||
export const CommandPalette: React.FC = () => {
|
||||
const {
|
||||
@@ -121,42 +122,42 @@ export const CommandPalette: React.FC = () => {
|
||||
<CommandItem onSelect={handleOpenSessionList}>
|
||||
<RiLayoutLeftLine className="mr-2 h-4 w-4" />
|
||||
<span>Open Session List</span>
|
||||
<CommandShortcut>Ctrl + L</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + L</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleCreateSession}>
|
||||
<RiAddLine className="mr-2 h-4 w-4" />
|
||||
<span>New Session</span>
|
||||
<CommandShortcut>Ctrl + N</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + N</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleOpenAdvancedSession}>
|
||||
<RiGitBranchLine className="mr-2 h-4 w-4" />
|
||||
<span>New Session with Worktree</span>
|
||||
<CommandShortcut>Shift + Ctrl + N</CommandShortcut>
|
||||
<CommandShortcut>Shift + {getModifierLabel()} + N</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleShowHelp}>
|
||||
<RiQuestionLine className="mr-2 h-4 w-4" />
|
||||
<span>Keyboard Shortcuts</span>
|
||||
<CommandShortcut>Ctrl + H</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + H</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleOpenDiffPanel}>
|
||||
<RiCodeLine className="mr-2 h-4 w-4" />
|
||||
<span>Open Diff Panel</span>
|
||||
<CommandShortcut>Ctrl + E</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + E</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleOpenGitPanel}>
|
||||
<RiGitBranchLine className="mr-2 h-4 w-4" />
|
||||
<span>Open Git Panel</span>
|
||||
<CommandShortcut>Ctrl + G</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + G</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleOpenTerminal}>
|
||||
<RiTerminalBoxLine className="mr-2 h-4 w-4" />
|
||||
<span>Open Terminal</span>
|
||||
<CommandShortcut>Ctrl + T</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + T</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleOpenSettings}>
|
||||
<RiSettings3Line className="mr-2 h-4 w-4" />
|
||||
<span>Open Settings</span>
|
||||
<CommandShortcut>Ctrl + ,</CommandShortcut>
|
||||
<CommandShortcut>{getModifierLabel()} + ,</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={handleReloadConfiguration}>
|
||||
<RiRestartLine className="mr-2 h-4 w-4" />
|
||||
|
||||
@@ -1,41 +1,66 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { RiAddLine, RiArrowUpSLine, RiArrowUpWideLine, RiBrainAi3Line, RiCloseCircleLine, RiCodeLine, RiCommandLine, RiGitBranchLine, RiLayoutLeftLine, RiPaletteLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, RiText } from '@remixicon/react';
|
||||
} from "@/components/ui/dialog";
|
||||
import { useUIStore } from "@/stores/useUIStore";
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowUpSLine,
|
||||
RiBrainAi3Line,
|
||||
RiCloseCircleLine,
|
||||
RiCodeLine,
|
||||
RiCommandLine,
|
||||
RiGitBranchLine,
|
||||
RiLayoutLeftLine,
|
||||
RiPaletteLine,
|
||||
RiQuestionLine,
|
||||
RiSettings3Line,
|
||||
RiTerminalBoxLine,
|
||||
RiText,
|
||||
} from "@remixicon/react";
|
||||
import { getModifierLabel } from "@/lib/utils";
|
||||
|
||||
const renderKeyToken = (token: string, index: number) => {
|
||||
const normalized = token.trim().toLowerCase();
|
||||
|
||||
if (normalized === 'ctrl' || normalized === 'control') {
|
||||
if (normalized === "ctrl" || normalized === "control") {
|
||||
return <RiArrowUpSLine key={`ctrl-${index}`} className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (normalized === 'shift' || normalized === '⇧') {
|
||||
return <RiArrowUpWideLine key={`shift-${index}`} className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (normalized === '⌘' || normalized === 'cmd' || normalized === 'command' || normalized === 'meta') {
|
||||
if (
|
||||
normalized === "⌘" ||
|
||||
normalized === "cmd" ||
|
||||
normalized === "command" ||
|
||||
normalized === "meta"
|
||||
) {
|
||||
return <RiCommandLine key={`cmd-${index}`} className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
return <span key={`key-${index}`} className="text-xs font-medium">{token.trim()}</span>;
|
||||
return (
|
||||
<span key={`key-${index}`} className="text-xs font-medium">
|
||||
{token.trim()}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderKeyCombo = (combo: string) => {
|
||||
const tokens = combo.split('+').map((token) => token.trim()).filter(Boolean);
|
||||
const tokens = combo
|
||||
.split("+")
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
if (tokens.length === 0) {
|
||||
return combo.trim();
|
||||
}
|
||||
|
||||
return tokens.map((token, index) => (
|
||||
<React.Fragment key={`${token}-${index}`}>
|
||||
{index > 0 && <span className="text-muted-foreground text-[10px]">+</span>}
|
||||
{index > 0 && (
|
||||
<span className="text-muted-foreground text-[10px]">+</span>
|
||||
)}
|
||||
{renderKeyToken(token, index)}
|
||||
</React.Fragment>
|
||||
));
|
||||
@@ -57,35 +82,85 @@ type ShortcutSection = {
|
||||
export const HelpDialog: React.FC = () => {
|
||||
const { isHelpDialogOpen, setHelpDialogOpen } = useUIStore();
|
||||
|
||||
const mod = getModifierLabel();
|
||||
|
||||
const shortcuts: ShortcutSection[] = [
|
||||
{
|
||||
category: "Navigation & Commands",
|
||||
items: [
|
||||
{ keys: ["Ctrl + X"], description: "Open Command Palette", icon: RiCommandLine },
|
||||
{ keys: ["Ctrl + H"], description: "Show Keyboard Shortcuts (this dialog)", icon: RiQuestionLine },
|
||||
{ keys: ["Ctrl + L"], description: "Toggle Session Sidebar", icon: RiLayoutLeftLine },
|
||||
{ keys: ["Ctrl + M"], description: "Open Model Selector", icon: RiBrainAi3Line },
|
||||
]
|
||||
{
|
||||
keys: [`${mod} + K`],
|
||||
description: "Open Command Palette",
|
||||
icon: RiCommandLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + H`],
|
||||
description: "Show Keyboard Shortcuts (this dialog)",
|
||||
icon: RiQuestionLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + L`],
|
||||
description: "Toggle Session Sidebar",
|
||||
icon: RiLayoutLeftLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + M`],
|
||||
description: "Open Model Selector",
|
||||
icon: RiBrainAi3Line,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Session Management",
|
||||
items: [
|
||||
{ keys: ["Ctrl + N"], description: "Create New Session", icon: RiAddLine },
|
||||
{ keys: ["Shift + Ctrl + N"], description: "Open Worktree Creator", icon: RiGitBranchLine },
|
||||
{ keys: ["Ctrl + I"], description: "Focus Chat Input", icon: RiText },
|
||||
{ keys: ["Esc + Esc"], description: "Abort active run (double press)", icon: RiCloseCircleLine },
|
||||
]
|
||||
{
|
||||
keys: [`${mod} + N`],
|
||||
description: "Create New Session",
|
||||
icon: RiAddLine,
|
||||
},
|
||||
{
|
||||
keys: [`Shift + ${mod} + N`],
|
||||
description: "Open Worktree Creator",
|
||||
icon: RiGitBranchLine,
|
||||
},
|
||||
{ keys: [`${mod} + I`], description: "Focus Chat Input", icon: RiText },
|
||||
{
|
||||
keys: ["Esc + Esc"],
|
||||
description: "Abort active run (double press)",
|
||||
icon: RiCloseCircleLine,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Interface",
|
||||
items: [
|
||||
{ keys: ["⌘ + /", "Ctrl + /"], description: "Cycle Theme (Light → Dark → System)", icon: RiPaletteLine },
|
||||
{ keys: ["Ctrl + E"], description: "Open Diff Panel", icon: RiCodeLine },
|
||||
{ keys: ["Ctrl + G"], description: "Open Git Panel", icon: RiGitBranchLine },
|
||||
{ keys: ["Ctrl + T"], description: "Open Terminal", icon: RiTerminalBoxLine },
|
||||
{ keys: ["Ctrl + ,"], description: "Open Settings", icon: RiSettings3Line },
|
||||
]
|
||||
}
|
||||
{
|
||||
keys: [`${mod} + /`],
|
||||
description: "Cycle Theme (Light → Dark → System)",
|
||||
icon: RiPaletteLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + E`],
|
||||
description: "Open Diff Panel",
|
||||
icon: RiCodeLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + G`],
|
||||
description: "Open Git Panel",
|
||||
icon: RiGitBranchLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + T`],
|
||||
description: "Open Terminal",
|
||||
icon: RiTerminalBoxLine,
|
||||
},
|
||||
{
|
||||
keys: [`${mod} + ,`],
|
||||
description: "Open Settings",
|
||||
icon: RiSettings3Line,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -117,12 +192,21 @@ export const HelpDialog: React.FC = () => {
|
||||
{shortcut.icon && (
|
||||
<shortcut.icon className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
)}
|
||||
<span className="typography-meta">{shortcut.description}</span>
|
||||
<span className="typography-meta">
|
||||
{shortcut.description}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{(Array.isArray(shortcut.keys) ? shortcut.keys : shortcut.keys.split(' / ')).map((keyCombo: string, i: number) => (
|
||||
{(Array.isArray(shortcut.keys)
|
||||
? shortcut.keys
|
||||
: shortcut.keys.split(" / ")
|
||||
).map((keyCombo: string, i: number) => (
|
||||
<React.Fragment key={`${keyCombo}-${i}`}>
|
||||
{i > 0 && <span className="typography-meta text-muted-foreground mx-1">or</span>}
|
||||
{i > 0 && (
|
||||
<span className="typography-meta text-muted-foreground mx-1">
|
||||
or
|
||||
</span>
|
||||
)}
|
||||
<kbd className="inline-flex items-center gap-1 px-1.5 py-0.5 typography-meta font-mono bg-muted rounded border border-border/20">
|
||||
{renderKeyCombo(keyCombo)}
|
||||
</kbd>
|
||||
@@ -141,11 +225,18 @@ export const HelpDialog: React.FC = () => {
|
||||
<RiQuestionLine className="h-3.5 w-3.5 text-muted-foreground mt-0.5" />
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
<p className="font-medium mb-1">Pro Tips:</p>
|
||||
<ul className="space-y-0.5 typography-meta">
|
||||
<li>• Use Command Palette (Ctrl + X) to quickly access all actions</li>
|
||||
<li>• The 5 most recent sessions appear in the Command Palette</li>
|
||||
<li>• Theme cycling remembers your preference across sessions</li>
|
||||
</ul>
|
||||
<ul className="space-y-0.5 typography-meta">
|
||||
<li>
|
||||
• Use Command Palette ({mod} + K) to quickly access all
|
||||
actions
|
||||
</li>
|
||||
<li>
|
||||
• The 5 most recent sessions appear in the Command Palette
|
||||
</li>
|
||||
<li>
|
||||
• Theme cycling remembers your preference across sessions
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, getModifierLabel } from '@/lib/utils';
|
||||
import { SIDEBAR_SECTIONS } from '@/constants/sidebar';
|
||||
import type { SidebarSection } from '@/constants/sidebar';
|
||||
import { RiArrowLeftSLine, RiCloseLine } from '@remixicon/react';
|
||||
@@ -244,7 +244,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
};
|
||||
|
||||
// Keyboard shortcut display based on platform
|
||||
const shortcutKey = isMacPlatform ? '⌘' : 'Ctrl';
|
||||
const shortcutKey = getModifierLabel();
|
||||
|
||||
// Desktop padding for Mac titlebar area
|
||||
const desktopPaddingClass = React.useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user