Skip to content

Commit 3d5f820

Browse files
committed
fix: sidebar keyboard shortcuts
1 parent 0eed84e commit 3d5f820

File tree

4 files changed

+41
-62
lines changed

4 files changed

+41
-62
lines changed

registry/sidebar/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ export { sidebarStore, sidebarActions } from "./sidebar-store";
2121
export type { SidebarState, SidebarStoreState } from "./sidebar-store";
2222

2323
// Script for zero-flash loading and keyboard shortcuts
24-
export { SidebarScript, SidebarKeyboardHandler } from "./sidebar-script";
24+
export { SidebarScript } from "./sidebar-script";

registry/sidebar/sidebar-script.tsx

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
* ```
1818
*/
1919

20-
import * as React from "react";
21-
import { sidebarActions, sidebarStore } from "./sidebar-store";
22-
2320
export function SidebarScript() {
2421
// This script runs BEFORE React hydration
2522
const script = `
@@ -49,46 +46,3 @@ export function SidebarScript() {
4946
);
5047
}
5148

52-
/**
53-
* Sidebar Keyboard Shortcuts Handler
54-
*
55-
* Add this component near the root of your app to enable keyboard shortcuts
56-
* for toggling sidebars. It listens for keyboard events and toggles the
57-
* appropriate sidebar based on registered shortcuts.
58-
*
59-
* Usage:
60-
* ```tsx
61-
* <SidebarKeyboardHandler />
62-
* ```
63-
*/
64-
export function SidebarKeyboardHandler() {
65-
React.useEffect(() => {
66-
const handleKeyDown = (event: KeyboardEvent) => {
67-
// Check if Cmd (Mac) or Ctrl (Windows/Linux) is pressed
68-
if (!(event.metaKey || event.ctrlKey)) {
69-
return;
70-
}
71-
72-
// Get the key pressed (lowercase)
73-
const key = event.key.toLowerCase();
74-
75-
// Check if this key matches any registered shortcut
76-
// We use the format "mod+key" where mod is cmd/ctrl
77-
const shortcut = `mod+${key}`;
78-
79-
// Check if this shortcut is registered
80-
const sidebarId = sidebarStore.state.keyboardShortcuts[shortcut];
81-
if (sidebarId) {
82-
// Prevent default browser behavior (e.g., Cmd+B opening bookmarks)
83-
event.preventDefault();
84-
// Toggle the sidebar
85-
sidebarActions.toggleSidebar(sidebarId);
86-
}
87-
};
88-
89-
window.addEventListener("keydown", handleKeyDown);
90-
return () => window.removeEventListener("keydown", handleKeyDown);
91-
}, []);
92-
93-
return null;
94-
}

registry/sidebar/sidebar.tsx

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import * as React from "react";
44
import { useStore } from "@tanstack/react-store";
5-
import { ChevronDownIcon, PanelLeftIcon } from "lucide-react";
5+
import { PanelLeftIcon } from "lucide-react";
66
import { cn } from "@/lib/utils";
77
import { Button } from "@/components/ui/button";
88
import {
@@ -24,7 +24,6 @@ import {
2424
PopoverTrigger,
2525
} from "@/components/ui/popover";
2626
import { sidebarStore, sidebarActions } from "./sidebar-store";
27-
import { Skeleton } from "@/components/ui/skeleton";
2827
import { IconChevronRight } from "@tabler/icons-react";
2928

3029
const SIDEBAR_WIDTH = "16rem";
@@ -103,29 +102,53 @@ export function Sidebar({
103102
// Register sidebar on mount
104103
React.useEffect(() => {
105104
if (hasRegistered.current) return;
106-
105+
106+
const normalizedShortcut =
107+
keyboardShortcut && keyboardShortcut.length === 1
108+
? `mod+${keyboardShortcut}`
109+
: keyboardShortcut;
110+
107111
const existing = sidebarStore.state.sidebars[id];
108112
if (!existing) {
109113
sidebarActions.registerSidebar(id, {
110114
open: defaultOpen,
111115
variant,
112116
side,
113117
openMobile: false,
114-
keyboardShortcut,
118+
keyboardShortcut: normalizedShortcut,
115119
});
116-
} else if (keyboardShortcut !== existing.keyboardShortcut) {
120+
} else if (normalizedShortcut !== existing.keyboardShortcut) {
117121
// Update keyboard shortcut if changed
118-
sidebarActions.setKeyboardShortcut(id, keyboardShortcut);
122+
sidebarActions.setKeyboardShortcut(id, normalizedShortcut);
119123
}
120-
124+
121125
hasRegistered.current = true;
122-
126+
123127
return () => {
124128
hasRegistered.current = false;
125129
sidebarActions.unregisterSidebar(id);
126130
};
127131
}, [id, defaultOpen, variant, side, keyboardShortcut]);
128132

133+
// Keyboard shortcut handler
134+
React.useEffect(() => {
135+
if (!keyboardShortcut) return;
136+
137+
const handleKeyDown = (event: KeyboardEvent) => {
138+
// Only support mod+key for now
139+
if (!event.metaKey && !event.ctrlKey) return;
140+
141+
const key = keyboardShortcut.replace(/mod\+/i, "").toLowerCase();
142+
if (event.key.toLowerCase() === key) {
143+
event.preventDefault();
144+
sidebarActions.toggleSidebar(id);
145+
}
146+
};
147+
148+
window.addEventListener("keydown", handleKeyDown);
149+
return () => window.removeEventListener("keydown", handleKeyDown);
150+
}, [id, keyboardShortcut]);
151+
129152
// Update CSS variable when sidebar state changes
130153
React.useEffect(() => {
131154
if (sidebar) {
@@ -189,16 +212,12 @@ export function Sidebar({
189212
minWidth: `var(--sidebar-${id}-width, ${defaultOpen ? width : collapsedWidth})`,
190213
maxWidth: `var(--sidebar-${id}-width, ${defaultOpen ? width : collapsedWidth})`
191214
}}
192-
className={cn(baseStyles, variantStyles[variant], className, "animate-pulse border-transparent")}
215+
className={cn(baseStyles, variantStyles[variant], className, "")}
193216
{...props}
194217
>
195218
{/* Skeleton - no content on server */}
196219
<div className="flex h-full flex-col overflow-hidden" style={{ width: `var(--sidebar-${id}-width, ${defaultOpen ? width : collapsedWidth})` }} >
197-
<div className="flex flex-col gap-0.5 p-2">
198-
<Skeleton className="w-full h-9 opacity-10"/>
199-
<Skeleton className="w-full h-9 opacity-10"/>
200-
<Skeleton className="w-full h-9 opacity-10"/>
201-
</div>
220+
202221
</div>
203222
</aside>
204223
</div>

src/components/layout/sidebar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ export function MainSidebar() {
5050
const isSidebarOpen = sidebarStore.state.sidebars[sidebarId]?.open ?? true;
5151

5252
return (
53-
<Sidebar id={sidebarId} collapsible variant="floating" className="">
53+
<Sidebar
54+
id={sidebarId}
55+
collapsible
56+
variant="floating"
57+
className=""
58+
keyboardShortcut="p"
59+
>
5460
<SidebarHeader>
5561
<SidebarMenuItem>
5662
<Link to="/" className="w-full">

0 commit comments

Comments
 (0)