From acf689f97fd8a6b3dfde51c5b1c914af68f99e70 Mon Sep 17 00:00:00 2001
From: Abelino Chavez
Date: Fri, 30 Jan 2026 20:31:25 -0600
Subject: [PATCH] fix: UI improvements and multiple keyboard shortcuts support
- Add theme toggle to Projects page header (#689)
- Fix nested button HTML validation errors in header and hero (#687)
- Use asChild prop to render Links with button styles
- Add ability to add multiple keyboard shortcuts per action (#696)
- New "+" button to add additional shortcuts
- Replace mode only removes the specific key being edited
- Right-click to remove a shortcut when multiple exist
Co-Authored-By: Claude Opus 4.5
---
apps/web/src/app/projects/page.tsx | 2 +
.../editor/dialogs/shortcuts-dialog.tsx | 118 ++++++++++++++----
apps/web/src/components/header.tsx | 24 ++--
apps/web/src/components/landing/hero.tsx | 18 +--
4 files changed, 117 insertions(+), 45 deletions(-)
diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx
index b855cefc..ea1ef522 100644
--- a/apps/web/src/app/projects/page.tsx
+++ b/apps/web/src/app/projects/page.tsx
@@ -56,6 +56,7 @@ import {
import { DeleteProjectDialog } from "@/components/editor/dialogs/delete-project-dialog";
import { ProjectInfoDialog } from "@/components/editor/dialogs/project-info-dialog";
import { RenameProjectDialog } from "@/components/editor/dialogs/rename-project-dialog";
+import { ThemeToggle } from "@/components/theme-toggle";
import { cn } from "@/utils/ui";
const formatProjectDuration = ({
@@ -175,6 +176,7 @@ function ProjectsHeader() {
+
diff --git a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
index 565fe705..bb013a12 100644
--- a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
+++ b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
@@ -16,6 +16,15 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
+import { PlusSignIcon } from "@hugeicons/core-free-icons";
+import { HugeiconsIcon } from "@hugeicons/react";
+import type { ShortcutKey } from "@/types/keybinding";
+
+interface RecordingState {
+ shortcut: KeyboardShortcut;
+ mode: "add" | "replace";
+ keyToReplace?: ShortcutKey;
+}
export function ShortcutsDialog({
isOpen,
@@ -24,15 +33,15 @@ export function ShortcutsDialog({
isOpen: boolean;
onOpenChange: (open: boolean) => void;
}) {
- const [recordingShortcut, setRecordingShortcut] =
- useState(null);
+ const [recordingState, setRecordingState] = useState(
+ null,
+ );
const {
updateKeybinding,
removeKeybinding,
getKeybindingString,
validateKeybinding,
- getKeybindingsForAction,
setIsRecording,
resetToDefaults,
isRecording,
@@ -43,7 +52,7 @@ export function ShortcutsDialog({
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
useEffect(() => {
- if (!isRecording || !recordingShortcut) return;
+ if (!isRecording || !recordingState) return;
const handleKeyDown = (e: KeyboardEvent) => {
e.preventDefault();
@@ -53,30 +62,31 @@ export function ShortcutsDialog({
if (keyString) {
const conflict = validateKeybinding(
keyString,
- recordingShortcut.action,
+ recordingState.shortcut.action,
);
if (conflict) {
toast.error(
`Key "${keyString}" is already bound to "${conflict.existingAction}"`,
);
- setRecordingShortcut(null);
+ setRecordingState(null);
+ setIsRecording(false);
return;
}
- const oldKeys = getKeybindingsForAction(recordingShortcut.action);
- for (const key of oldKeys) {
- removeKeybinding(key);
+ // Only remove the specific key being replaced, not all keys
+ if (recordingState.mode === "replace" && recordingState.keyToReplace) {
+ removeKeybinding(recordingState.keyToReplace);
}
- updateKeybinding(keyString, recordingShortcut.action);
+ updateKeybinding(keyString, recordingState.shortcut.action);
setIsRecording(false);
- setRecordingShortcut(null);
+ setRecordingState(null);
}
};
const handleClickOutside = () => {
- setRecordingShortcut(null);
+ setRecordingState(null);
setIsRecording(false);
};
@@ -88,21 +98,29 @@ export function ShortcutsDialog({
document.removeEventListener("click", handleClickOutside);
};
}, [
- recordingShortcut,
+ recordingState,
getKeybindingString,
updateKeybinding,
removeKeybinding,
validateKeybinding,
- getKeybindingsForAction,
setIsRecording,
isRecording,
]);
- const handleStartRecording = (shortcut: KeyboardShortcut) => {
- setRecordingShortcut(shortcut);
+ const handleStartReplacing = (shortcut: KeyboardShortcut, key: ShortcutKey) => {
+ setRecordingState({ shortcut, mode: "replace", keyToReplace: key });
setIsRecording(true);
};
+ const handleStartAdding = (shortcut: KeyboardShortcut) => {
+ setRecordingState({ shortcut, mode: "add" });
+ setIsRecording(true);
+ };
+
+ const handleRemoveKey = (key: ShortcutKey) => {
+ removeKeybinding(key);
+ };
+
return (
-
-
+
+
Try early beta
-
-
+
+