Skip to content

Commit 095eceb

Browse files
committed
Unmount the command palette while closed
1 parent 7230dd0 commit 095eceb

3 files changed

Lines changed: 63 additions & 14 deletions

File tree

packages/app/src/web/shell.tsx

Lines changed: 21 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,14 @@ 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+
<button
221+
type="button"
222+
onClick={props.onOpenCommands}
223+
className="flex items-center justify-between text-left text-muted-foreground transition-colors hover:text-foreground"
224+
>
225+
<span>Commands</span>
226+
<span className="font-mono text-[11px]">⌘K</span>
227+
</button>
215228
<a
216229
href="https://executor.sh/docs"
217230
target="_blank"
@@ -254,6 +267,7 @@ export function Shell() {
254267
const refreshTools = useAtomRefresh(toolsAllAtom);
255268
const lastPathname = useRef(pathname);
256269
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
270+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
257271
if (lastPathname.current !== pathname) {
258272
lastPathname.current = pathname;
259273
if (mobileSidebarOpen) setMobileSidebarOpen(false);
@@ -288,10 +302,10 @@ export function Shell() {
288302

289303
return (
290304
<div className="flex h-screen overflow-hidden">
291-
<CommandPalette />
305+
<CommandPalette open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen} />
292306
{/* Desktop sidebar */}
293307
<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} />
308+
<SidebarContent pathname={pathname} onOpenCommands={() => setCommandPaletteOpen(true)} />
295309
</aside>
296310

297311
{/* Mobile sidebar overlay */}
@@ -330,6 +344,10 @@ export function Shell() {
330344
pathname={pathname}
331345
onNavigate={() => setMobileSidebarOpen(false)}
332346
showBrand={false}
347+
onOpenCommands={() => {
348+
setMobileSidebarOpen(false);
349+
setCommandPaletteOpen(true);
350+
}}
333351
/>
334352
</div>
335353
</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: 33 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,20 @@ function DocsLink(props: { href: string; onNavigate?: () => void }) {
163163
);
164164
}
165165

166+
function CommandsButton(props: { onOpen: () => void }) {
167+
return (
168+
<button
169+
type="button"
170+
onClick={props.onOpen}
171+
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"
172+
>
173+
<Command className="size-4 shrink-0" />
174+
<span className="flex-1 text-left">Commands</span>
175+
<span className="font-mono text-[11px] text-muted-foreground">⌘K</span>
176+
</button>
177+
);
178+
}
179+
166180
// ── IntegrationList ───────────────────────────────────────────────────────────
167181

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

328342
function SidebarContent(
329-
props: ShellProps & { pathname: string; onNavigate?: () => void; showBrand?: boolean },
343+
props: ShellProps & {
344+
pathname: string;
345+
onNavigate?: () => void;
346+
showBrand?: boolean;
347+
onOpenCommands: () => void;
348+
},
330349
) {
331350
const plugins = useClientPlugins();
332351
const pluginNavItems = plugins.flatMap((plugin) =>
@@ -371,6 +390,7 @@ function SidebarContent(
371390
<SidebarUpdateCard />
372391

373392
<div className="shrink-0 border-t border-sidebar-border p-2">
393+
<CommandsButton onOpen={props.onOpenCommands} />
374394
<DocsLink href={props.docsUrl ?? DEFAULT_DOCS_URL} onNavigate={props.onNavigate} />
375395
{APP_VERSION && (
376396
<p className="px-2.5 pt-1.5 font-mono text-[11px] text-muted-foreground tabular-nums">
@@ -392,6 +412,7 @@ export function Shell(props: ShellProps) {
392412
const pathname = useScopeRelativePathname();
393413
const lastPathname = useRef(pathname);
394414
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
415+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
395416
if (lastPathname.current !== pathname) {
396417
lastPathname.current = pathname;
397418
if (mobileSidebarOpen) setMobileSidebarOpen(false);
@@ -408,10 +429,14 @@ export function Shell(props: ShellProps) {
408429

409430
return (
410431
<div className="flex h-screen overflow-hidden">
411-
<CommandPalette />
432+
<CommandPalette open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen} />
412433
{/* Desktop sidebar */}
413434
<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} />
435+
<SidebarContent
436+
{...props}
437+
pathname={pathname}
438+
onOpenCommands={() => setCommandPaletteOpen(true)}
439+
/>
415440
</aside>
416441

417442
{/* Mobile sidebar overlay */}
@@ -450,6 +475,10 @@ export function Shell(props: ShellProps) {
450475
pathname={pathname}
451476
onNavigate={() => setMobileSidebarOpen(false)}
452477
showBrand={false}
478+
onOpenCommands={() => {
479+
setMobileSidebarOpen(false);
480+
setCommandPaletteOpen(true);
481+
}}
453482
/>
454483
</div>
455484
</div>

0 commit comments

Comments
 (0)