Skip to content

Commit 711bb2d

Browse files
kevin9327claudedavidmckayv
authored
Toggle the sidebar with Ctrl+B on a layout that does not write Latin letters (#600)
SidebarToggle's tooltip names Ctrl+B, or ⌘B on a Mac, and the sidebar's listener compared KeyboardEvent.key with "b". On a Russian layout the B key writes "и" and on Greek it writes "β", so the shortcut the tooltip names did nothing for anybody with one of those layouts selected. Shift+N had the same miss and was fixed with keyOf, which the paste shortcut on a Bot's screen now reads too. The sidebar reads it as well: the letter a layout writes when it writes one in ASCII, and the physical key when it writes another script. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: David McKay <david@copilotkit.ai>
1 parent 37201c5 commit 711bb2d

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
88

99
## Unreleased
1010

11+
### Ctrl+B shows and hides the sidebar on a layout that does not write Latin letters
12+
13+
The sidebar toggle's tooltip names Ctrl+B, or ⌘B on a Mac, and the shortcut was recognised by the
14+
character the key writes. On a Russian or Greek layout the B key writes "и" or "β", so the shortcut
15+
did nothing there. It is now read the way the app's own shortcuts read it since Shift+N was fixed
16+
for the same layouts: from the physical key when the layout writes a character outside ASCII there.
1117
### Scrolling a Bot's browser no longer scrolls or zooms the page around it
1218

1319
While somebody drives a Bot's browser, a turn of the mouse wheel over its screen is sent to it, and

app/src/components/ui/sidebar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useRender } from "@base-ui/react/use-render";
66
import { cva, type VariantProps } from "class-variance-authority";
77

88
import { useIsMobile } from "@/hooks/use-mobile";
9+
import { keyOf } from "@/lib/hotkeys/hotkeys";
910
import { cn } from "@/lib/utils";
1011
import { Button } from "@/components/ui/button";
1112
import { Input } from "@/components/ui/input";
@@ -112,8 +113,10 @@ function SidebarProvider({
112113
// Adds a keyboard shortcut to toggle the sidebar.
113114
React.useEffect(() => {
114115
const handleKeyDown = (event: KeyboardEvent) => {
116+
// `keyOf` rather than `key`, as the app's own shortcuts read it: on a layout that writes
117+
// another script the B key writes "и" or "β", and only its `code` still says B.
115118
if (
116-
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
119+
keyOf(event) === SIDEBAR_KEYBOARD_SHORTCUT &&
117120
(event.metaKey || event.ctrlKey)
118121
) {
119122
event.preventDefault();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { afterAll, afterEach, beforeAll, expect, test } from "bun:test";
2+
import { GlobalRegistrator } from "@happy-dom/global-registrator";
3+
import { act, cleanup, render } from "@testing-library/react";
4+
import { SidebarProvider, useSidebar } from "@/components/ui/sidebar";
5+
6+
/**
7+
* The sidebar shortcut on a keyboard layout that does not write Latin letters.
8+
*
9+
* The toggle's tooltip names it as Ctrl+B, or ⌘B on a Mac, and the sidebar's listener compared
10+
* `KeyboardEvent.key` with "b". On Russian the B key writes "и", and on Greek it writes "β", so the
11+
* shortcut the tooltip names never fired for anybody with one of those layouts selected. It is the
12+
* same miss Shift+N and the paste shortcut on a Bot's screen had.
13+
*/
14+
15+
beforeAll(() => {
16+
GlobalRegistrator.register();
17+
});
18+
19+
afterEach(() => {
20+
cleanup();
21+
});
22+
23+
afterAll(() => {
24+
GlobalRegistrator.unregister();
25+
});
26+
27+
function SidebarState() {
28+
return <output>{useSidebar().open ? "open" : "closed"}</output>;
29+
}
30+
31+
type Modifiers = Partial<Pick<KeyboardEvent, "ctrlKey" | "metaKey">>;
32+
33+
/**
34+
* A sidebar, and a way to press a key at it that answers with the state it is left in.
35+
*
36+
* The keydown is the one a browser reports: `key` from the layout, `code` from the physical key.
37+
*/
38+
function renderSidebar() {
39+
const { container } = render(
40+
<SidebarProvider>
41+
<SidebarState />
42+
</SidebarProvider>,
43+
);
44+
return (key: string, code: string, modifiers: Modifiers) => {
45+
act(() => {
46+
window.dispatchEvent(
47+
new KeyboardEvent("keydown", {
48+
key,
49+
code,
50+
...modifiers,
51+
bubbles: true,
52+
cancelable: true,
53+
}),
54+
);
55+
});
56+
return container.querySelector("output")?.textContent;
57+
};
58+
}
59+
60+
test("Ctrl+B and Cmd+B toggle the sidebar on a layout that writes another script", () => {
61+
const press = renderSidebar();
62+
63+
expect(press("и", "KeyB", { ctrlKey: true })).toBe("closed");
64+
expect(press("β", "KeyB", { metaKey: true })).toBe("open");
65+
});
66+
67+
test("a layout that writes Latin letters still goes by the letter, wherever its key is", () => {
68+
const press = renderSidebar();
69+
70+
// Dvorak writes B on the key QWERTY calls N, and X on the key QWERTY calls B.
71+
expect(press("b", "KeyN", { ctrlKey: true })).toBe("closed");
72+
expect(press("x", "KeyB", { ctrlKey: true })).toBe("closed");
73+
});
74+
75+
test("another letter, or the B key without its modifier, still does nothing", () => {
76+
const press = renderSidebar();
77+
78+
expect(press("т", "KeyN", { ctrlKey: true })).toBe("open");
79+
expect(press("и", "KeyB", {})).toBe("open");
80+
});

0 commit comments

Comments
 (0)