Component properties.
Whether the panel is visible.
Callback invoked when panel should close.
Shortcuts modal or null when closed.
export function ShortcutsPanel({
isOpen,
onClose,
}: Readonly<ShortcutsPanelProps>) {
const [searchQuery, setSearchQuery] = useState("");
const searchInputRef = useRef<HTMLInputElement>(null);
// Manage autofocus: focus after a small delay when dialog opens
useEffect(() => {
if (isOpen) {
const timer = globalThis.setTimeout(() => {
if (searchInputRef.current) {
searchInputRef.current.focus();
}
}, 100);
return () => globalThis.clearTimeout(timer);
}
return undefined;
}, [isOpen]);
// Memoize categories to prevent recomputation
const allCategories = useMemo(() => Object.values(ShortcutCategory), []);
// Filter shortcuts by search query
const filteredShortcuts = useMemo(() => {
if (!searchQuery.trim()) {
return SHORTCUTS;
}
// Precompute keyLabel for each shortcut to enable string-based search
const shortcutsWithKeyLabels = SHORTCUTS.map((shortcut) => ({
...shortcut,
keyLabel: formatShortcutKey(shortcut.keys),
}));
const fuse = buildFuse(shortcutsWithKeyLabels, [
{ name: "description", weight: 0.5 },
{ name: "action", weight: 0.3 },
{ name: "keyLabel", weight: 0.2 },
]);
const results = fuse.search(searchQuery);
return results.map((result) => result.item);
}, [searchQuery]);
// Get shortcuts filtered by category
const getFilteredByCategory = (category: ShortcutCategory) => {
return filteredShortcuts.filter((s) => s.category === category);
};
// Check if search has results
const hasResults = filteredShortcuts.length > 0;
// Handle Escape key to close panel
const handleEscapeKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
onClose();
}
};
return (
<AnimatePresence>
{isOpen && (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<DialogContent
className="max-h-[80vh] max-w-3xl overflow-hidden rounded-2xl border-white/10 bg-slate-950/90 p-6 shadow-2xl backdrop-blur-xl"
onKeyDown={handleEscapeKeyDown}
>
<DialogHeader>
<div className="flex items-center gap-3">
<div className="rounded-full bg-blue-500/20 p-2">
<Keyboard className="h-5 w-5 text-blue-400" />
</div>
<div>
<DialogTitle className="text-2xl">
Keyboard Shortcuts
</DialogTitle>
<DialogDescription>
Navigate and control the app using your keyboard
</DialogDescription>
</div>
</div>
</DialogHeader>
{/* Search Input */}
<div className="relative">
<Search className="text-muted-foreground absolute left-3 top-3 h-4 w-4" />
<Input
ref={searchInputRef}
placeholder="Search shortcuts by name or key..."
className="border-white/10 bg-white/5 pl-10 focus:bg-white/10"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
aria-label="Search shortcuts"
aria-describedby="shortcuts-search-status"
aria-controls="shortcuts-list"
/>
</div>
{/* Shortcuts Display */}
<ScrollArea className="h-[400px] w-full rounded-lg border border-white/10 bg-white/5 p-4">
{/* Announcements for search results */}
{searchQuery && (
<output
id="shortcuts-search-status"
className="sr-only"
aria-live="polite"
aria-atomic="true"
>
{(() => {
const countText =
filteredShortcuts.length === 1 ? "shortcut" : "shortcuts";
return hasResults
? `Found ${filteredShortcuts.length} ${countText}`
: `No shortcuts found matching "${searchQuery}"`;
})()}
</output>
)}
<section id="shortcuts-list" aria-label="Search results">
{hasResults ? (
<div className="space-y-6 pr-4">
{searchQuery ? (
// Show all matching shortcuts when searching
<div className="space-y-2">
{filteredShortcuts.map((shortcut) => (
<ShortcutCard
key={shortcut.id}
shortcutItem={shortcut}
/>
))}
</div>
) : (
// Show organized by category using memoized categories
<>
{allCategories.map((category) => (
<ShortcutCategorySection
key={category}
category={category}
categoryShortcuts={getFilteredByCategory(category)}
/>
))}
</>
)}
</div>
) : (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<p className="text-muted-foreground">
No shortcuts found matching "{searchQuery}"
</p>
</div>
</div>
)}
</section>
</ScrollArea>
{/* Footer */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="text-muted-foreground flex items-center justify-between border-t border-white/10 pt-4 text-xs"
>
<p>
Press{" "}
<kbd className="rounded bg-white/10 px-1.5 py-0.5 font-mono">
?
</kbd>{" "}
or{" "}
<kbd className="rounded bg-white/10 px-1.5 py-0.5 font-mono">
Ctrl+/
</kbd>{" "}
to toggle this panel
</p>
<Badge variant="outline">
{SHORTCUTS.length} shortcuts total
</Badge>
</motion.div>
</DialogContent>
</Dialog>
)}
</AnimatePresence>
);
}
Modal dialog for displaying keyboard shortcuts with search functionality.