Skip to content

Commit fc199ee

Browse files
committed
Unmount the command palette while closed
1 parent 9566a11 commit fc199ee

3 files changed

Lines changed: 65 additions & 14 deletions

File tree

packages/app/src/web/shell.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ function IntegrationList(props: { pathname: string; onNavigate?: () => void }) {
152152

153153
// ── SidebarContent ───────────────────────────────────────────────────────
154154

155-
function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) {
155+
function SidebarContent(props: {
156+
pathname: string;
157+
onNavigate?: () => void;
158+
showBrand?: boolean;
159+
onOpenCommands: () => void;
160+
}) {
156161
const isHome = props.pathname === "/";
157162
const isSecrets = props.pathname === "/secrets";
158163
const isPolicies = props.pathname === "/policies";
@@ -212,6 +217,15 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show
212217
{/* Footer */}
213218
<div className="shrink-0 border-t border-sidebar-border px-4 py-2.5">
214219
<div className="flex flex-col gap-1.5 text-xs leading-none">
220+
{/* oxlint-disable-next-line react/forbid-elements */}
221+
<button
222+
type="button"
223+
onClick={props.onOpenCommands}
224+
className="flex items-center justify-between text-left text-muted-foreground transition-colors hover:text-foreground"
225+
>
226+
<span>Commands</span>
227+
<span className="font-mono text-[11px]">⌘K</span>
228+
</button>
215229
<a
216230
href="https://executor.sh/docs"
217231
target="_blank"
@@ -254,6 +268,7 @@ export function Shell() {
254268
const refreshTools = useAtomRefresh(toolsAllAtom);
255269
const lastPathname = useRef(pathname);
256270
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
271+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
257272
if (lastPathname.current !== pathname) {
258273
lastPathname.current = pathname;
259274
if (mobileSidebarOpen) setMobileSidebarOpen(false);
@@ -288,10 +303,10 @@ export function Shell() {
288303

289304
return (
290305
<div className="flex h-screen overflow-hidden">
291-
<CommandPalette />
306+
<CommandPalette open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen} />
292307
{/* Desktop sidebar */}
293308
<aside className="desktop-macos-sidebar hidden w-52 shrink-0 border-r border-sidebar-border bg-sidebar md:flex md:flex-col lg:w-56">
294-
<SidebarContent pathname={pathname} />
309+
<SidebarContent pathname={pathname} onOpenCommands={() => setCommandPaletteOpen(true)} />
295310
</aside>
296311

297312
{/* Mobile sidebar overlay */}
@@ -330,6 +345,10 @@ export function Shell() {
330345
pathname={pathname}
331346
onNavigate={() => setMobileSidebarOpen(false)}
332347
showBrand={false}
348+
onOpenCommands={() => {
349+
setMobileSidebarOpen(false);
350+
setCommandPaletteOpen(true);
351+
}}
333352
/>
334353
</div>
335354
</div>

packages/react/src/components/command-palette.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useState } from "react";
1+
import { useCallback, useEffect, useMemo } from "react";
22
import { useNavigate } from "@tanstack/react-router";
33
import { useAtomValue } from "@effect/atom-react";
44
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
@@ -28,9 +28,9 @@ import {
2828
// 3. Popular integrations (plugin presets)
2929
// ---------------------------------------------------------------------------
3030

31-
export function CommandPalette() {
31+
export function CommandPalette(props: { open: boolean; onOpenChange: (open: boolean) => void }) {
32+
const { open, onOpenChange } = props;
3233
const integrationPlugins = useIntegrationPlugins();
33-
const [open, setOpen] = useState(false);
3434
const navigate = useNavigate();
3535
const integrationsResult = useAtomValue(integrationsOptimisticAtom);
3636

@@ -39,12 +39,12 @@ export function CommandPalette() {
3939
const onKeyDown = (e: KeyboardEvent) => {
4040
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
4141
e.preventDefault();
42-
setOpen((o) => !o);
42+
onOpenChange(!open);
4343
}
4444
};
4545
document.addEventListener("keydown", onKeyDown);
4646
return () => document.removeEventListener("keydown", onKeyDown);
47-
}, []);
47+
}, [onOpenChange, open]);
4848

4949
const connectedSources = useMemo(
5050
() =>
@@ -88,7 +88,7 @@ export function CommandPalette() {
8888
return entries;
8989
}, [integrationPlugins]);
9090

91-
const close = useCallback(() => setOpen(false), []);
91+
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
9292

9393
const goToIntegration = useCallback(
9494
(id: string) => {
@@ -132,8 +132,10 @@ export function CommandPalette() {
132132
[close, navigate],
133133
);
134134

135+
if (!open) return null;
136+
135137
return (
136-
<CommandDialog open={open} onOpenChange={setOpen}>
138+
<CommandDialog open={open} onOpenChange={onOpenChange}>
137139
<CommandInput placeholder="Search integrations or jump to add…" />
138140
<CommandList>
139141
<CommandEmpty>No results found.</CommandEmpty>

packages/react/src/multiplayer/shell.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Link, Outlet, useLocation, useParams } from "@tanstack/react-router";
22
import { useEffect, useRef, useState, type ReactNode } from "react";
33
import { useAtomValue } from "@effect/atom-react";
44
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
5-
import { BookOpen, ExternalLink } from "lucide-react";
5+
import { BookOpen, Command, ExternalLink } from "lucide-react";
66
import type { Integration } from "@executor-js/sdk/shared";
77
import { integrationsOptimisticAtom } from "../api/atoms";
88
import { trackEvent } from "../api/analytics";
@@ -163,6 +163,21 @@ function DocsLink(props: { href: string; onNavigate?: () => void }) {
163163
);
164164
}
165165

166+
function CommandsButton(props: { onOpen: () => void }) {
167+
return (
168+
// oxlint-disable-next-line react/forbid-elements
169+
<button
170+
type="button"
171+
onClick={props.onOpen}
172+
className="group flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-sm text-sidebar-foreground transition-colors hover:bg-sidebar-active/60 hover:text-foreground"
173+
>
174+
<Command className="size-4 shrink-0" />
175+
<span className="flex-1 text-left">Commands</span>
176+
<span className="font-mono text-[11px] text-muted-foreground">⌘K</span>
177+
</button>
178+
);
179+
}
180+
166181
// ── IntegrationList ───────────────────────────────────────────────────────────
167182

168183
// `pathname` is scope-relative (org-slug prefix already stripped).
@@ -326,7 +341,12 @@ function UserFooter(props: Pick<ShellProps, "onSignOut" | "orgMenuSlot">) {
326341
// ── SidebarContent ───────────────────────────────────────────────────────
327342

328343
function SidebarContent(
329-
props: ShellProps & { pathname: string; onNavigate?: () => void; showBrand?: boolean },
344+
props: ShellProps & {
345+
pathname: string;
346+
onNavigate?: () => void;
347+
showBrand?: boolean;
348+
onOpenCommands: () => void;
349+
},
330350
) {
331351
const plugins = useClientPlugins();
332352
const pluginNavItems = plugins.flatMap((plugin) =>
@@ -371,6 +391,7 @@ function SidebarContent(
371391
<SidebarUpdateCard />
372392

373393
<div className="shrink-0 border-t border-sidebar-border p-2">
394+
<CommandsButton onOpen={props.onOpenCommands} />
374395
<DocsLink href={props.docsUrl ?? DEFAULT_DOCS_URL} onNavigate={props.onNavigate} />
375396
{APP_VERSION && (
376397
<p className="px-2.5 pt-1.5 font-mono text-[11px] text-muted-foreground tabular-nums">
@@ -392,6 +413,7 @@ export function Shell(props: ShellProps) {
392413
const pathname = useScopeRelativePathname();
393414
const lastPathname = useRef(pathname);
394415
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
416+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
395417
if (lastPathname.current !== pathname) {
396418
lastPathname.current = pathname;
397419
if (mobileSidebarOpen) setMobileSidebarOpen(false);
@@ -408,10 +430,14 @@ export function Shell(props: ShellProps) {
408430

409431
return (
410432
<div className="flex h-screen overflow-hidden">
411-
<CommandPalette />
433+
<CommandPalette open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen} />
412434
{/* Desktop sidebar */}
413435
<aside className="hidden w-52 shrink-0 border-r border-sidebar-border bg-sidebar md:flex md:flex-col lg:w-56">
414-
<SidebarContent {...props} pathname={pathname} />
436+
<SidebarContent
437+
{...props}
438+
pathname={pathname}
439+
onOpenCommands={() => setCommandPaletteOpen(true)}
440+
/>
415441
</aside>
416442

417443
{/* Mobile sidebar overlay */}
@@ -450,6 +476,10 @@ export function Shell(props: ShellProps) {
450476
pathname={pathname}
451477
onNavigate={() => setMobileSidebarOpen(false)}
452478
showBrand={false}
479+
onOpenCommands={() => {
480+
setMobileSidebarOpen(false);
481+
setCommandPaletteOpen(true);
482+
}}
453483
/>
454484
</div>
455485
</div>

0 commit comments

Comments
 (0)