Make keyboard shortcuts consistent (#20326)

* Make keyboard shortcuts consistent

* Cleanup

* Refactor prevent default to not require separate input

* Fix

* Implement escape for reviews

* Implement escape for explore

* Send content ref to get page changes for free
This commit is contained in:
Nicolas Mowen
2025-10-02 07:21:37 -06:00
committed by GitHub
parent 85ace6a6be
commit 2030809a6d
18 changed files with 231 additions and 122 deletions
+52 -11
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react";
import { MutableRefObject, useCallback, useEffect, useMemo } from "react";
export type KeyModifiers = {
down: boolean;
@@ -9,9 +9,17 @@ export type KeyModifiers = {
export default function useKeyboardListener(
keys: string[],
listener: (key: string | null, modifiers: KeyModifiers) => void,
preventDefault: boolean = true,
listener?: (key: string | null, modifiers: KeyModifiers) => boolean,
contentRef?: MutableRefObject<HTMLDivElement | null>,
) {
const pageKeys = useMemo(
() =>
contentRef != undefined
? ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
: [],
[contentRef],
);
const keyDownListener = useCallback(
(e: KeyboardEvent) => {
// @ts-expect-error we know this field exists
@@ -26,14 +34,44 @@ export default function useKeyboardListener(
shift: e.shiftKey,
};
if (keys.includes(e.key)) {
if (contentRef && pageKeys.includes(e.key)) {
switch (e.key) {
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
break;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
break;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
}
} else if (keys.includes(e.key) && listener) {
const preventDefault = listener(e.key, modifiers);
if (preventDefault) e.preventDefault();
listener(e.key, modifiers);
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
} else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")
) {
listener(null, modifiers);
}
},
[keys, listener, preventDefault],
[keys, pageKeys, listener, contentRef],
);
const keyUpListener = useCallback(
@@ -49,10 +87,13 @@ export default function useKeyboardListener(
shift: false,
};
if (keys.includes(e.key)) {
e.preventDefault();
listener(e.key, modifiers);
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
if (listener && keys.includes(e.key)) {
const preventDefault = listener(e.key, modifiers);
if (preventDefault) e.preventDefault();
} else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")
) {
listener(null, modifiers);
}
},