Skip to content

Commit 9983364

Browse files
committed
feat(ui): add command palette for quick navigation and actions
1 parent 7f2fc1a commit 9983364

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4544
-66
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This focus helps guide our project decisions as a community and what we choose t
4242
- [Naming conventions](#naming-conventions)
4343
- [Vue components](#vue-components)
4444
- [Internal linking](#internal-linking)
45+
- [Command palette](#command-palette)
4546
- [Cursor and navigation](#cursor-and-navigation)
4647
- [RTL Support](#rtl-support)
4748
- [Localization (i18n)](#localization-i18n)
@@ -400,6 +401,14 @@ For package links, use the auto-imported `packageRoute()` utility from `app/util
400401
> [!IMPORTANT]
401402
> Never construct package URLs as strings. The route structure uses separate `org` and `name` params, and `packageRoute()` handles the splitting correctly.
402403
404+
### Command palette
405+
406+
The command palette is a first-class navigation surface. When you add a new user-facing capability to the app, you should also register a corresponding command palette entry for it whenever that action or destination makes sense outside its immediate UI.
407+
408+
- Add global entries in the command palette composables.
409+
- Add page- or component-scoped entries from the page/component that owns the capability.
410+
- Prefer palette entries for actions users may reasonably want to trigger from the keyboard or jump to directly.
411+
403412
#### Available route names
404413

405414
| Route name | URL pattern | Parameters |

app/app.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ if (import.meta.client) {
144144
<NuxtPage />
145145
</div>
146146

147+
<CommandPalette />
148+
147149
<AppFooter />
148150

149151
<ScrollToTop />

app/components/AppFooter.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const route = useRoute()
55
const isHome = computed(() => route.name === 'index')
66
77
const discord = useDiscordLink()
8+
const { commandPaletteShortcutLabel } = usePlatformModifierKey()
89
const modalRef = useTemplateRef('modalRef')
910
const showModal = () => modalRef.value?.showModal?.()
1011
const closeModal = () => modalRef.value?.close?.()
@@ -52,10 +53,19 @@ const closeModal = () => modalRef.value?.close?.()
5253
:modalTitle="$t('footer.keyboard_shortcuts')"
5354
class="w-auto max-w-lg"
5455
>
56+
<p class="mb-4 text-sm leading-relaxed text-fg-muted">
57+
{{
58+
$t('shortcuts.command_palette_description', { ctrlKey: $t('shortcuts.ctrl_key') })
59+
}}
60+
</p>
5561
<p class="mb-2 font-mono text-fg-subtle">
5662
{{ $t('shortcuts.section.global') }}
5763
</p>
5864
<ul class="mb-6 flex flex-col gap-2">
65+
<li class="flex gap-2 items-center">
66+
<kbd class="kbd">{{ commandPaletteShortcutLabel }}</kbd>
67+
<span>{{ $t('shortcuts.command_palette') }}</span>
68+
</li>
5969
<li class="flex gap-2 items-center">
6070
<kbd class="kbd">/</kbd>
6171
<span>{{ $t('shortcuts.focus_search') }}</span>

app/components/AppHeader.vue

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { NPMX_DOCS_SITE } from '#shared/utils/constants'
66
77
const keyboardShortcuts = useKeyboardShortcuts()
88
const discord = useDiscordLink()
9+
const { open: openCommandPalette } = useCommandPalette()
10+
const { commandPaletteShortcutLabel } = usePlatformModifierKey()
911
1012
withDefaults(
1113
defineProps<{
@@ -258,6 +260,24 @@ onKeyStroke(
258260
<!-- Spacer when logo is hidden on desktop -->
259261
<span v-else class="hidden sm:block w-1" />
260262

263+
<ButtonBase
264+
type="button"
265+
variant="secondary"
266+
class="hidden lg:inline-flex shrink-0 gap-2 px-2.5 me-3"
267+
:aria-label="$t('shortcuts.command_palette')"
268+
:title="$t('shortcuts.command_palette_description', { ctrlKey: $t('shortcuts.ctrl_key') })"
269+
@click="openCommandPalette"
270+
>
271+
<span>{{ $t('command_palette.quick_actions') }}</span>
272+
<span class="inline-flex items-center gap-1 text-xs text-fg-subtle">
273+
<kbd
274+
class="inline-flex items-center justify-center rounded border border-border bg-bg-muted px-1.5 py-0.5 font-mono text-[0.7rem] text-fg-muted"
275+
>
276+
{{ commandPaletteShortcutLabel }}
277+
</kbd>
278+
</span>
279+
</ButtonBase>
280+
261281
<!-- Center: Search bar + nav items -->
262282
<div
263283
class="flex-1 flex items-center md:gap-6"
@@ -293,7 +313,7 @@ onKeyStroke(
293313
</div>
294314

295315
<!-- End: Desktop nav items + Mobile menu button -->
296-
<div class="hidden sm:flex flex-shrink-0">
316+
<div class="hidden sm:flex flex-shrink-0 items-center gap-2">
297317
<!-- Desktop: Explore link -->
298318
<LinkBase
299319
v-for="link in desktopLinks"

0 commit comments

Comments
 (0)