mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat: Add keyboard shortcuts for navigation and action buttons
- Created use-keyboard-shortcuts hook to manage global keyboard shortcuts - Added navigation shortcuts: K (Kanban), A (Agent), E (Spec Editor), C (Context), T (Tools), S (Settings) - Added action shortcuts: N (Add Feature on board), F (Add File on context) - Shortcuts automatically disabled when typing in inputs/textareas or when dialogs are open - Display shortcut key indicators in navigation links and action buttons - Added test utilities for keyboard shortcut testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
109
app/src/hooks/use-keyboard-shortcuts.ts
Normal file
109
app/src/hooks/use-keyboard-shortcuts.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useCallback } from "react";
|
||||
|
||||
export interface KeyboardShortcut {
|
||||
key: string;
|
||||
action: () => void;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the currently focused element is an input, textarea, or contenteditable element
|
||||
* or if an autocomplete/typeahead dropdown is open
|
||||
*/
|
||||
function isInputFocused(): boolean {
|
||||
const activeElement = document.activeElement;
|
||||
if (!activeElement) return false;
|
||||
|
||||
// Check if it's a form input element
|
||||
const tagName = activeElement.tagName.toLowerCase();
|
||||
if (tagName === "input" || tagName === "textarea" || tagName === "select") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a contenteditable element
|
||||
if (activeElement.getAttribute("contenteditable") === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it has a role of textbox or searchbox
|
||||
const role = activeElement.getAttribute("role");
|
||||
if (role === "textbox" || role === "searchbox" || role === "combobox") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for autocomplete/typeahead dropdowns being open
|
||||
const autocompleteList = document.querySelector(
|
||||
'[data-testid="category-autocomplete-list"]'
|
||||
);
|
||||
if (autocompleteList) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for any open dialogs
|
||||
const dialog = document.querySelector('[role="dialog"][data-state="open"]');
|
||||
if (dialog) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manage keyboard shortcuts
|
||||
* Shortcuts won't fire when user is typing in inputs, textareas, or when dialogs are open
|
||||
*/
|
||||
export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
// Don't trigger shortcuts when typing in inputs
|
||||
if (isInputFocused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't trigger if any modifier keys are pressed (except for specific combos we want)
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find matching shortcut
|
||||
const matchingShortcut = shortcuts.find(
|
||||
(shortcut) => shortcut.key.toLowerCase() === event.key.toLowerCase()
|
||||
);
|
||||
|
||||
if (matchingShortcut) {
|
||||
event.preventDefault();
|
||||
matchingShortcut.action();
|
||||
}
|
||||
},
|
||||
[shortcuts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [handleKeyDown]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut definitions for navigation
|
||||
*/
|
||||
export const NAV_SHORTCUTS: Record<string, string> = {
|
||||
board: "K", // K for Kanban
|
||||
agent: "A", // A for Agent
|
||||
spec: "E", // E for Editor (Spec)
|
||||
context: "C", // C for Context
|
||||
tools: "T", // T for Tools
|
||||
settings: "S", // S for Settings
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcut definitions for add buttons
|
||||
*/
|
||||
export const ACTION_SHORTCUTS: Record<string, string> = {
|
||||
addFeature: "N", // N for New feature
|
||||
addContextFile: "F", // F for File (add context file)
|
||||
};
|
||||
Reference in New Issue
Block a user