ux: improvements to keyboard navigation

This commit is contained in:
Alejandro Gómez
2025-12-18 16:14:56 +01:00
parent a6650ff6e1
commit 967f1eb89b
2 changed files with 53 additions and 6 deletions

View File

@@ -6,7 +6,12 @@ import { LayoutControls } from "./LayoutControls";
import { useEffect } from "react";
export function TabBar() {
const { state, setActiveWorkspace, createWorkspace } = useGrimoire();
const {
state,
setActiveWorkspace,
createWorkspace,
createWorkspaceWithNumber,
} = useGrimoire();
const { workspaces, activeWorkspaceId } = state;
const handleNewTab = () => {
@@ -18,24 +23,39 @@ export function TabBar() {
(a, b) => a.number - b.number,
);
// Keyboard shortcut: Cmd+1-9 to switch workspaces by position
// Keyboard shortcut: Cmd+1-9 to switch (or create) workspaces by number
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Check for Cmd/Ctrl + number key (1-9)
if ((e.metaKey || e.ctrlKey) && e.key >= "1" && e.key <= "9") {
const index = Number.parseInt(e.key, 10) - 1; // Convert key to array index
const targetWorkspace = sortedWorkspaces[index];
e.preventDefault(); // Prevent browser default (like Cmd+1 = first tab)
const desiredNumber = Number.parseInt(e.key, 10);
// Safety check: ensure valid workspace number (1-9)
if (desiredNumber < 1 || desiredNumber > 9) {
return;
}
// Find workspace with this number
const targetWorkspace = sortedWorkspaces.find(
(ws) => ws.number === desiredNumber,
);
if (targetWorkspace) {
e.preventDefault(); // Prevent browser default (like Cmd+1 = first tab)
// Workspace exists - switch to it
setActiveWorkspace(targetWorkspace.id);
} else {
// Workspace doesn't exist - create it and switch to it
createWorkspaceWithNumber(desiredNumber);
// Note: We don't need to explicitly switch - createWorkspace sets it as active
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [sortedWorkspaces, setActiveWorkspace]);
}, [sortedWorkspaces, setActiveWorkspace, createWorkspaceWithNumber]);
return (
<>

View File

@@ -142,6 +142,32 @@ export const useGrimoire = () => {
});
}, [setState]);
const createWorkspaceWithNumber = useCallback(
(number: number) => {
setState((prev) => {
// Check if we're leaving an empty workspace and should auto-remove it
const currentWorkspace = prev.workspaces[prev.activeWorkspaceId];
const shouldDeleteCurrent =
currentWorkspace &&
currentWorkspace.windowIds.length === 0 &&
Object.keys(prev.workspaces).length > 1;
if (shouldDeleteCurrent) {
// Delete the empty workspace, then create new one
const afterDelete = Logic.deleteWorkspace(
prev,
prev.activeWorkspaceId,
);
return Logic.createWorkspace(afterDelete, number);
}
// Normal workspace creation
return Logic.createWorkspace(prev, number);
});
},
[setState],
);
const addWindow = useCallback(
(appId: AppId, props: any, commandString?: string, customTitle?: string) =>
setState((prev) =>
@@ -250,6 +276,7 @@ export const useGrimoire = () => {
locale: state.locale || browserLocale,
activeWorkspace: state.workspaces[state.activeWorkspaceId],
createWorkspace,
createWorkspaceWithNumber,
addWindow,
updateWindow,
removeWindow,