From 99833647ed93f7a9e193f6b5dc5e64067e1b04ce Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Thu, 19 Mar 2026 21:32:29 -0400 Subject: [PATCH] feat(ui): add command palette for quick navigation and actions --- CONTRIBUTING.md | 9 + app/app.vue | 2 + app/components/AppFooter.vue | 10 + app/components/AppHeader.vue | 22 +- app/components/CommandPalette.client.vue | 478 +++++++++++++ app/components/Diff/ViewerPanel.vue | 30 + app/components/Header/MobileMenu.client.vue | 28 + app/components/Package/DownloadButton.vue | 36 +- app/components/Package/ExternalLinks.vue | 99 +++ app/components/Package/Header.vue | 50 +- app/components/Package/Playgrounds.vue | 15 + app/components/Package/SkillsCard.vue | 23 +- app/components/Settings/AccentColorPicker.vue | 10 +- app/components/Settings/BgThemePicker.vue | 14 +- app/components/Terminal/Install.vue | 81 +++ app/composables/useCommandPalette.ts | 312 +++++++++ app/composables/useCommandPaletteCommands.ts | 220 ++++++ .../useCommandPaletteGlobalCommands.ts | 643 ++++++++++++++++++ .../useCommandPalettePackageCommands.ts | 154 +++++ .../useCommandPalettePackageVersions.ts | 39 ++ .../useCommandPaletteVersionCommands.ts | 63 ++ app/composables/usePlatformModifierKey.ts | 30 + app/composables/useSettings.ts | 20 +- app/pages/compare.vue | 71 ++ .../[packageName]/v/[versionRange].vue | 21 + .../v/[version]/[...filePath].vue | 92 +++ app/pages/package-docs/[...path].vue | 21 + app/pages/package/[[org]]/[name].vue | 22 + app/pages/profile/[identity]/index.vue | 44 ++ app/types/command-palette.ts | 85 +++ app/utils/package-download.ts | 42 ++ docs/content/2.guide/1.features.md | 4 + docs/content/2.guide/2.keyboard-shortcuts.md | 28 +- docs/content/index.md | 2 +- i18n/locales/en.json | 103 +++ i18n/locales/fr-FR.json | 109 ++- i18n/schema.json | 309 +++++++++ test/nuxt/a11y.spec.ts | 38 ++ test/nuxt/app/utils/package-download.spec.ts | 131 ++++ test/nuxt/components/CommandPalette.spec.ts | 296 ++++++++ .../use-command-palette-commands.spec.ts | 500 ++++++++++++++ ...e-command-palette-package-versions.spec.ts | 121 ++++ .../composables/use-command-palette.spec.ts | 183 +++++ 43 files changed, 4544 insertions(+), 66 deletions(-) create mode 100644 app/components/CommandPalette.client.vue create mode 100644 app/composables/useCommandPalette.ts create mode 100644 app/composables/useCommandPaletteCommands.ts create mode 100644 app/composables/useCommandPaletteGlobalCommands.ts create mode 100644 app/composables/useCommandPalettePackageCommands.ts create mode 100644 app/composables/useCommandPalettePackageVersions.ts create mode 100644 app/composables/useCommandPaletteVersionCommands.ts create mode 100644 app/composables/usePlatformModifierKey.ts create mode 100644 app/types/command-palette.ts create mode 100644 app/utils/package-download.ts create mode 100644 test/nuxt/app/utils/package-download.spec.ts create mode 100644 test/nuxt/components/CommandPalette.spec.ts create mode 100644 test/nuxt/composables/use-command-palette-commands.spec.ts create mode 100644 test/nuxt/composables/use-command-palette-package-versions.spec.ts create mode 100644 test/nuxt/composables/use-command-palette.spec.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc6afeb794..d5a8d71742 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ This focus helps guide our project decisions as a community and what we choose t - [Naming conventions](#naming-conventions) - [Vue components](#vue-components) - [Internal linking](#internal-linking) + - [Command palette](#command-palette) - [Cursor and navigation](#cursor-and-navigation) - [RTL Support](#rtl-support) - [Localization (i18n)](#localization-i18n) @@ -400,6 +401,14 @@ For package links, use the auto-imported `packageRoute()` utility from `app/util > [!IMPORTANT] > Never construct package URLs as strings. The route structure uses separate `org` and `name` params, and `packageRoute()` handles the splitting correctly. +### Command palette + +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. + +- Add global entries in the command palette composables. +- Add page- or component-scoped entries from the page/component that owns the capability. +- Prefer palette entries for actions users may reasonably want to trigger from the keyboard or jump to directly. + #### Available route names | Route name | URL pattern | Parameters | diff --git a/app/app.vue b/app/app.vue index 5916e3e328..9a90d2b0b9 100644 --- a/app/app.vue +++ b/app/app.vue @@ -144,6 +144,8 @@ if (import.meta.client) { + + diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 6955352350..75e725d2fa 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -5,6 +5,7 @@ const route = useRoute() const isHome = computed(() => route.name === 'index') const discord = useDiscordLink() +const { commandPaletteShortcutLabel } = usePlatformModifierKey() const modalRef = useTemplateRef('modalRef') const showModal = () => modalRef.value?.showModal?.() const closeModal = () => modalRef.value?.close?.() @@ -52,10 +53,19 @@ const closeModal = () => modalRef.value?.close?.() :modalTitle="$t('footer.keyboard_shortcuts')" class="w-auto max-w-lg" > +

+ {{ + $t('shortcuts.command_palette_description', { ctrlKey: $t('shortcuts.ctrl_key') }) + }} +

{{ $t('shortcuts.section.global') }}

    +
  • + {{ commandPaletteShortcutLabel }} + {{ $t('shortcuts.command_palette') }} +
  • / {{ $t('shortcuts.focus_search') }} diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 8f0cb2290c..fc3f29631d 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -6,6 +6,8 @@ import { NPMX_DOCS_SITE } from '#shared/utils/constants' const keyboardShortcuts = useKeyboardShortcuts() const discord = useDiscordLink() +const { open: openCommandPalette } = useCommandPalette() +const { commandPaletteShortcutLabel } = usePlatformModifierKey() withDefaults( defineProps<{ @@ -258,6 +260,24 @@ onKeyStroke(