From 437f5e61695c97f66a89c085507601ee8302ad49 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Mar 2026 12:30:03 +0000 Subject: [PATCH] Add global command bar for app navigation Co-authored-by: Ben Davis --- apps/web/src/lib/components/CommandBar.svelte | 433 ++++++++++++++++++ apps/web/src/lib/components/Sidebar.svelte | 52 +-- apps/web/src/routes/app/+layout.svelte | 43 ++ 3 files changed, 497 insertions(+), 31 deletions(-) create mode 100644 apps/web/src/lib/components/CommandBar.svelte diff --git a/apps/web/src/lib/components/CommandBar.svelte b/apps/web/src/lib/components/CommandBar.svelte new file mode 100644 index 00000000..2377ae0d --- /dev/null +++ b/apps/web/src/lib/components/CommandBar.svelte @@ -0,0 +1,433 @@ + + + + +{#if isOpen} + +{/if} diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index ea4315a0..dcb0c831 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -14,8 +14,7 @@ Settings, Sun, Trash2, - User, - X + User } from '@lucide/svelte'; import { goto } from '$app/navigation'; import { createEventDispatcher, onDestroy } from 'svelte'; @@ -45,25 +44,18 @@ let { threads, currentThreadId, isOpen, isLoading = false }: Props = $props(); - const dispatch = createEventDispatcher<{ close: void }>(); + const dispatch = createEventDispatcher<{ close: void; openCommandBar: void }>(); const auth = getAuthState(); const themeStore = getThemeStore(); const projectStore = getProjectStore(); const client = useConvexClient(); - let searchValue = $state(''); let showUserMenu = $state(false); let showProjectsSection = $state(false); const preloadDelayMs = 120; const preloadTimers = new Map>(); const preloadInFlight = new Set(); - const filteredThreads = $derived.by(() => { - const query = searchValue.trim().toLowerCase(); - if (!query) return threads; - return threads.filter((thread) => (thread.title ?? thread._id).toLowerCase().includes(query)); - }); - function formatDate(timestamp: number): string { return new Date(timestamp).toLocaleDateString(undefined, { month: 'short', @@ -77,6 +69,10 @@ if (isOpen) dispatch('close'); } + function openCommandBar() { + dispatch('openCommandBar'); + } + function createNewThread() { goto('/app/chat/new'); handleNavigate(); @@ -269,20 +265,18 @@ - {#if searchValue} - - {/if} + + ⌘K + @@ -296,17 +290,13 @@ Loading threads... - {:else if filteredThreads.length === 0} + {:else if threads.length === 0}
-
- {searchValue ? 'No matches found' : 'No threads yet'} -
-

- {searchValue ? 'Try a different search.' : 'Create a new thread to get started.'} -

+
No threads yet
+

Create a new thread to get started.

{:else} - {#each filteredThreads as thread (thread._id)} + {#each threads as thread (thread._id)} { + commandBarOpen = true; + sidebarOpen = false; + }; + + const closeCommandBar = () => { + commandBarOpen = false; + }; + + const openAddResourceModal = () => { + addResourceModalOpen = true; + }; + + const closeAddResourceModal = () => { + addResourceModalOpen = false; + }; + + const handleGlobalKeydown = (event: KeyboardEvent) => { + if (event.defaultPrevented) return; + if (event.key.toLowerCase() !== 'k') return; + if (!event.metaKey && !event.ctrlKey) return; + if (event.shiftKey || event.altKey) return; + if (addResourceModalOpen) return; + event.preventDefault(); + openCommandBar(); + }; + onMount(async () => { clerkInitPromise = (async () => { try { @@ -123,6 +154,7 @@ $effect(() => { page.url.pathname; sidebarOpen = false; + commandBarOpen = false; }); $effect(() => { @@ -134,6 +166,8 @@ }); + + btca | App @@ -163,6 +197,7 @@ isOpen={sidebarOpen} isLoading={threadsLoading} on:close={() => (sidebarOpen = false)} + on:openCommandBar={openCommandBar} /> @@ -189,3 +224,11 @@ + +