Skip to content

g1t/apps/web/app/components/command-palette.tsx

85 lines3,102 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Usage, Billing settings and prepaid AI credit; fixes from the UX audit1import { type ReactNode, Suspense, lazy, useEffect, useRef, useState, useSyncExternalStore } from "react";
2
3import { cn } from "../lib/cn";
4import { paletteKeyLabel } from "../lib/shortcut";
Search across all of g1t, Explore, and a command palette5
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look6/*
7 * The ⌘K palette. The dialog itself, with its search and its dependencies,
8 * loads the first time it is opened, so pages that never open it do not
9 * carry it.
10 */
11const Dialog = lazy(() => import("./command-palette-dialog"));
Search across all of g1t, Explore, and a command palette12
13/** A page or action the palette can jump to. */
14export type PaletteCommand = { label: string; hint?: string; to: string; icon: ReactNode };
15
16/** Opens and closes the palette on ⌘K or Ctrl-K, from anywhere on the page. */
17export function usePaletteShortcut(toggle: () => void) {
18 const latest = useRef(toggle);
19 latest.current = toggle;
20 useEffect(() => {
21 const onKey = (event: KeyboardEvent) => {
22 if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
23 event.preventDefault();
24 latest.current();
25 }
26 };
27 window.addEventListener("keydown", onKey);
28 return () => window.removeEventListener("keydown", onKey);
29 }, []);
30}
31
Usage, Billing settings and prepaid AI credit; fixes from the UX audit32const never = () => () => {};
33
34function platform(): string {
35 const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
36 return nav.userAgentData?.platform || nav.platform || nav.userAgent;
37}
38
39/**
40 * The palette's shortcut as this computer writes it: ⌘K on a Mac, Ctrl K
41 * elsewhere. Not shown on a touch screen, which has no keyboard to press it.
42 */
43export function PaletteKey({ className }: { className?: string }) {
44 // The server cannot know the computer: it says ⌘K, and the browser corrects it.
45 const label = useSyncExternalStore(never, () => paletteKeyLabel(platform()), () => "⌘K");
46 return <kbd className={cn(className, "pointer-coarse:hidden")}>{label}</kbd>;
47}
48
Search across all of g1t, Explore, and a command palette49/**
50 * ⌘K: pages and actions from what the page already knows, and, as someone
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51 * types, repositories, issues, pull requests and people from search
52 * (command-palette-dialog.tsx).
Search across all of g1t, Explore, and a command palette53 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look54export function CommandPalette(props: {
Search across all of g1t, Explore, and a command palette55 open: boolean;
56 onOpenChange: (open: boolean) => void;
57 commands: PaletteCommand[];
58 /** The repository being looked at, `owner/name`, to offer searching its code. */
59 repo?: string | null;
60}) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look61 const [wanted, setWanted] = useState(props.open);
Search across all of g1t, Explore, and a command palette62 useEffect(() => {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look63 if (props.open) setWanted(true);
64 }, [props.open]);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit65 // What had focus when it opened, the button or the page, to give it back
66 // on closing: the dialog has no trigger of its own to return to.
67 const opener = useRef<HTMLElement | null>(null);
68 const wasOpen = useRef(false);
69 if (props.open && !wasOpen.current && typeof document !== "undefined") {
70 opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
71 }
72 wasOpen.current = props.open;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look73 if (!wanted) return null;
Search across all of g1t, Explore, and a command palette74 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look75 <Suspense fallback={null}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit76 <Dialog
77 {...props}
78 onCloseAutoFocus={(event) => {
79 event.preventDefault();
80 if (opener.current?.isConnected) opener.current.focus();
81 }}
82 />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look83 </Suspense>
Search across all of g1t, Explore, and a command palette84 );
85}