import { useEffect, useLayoutEffect, useRef } from "react"; import { navigate } from "../router"; import { navItems } from "./nav"; const typing = (t: EventTarget | null) => t instanceof HTMLElement && (t.isContentEditable || t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT"); const overlayOpen = () => !!document.querySelector("dialog[open], [role=dialog], [role=menu]"); /** ⌘B (Ctrl+B) folds the sidebar, ⌘K opens search, G then a letter jumps to a page. */ export function useShellKeys(actions: { toggle: () => void; search: () => void }) { const ref = useRef(actions); useLayoutEffect(() => { ref.current = actions; }); useEffect(() => { let gAt = 0; const onKey = (e: KeyboardEvent) => { const key = e.key.toLowerCase(); const mod = (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey; if (mod && key === "b" && !(e.target instanceof HTMLElement && e.target.isContentEditable)) { e.preventDefault(); ref.current.toggle(); return; } if (mod && key === "k") { e.preventDefault(); ref.current.search(); return; } if (e.metaKey || e.ctrlKey || e.altKey || e.defaultPrevented || typing(e.target) || overlayOpen()) return; const after = Date.now() - gAt < 1200; gAt = key === "g" && !after ? Date.now() : 0; const item = after ? navItems.find((n) => n.key.toLowerCase() === key) : undefined; if (item) { e.preventDefault(); navigate(item.to); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); }