react-keyhub
v1.1.6
Published
A lightweight, scalable keyboard shortcut manager for React applications with TypeScript support
Downloads
99
Maintainers
Readme
React KeyHub
A lightweight, scalable keyboard shortcut manager for React applications with TypeScript support.
Demo
- 🔗 Live Shortcut Sheet Demo: KeyHub Example App
- 💻 Example Source Code: GitHub Repository
Features
- 🔑 Central Configuration: Define all keyboard shortcuts in one place
- 🔄 Type Safety: Full TypeScript support for shortcut definitions and hooks
- 🎯 Optimized Performance: Single event listener with efficient lookup
- 🧩 Modular API: Subscribe to shortcuts from any component
- 📋 Built-in Shortcut Sheet: Display all registered shortcuts in a user-friendly format
- 🔌 Zero Dependencies: No external dependencies (aside from React)
- 🔄 Dynamic Updates: Enable, disable, or modify shortcuts at runtime
- 🌐 Context Awareness: Define shortcuts that only work in specific contexts
- 🔢 Sequence Support: Create shortcuts that require a sequence of key presses
- 🎨 Theming Support: Light, dark, and auto themes for the shortcut sheet
- 📱 Responsive Layouts: Modal and sidebar layouts for the shortcut sheet
- 💡 Type Suggestions: Enhanced hooks with autocomplete for registered shortcuts
Installation
npm install react-keyhub
# or
yarn add react-keyhub
Quick Start
import React, { useState } from 'react';
import {
KeyHubProvider,
useShortcut,
useKeyboardShortcut,
ShortcutSheet,
defaultShortcuts
} from 'react-keyhub';
// Define your shortcuts (or use the default ones)
const myShortcuts = {
...defaultShortcuts,
customAction: {
keyCombo: 'ctrl+k',
name: 'Custom Action',
description: 'Perform a custom action',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Custom',
type: 'regular'
},
};
// Your app component
function App() {
const [isShortcutSheetOpen, setShortcutSheetOpen] = useState(false);
// Use the enhanced hook with type suggestions
const isSaveRegistered = useShortcut('save', (e) => {
console.log('Save triggered!');
// Your save logic here
});
// You can also use the backward compatibility hooks
useKeyboardShortcut('customAction', (e) => {
console.log('Custom action triggered!');
// Your custom action logic here
});
// Toggle the shortcut sheet
useShortcut('showShortcuts', () => {
setShortcutSheetOpen(prev => !prev);
});
return (
<div>
<h1>My App</h1>
<p>Press Ctrl+/ to see all shortcuts</p>
<p>Save shortcut registered: {isSaveRegistered ? 'Yes' : 'No'}</p>
{/* Shortcut Sheet */}
<ShortcutSheet
isOpen={isShortcutSheetOpen}
onClose={() => setShortcutSheetOpen(false)}
/>
</div>
);
}
// Wrap your app with the provider
function Root() {
return (
<KeyHubProvider shortcuts={myShortcuts}>
<App />
</KeyHubProvider>
);
}
export default Root;
API Reference
KeyHubProvider
The provider component that makes shortcuts available throughout your application.
<KeyHubProvider
shortcuts={myShortcuts}
options={{
preventDefault: true,
stopPropagation: true,
debounceTime: 0,
sequenceTimeout: 1000,
ignoreInputFields: true,
ignoreModifierOnlyEvents: true
}}
>
{children}
</KeyHubProvider>
Props
shortcuts
: A record of shortcut configurationsoptions
(optional):preventDefault
: Whether to prevent the default browser behavior (default:true
)stopPropagation
: Whether to stop event propagation (default:true
)target
: The element to attach the event listener to (default:document
)debounceTime
: Debounce time in milliseconds (default:0
)sequenceTimeout
: Timeout for sequence shortcuts in milliseconds (default:1000
)ignoreInputFields
: Whether to ignore keyboard events from input fields (default:true
)ignoreModifierOnlyEvents
: Whether to ignore keyboard events that only contain modifier keys (default:true
)
useShortcut
A hook to subscribe to a keyboard shortcut with type suggestions.
// The shortcutId will have type suggestions for all registered shortcuts
// TypeScript will show an error for non-existent shortcuts
const isSaveRegistered = useShortcut('save', (e) => {
console.log('Save shortcut triggered!');
// Your save logic here
});
// The hook returns a boolean indicating if the shortcut is registered
console.log('Is save shortcut registered?', isSaveRegistered);
Parameters
shortcutId
: The ID of the shortcut to subscribe to (with type suggestions based on provider shortcuts)callback
: The callback to execute when the shortcut is triggered
Return Value
boolean
: Indicates if the shortcut is registered
Type Safety
The hook uses the actual shortcuts provided to the KeyHubProvider for type checking:
import {
ShortcutScope,
ShortcutStatus,
ShortcutType,
ShortcutSettings
} from 'react-keyhub';
// Define custom shortcuts
const myShortcuts = {
...defaultShortcuts,
customAction: {
keyCombo: 'ctrl+shift+c',
name: 'Custom Action',
description: 'A custom action shortcut',
scope: ShortcutScope.GLOBAL,
priority: 100,
status: ShortcutStatus.ENABLED,
group: 'Custom',
type: ShortcutType.REGULAR
}
} as ShortcutSettings;
// In your component
function MyComponent() {
// This will work fine
useShortcut('customAction', () => {});
// This will cause a TypeScript error
useShortcut('nonExistentShortcut', () => {});
}
Error Handling
The hook checks if the shortcut is registered and provides a warning if it's not:
// This will log a warning if 'nonExistentShortcut' is not registered
useShortcut('nonExistentShortcut', (e) => {});
// Warning: Shortcut "nonExistentShortcut" is not registered. Available shortcuts: save, saveAs, print, ...
useKeyboardShortcut
and useKey
For backward compatibility, useKeyboardShortcut
and useKey
are also available as aliases for useShortcut
:
// These are all equivalent
useShortcut('save', callback);
useKeyboardShortcut('save', callback);
useKey('save', callback);
AvailableShortcuts
A type that provides suggestions for all registered shortcuts based on what's provided to the KeyHubProvider:
import { AvailableShortcuts } from 'react-keyhub';
// This will have type suggestions for all registered shortcuts
// based on what's provided to the KeyHubProvider
const shortcutId: AvailableShortcuts = 'save';
// If you've added a custom shortcut, it will be included in the suggestions
const customShortcutId: AvailableShortcuts = 'customAction'; // Works if customAction is registered
getRegisteredShortcuts
A function to get all registered shortcuts from the current provider:
import { getRegisteredShortcuts } from 'react-keyhub';
function MyComponent() {
// This will return the shortcuts from the current provider
const shortcuts = getRegisteredShortcuts();
return (
<div>
<h2>Registered Shortcuts</h2>
<ul>
{Object.entries(shortcuts).map(([id, config]) => (
<li key={id}>
{id}: {config.name} - {config.type === 'regular' ? config.keyCombo : config.sequence}
</li>
))}
</ul>
</div>
);
}
useShortcutSheet
A hook to get all registered shortcuts.
const shortcuts = useShortcutSheet();
useShortcutStatus
A hook to enable or disable a shortcut.
useShortcutStatus('save', true); // Enable the "save" shortcut
useShortcutStatus('save', false); // Disable the "save" shortcut
useShortcutUpdate
A hook to update a shortcut configuration.
useShortcutUpdate('save', {
keyCombo: 'ctrl+shift+s',
priority: 200,
});
useShortcutRegister
A hook to register a new shortcut dynamically.
useShortcutRegister('myDynamicShortcut', {
keyCombo: 'ctrl+d',
name: 'Dynamic Shortcut',
description: 'A dynamically registered shortcut',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Dynamic',
type: 'regular'
});
useShortcutContext
A hook to set the active context.
useShortcutContext('editor'); // Set the active context to "editor"
useShortcutContext(null); // Clear the active context
useShortcutPause
A hook to pause and resume the event bus.
useShortcutPause(true); // Pause all shortcuts
useShortcutPause(false); // Resume all shortcuts
useShortcutGroups
A hook to get all shortcut groups.
const groups = useShortcutGroups(); // Returns an array of group names
useShortcutsByGroup
A hook to get shortcuts by group.
const fileShortcuts = useShortcutsByGroup('File'); // Returns all shortcuts in the "File" group
useKeyHub
A hook to access the KeyHub event bus directly.
const eventBus = useKeyHub();
// Now you can use the event bus methods
eventBus.on('save', callback);
eventBus.off(subscriptionId);
eventBus.enableShortcut('save');
eventBus.disableShortcut('save');
eventBus.updateShortcut('save', { priority: 200 });
eventBus.registerShortcut('myShortcut', { ... });
eventBus.unregisterShortcut('myShortcut');
eventBus.setContext('editor');
eventBus.getContext();
eventBus.pause();
eventBus.resume();
eventBus.getShortcuts();
eventBus.getShortcutsByGroup('File');
eventBus.getShortcutGroups();
ShortcutSheet
A component to display all registered shortcuts.
<ShortcutSheet
isOpen={isOpen}
onClose={handleClose}
theme="light" // 'light', 'dark', or 'auto'
layout="modal" // 'modal' or 'sidebar'
filter={{
scope: 'global',
search: 'save',
group: 'File',
context: 'editor'
}}
/>
Props
isOpen
: Whether to show the shortcut sheetonClose
: Callback to close the shortcut sheettheme
(optional): Theme for the shortcut sheet ('light'
,'dark'
, or'auto'
)layout
(optional): Layout for the shortcut sheet ('modal'
or'sidebar'
)filter
(optional): Filter for the shortcuts to displayscope
(optional): Filter by scope ('global'
or'local'
)search
(optional): Filter by search termgroup
(optional): Filter by groupcontext
(optional): Filter by context
className
(optional): Custom class name for the shortcut sheet
ShortcutSheetStyles
A string of CSS styles for the ShortcutSheet component.
import { ShortcutSheetStyles } from 'react-keyhub';
// Add the styles to your app
const App = () => (
<>
<style>{ShortcutSheetStyles}</style>
{/* Your app content */}
</>
);
Shortcut Configuration
Each shortcut is defined with the following properties:
Regular Shortcut
{
keyCombo: 'ctrl+s',
name: 'Save',
description: 'Save the current document',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'File',
context: 'editor', // Optional
type: 'regular',
action: (e) => { /* Optional default action */ }
}
Sequence Shortcut
{
sequence: 'g c', // 'g' followed by 'c'
name: 'Git Commands',
description: 'Show git commands menu',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Git',
context: 'editor', // Optional
type: 'sequence',
action: (e) => { /* Optional default action */ }
}
Properties
keyCombo
(for regular shortcuts): The key combination (e.g.,'ctrl+s'
,'ctrl+shift+n'
)sequence
(for sequence shortcuts): The sequence of key combinations (e.g.,'g c'
for "g" followed by "c")name
: A human-readable name for the shortcutdescription
: A detailed description of what the shortcut doesscope
: Either'global'
or'local'
priority
: The priority of the shortcut (higher numbers take precedence)status
: Either'enabled'
or'disabled'
group
: A group for the shortcut (used for organizing in the shortcut sheet)context
(optional): A context for the shortcut (only active when the context matches)type
: Either'regular'
or'sequence'
action
(optional): A default action to execute when the shortcut is triggered
Default Shortcuts
React KeyHub comes with a set of default shortcuts organized by groups:
File Operations
save
: Ctrl+SsaveAs
: Ctrl+Shift+Sprint
: Ctrl+PnewWindow
: Ctrl+Shift+N
Edit Operations
find
: Ctrl+Freplace
: Ctrl+Hundo
: Ctrl+Zredo
: Ctrl+Ycut
: Ctrl+Xcopy
: Ctrl+Cpaste
: Ctrl+VselectAll
: Ctrl+A
Navigation
goToLine
: Ctrl+GgoToFile
: Ctrl+P (lower priority than print)
Help
help
: F1showShortcuts
: Ctrl+/
Git (Sequence Shortcuts)
gitCommands
: g c (press "g" then "c")gitStatus
: g s (press "g" then "s")
Vim Navigation (Context-Specific)
vimUp
: k (only active in "vim" context)vimDown
: j (only active in "vim" context)vimLeft
: h (only active in "vim" context)vimRight
: l (only active in "vim" context)
You can use these as a starting point and override or extend them as needed.
Advanced Usage
Context-Aware Shortcuts
// Define shortcuts with contexts
const myShortcuts = {
...defaultShortcuts,
editorSave: {
keyCombo: 'ctrl+s',
name: 'Save',
description: 'Save the current document',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'File',
context: 'editor', // Only active in "editor" context
type: 'regular'
},
terminalClear: {
keyCombo: 'ctrl+l',
name: 'Clear Terminal',
description: 'Clear the terminal',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Terminal',
context: 'terminal', // Only active in "terminal" context
type: 'regular'
}
};
// In your component, set the active context
function EditorComponent() {
// Set the active context to "editor"
useShortcutContext('editor');
// ...
}
function TerminalComponent() {
// Set the active context to "terminal"
useShortcutContext('terminal');
// ...
}
Sequence Shortcuts
// Define sequence shortcuts
const myShortcuts = {
...defaultShortcuts,
gitCommit: {
sequence: 'g c', // Press "g" then "c"
name: 'Git Commit',
description: 'Open git commit dialog',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Git',
type: 'sequence'
}
};
// Subscribe to the sequence shortcut
function MyComponent() {
useShortcut('gitCommit', () => {
console.log('Git commit dialog opened');
});
// ...
}
Dynamic Shortcut Registration
function MyComponent() {
// Register a dynamic shortcut
useShortcutRegister('dynamicShortcut', {
keyCombo: 'ctrl+d',
name: 'Dynamic Shortcut',
description: 'A dynamically registered shortcut',
scope: 'global',
priority: 100,
status: 'enabled',
group: 'Dynamic',
type: 'regular',
action: () => {
console.log('Dynamic shortcut triggered!');
}
});
// The shortcut will be automatically unregistered when the component unmounts
// ...
}
Pausing and Resuming Shortcuts
function MyComponent() {
const [isPaused, setIsPaused] = useState(false);
// Pause or resume all shortcuts
useShortcutPause(isPaused);
return (
<div>
<button onClick={() => setIsPaused(!isPaused)}>
{isPaused ? 'Resume Shortcuts' : 'Pause Shortcuts'}
</button>
</div>
);
}
Themed Shortcut Sheet
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
const [theme, setTheme] = useState<'light' | 'dark' | 'auto'>('auto');
return (
<div>
<button onClick={() => setIsOpen(true)}>Show Shortcuts</button>
<select value={theme} onChange={(e) => setTheme(e.target.value as any)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="auto">Auto (System)</option>
</select>
<ShortcutSheet
isOpen={isOpen}
onClose={() => setIsOpen(false)}
theme={theme}
/>
</div>
);
}
Browser Support
React KeyHub works in all modern browsers that support React.
License
MIT