Skip to content

Conversation

@MatthewAry
Copy link
Contributor

@MatthewAry MatthewAry commented Oct 17, 2025

Description

Adds a new VCommandPalette component

Requires #22328 to be merged first.

Markup:

Use the packages/vuetify/playgrounds/Playground.commandpalette.vue file to play around with it.

<script setup lang="ts">
  /* eslint-disable no-console */
  import { ref } from 'vue'
  import { useTheme } from 'vuetify'

  const model = ref(false)
  const showSnack = ref(false)
  const lastExecutedCommand = ref('')
  const search = ref('')

  // Theme management
  const theme = useTheme()

  const items = [
    // File Operations
    {
      type: 'subheader' as const,
      title: 'File Operations',
    },
    {
      title: 'New File',
      subtitle: 'Create a new file',
      value: 'new:file',
      prependIcon: 'mdi-file-plus',
      onClick: () => {
        lastExecutedCommand.value = 'New File created'
        showSnack.value = true
        console.log('Creating new file')
      },
    },
    {
      title: 'New Folder',
      subtitle: 'Create a new folder',
      value: 'new:folder',
      prependIcon: 'mdi-folder-plus',
      onClick: () => {
        lastExecutedCommand.value = 'New Folder created'
        showSnack.value = true
        console.log('Creating new folder')
      },
    },
    {
      title: 'Open File',
      subtitle: 'Open an existing file',
      value: 'file:open',
      prependIcon: 'mdi-folder-open',
      onClick: () => {
        lastExecutedCommand.value = 'Open File dialog opened'
        showSnack.value = true
        console.log('Opening file dialog')
      },
    },
    {
      title: 'Save',
      subtitle: 'Save the current file',
      value: 'file:save',
      prependIcon: 'mdi-content-save',
      hotkey: 'ctrl+s',
      onClick: () => {
        lastExecutedCommand.value = 'File saved'
        showSnack.value = true
        console.log('Saving file')
      },
    },
    {
      type: 'divider' as const,
    },
    // Git Operations
    {
      type: 'subheader' as const,
      title: 'Git Operations',
    },
    {
      title: 'Commit',
      subtitle: 'Commit changes',
      value: 'git:commit',
      prependIcon: 'mdi-source-commit',
      hotkey: 'ctrl+shift+c',
      onClick: () => {
        lastExecutedCommand.value = 'Git Commit'
        showSnack.value = true
        console.log('Committing changes')
      },
    },
    {
      title: 'Push',
      subtitle: 'Push changes to remote',
      value: 'git:push',
      prependIcon: 'mdi-source-pull',
      hotkey: 'ctrl+shift+p',
      onClick: () => {
        lastExecutedCommand.value = 'Git Push'
        showSnack.value = true
        console.log('Pushing changes')
      },
    },
    {
      title: 'Fetch',
      subtitle: 'Fetch from remote',
      value: 'git:fetch',
      prependIcon: 'mdi-source-branch',
      onClick: () => {
        lastExecutedCommand.value = 'Git Fetch'
        showSnack.value = true
        console.log('Fetching changes')
      },
    },
    {
      type: 'divider' as const,
    },
    // Preferences
    {
      type: 'subheader' as const,
      title: 'Preferences',
    },
    {
      title: 'Settings',
      subtitle: 'Open settings',
      value: 'pref:settings',
      prependIcon: 'mdi-cog',
      onClick: () => {
        lastExecutedCommand.value = 'Settings opened'
        showSnack.value = true
        console.log('Opening settings')
      },
    },
    {
      title: 'Toggle Theme',
      subtitle: 'Switch between light and dark theme',
      value: 'theme:toggle',
      prependIcon: 'mdi-palette',
      hotkey: 'ctrl+t',
      onClick: () => {
        theme.toggle()
        lastExecutedCommand.value = `Switched to ${theme.name.value} theme`
        showSnack.value = true
      },
    },
    {
      type: 'divider' as const,
    },
    // Search & Tools
    {
      type: 'subheader' as const,
      title: 'Search & Tools',
    },
    {
      title: 'Find',
      subtitle: 'Find in the current file',
      value: 'find',
      prependIcon: 'mdi-magnify',
      hotkey: 'ctrl+f',
      onClick: () => {
        lastExecutedCommand.value = 'Find dialog opened'
        showSnack.value = true
        console.log('Finding in file')
      },
    },
    {
      title: 'Find in Files',
      subtitle: 'Find in the entire workspace',
      value: 'find:files',
      prependIcon: 'mdi-file-find',
      hotkey: 'ctrl+shift+f',
      onClick: () => {
        lastExecutedCommand.value = 'Find in Files opened'
        showSnack.value = true
        console.log('Finding in files')
      },
    },
    {
      title: 'Replace',
      subtitle: 'Find and replace text',
      value: 'find:replace',
      prependIcon: 'mdi-find-replace',
      hotkey: 'ctrl+h',
      onClick: () => {
        lastExecutedCommand.value = 'Find and Replace opened'
        showSnack.value = true
        console.log('Find and replace')
      },
    },
  ]
</script>

<template>
  <v-app>
    <v-container class="pa-4">
      <v-row align="center" class="mb-4">
        <v-col cols="auto">
          <h1 class="text-h4">VCommandPalette Playground</h1>
        </v-col>
        <v-spacer />
        <v-col cols="auto">
          <v-btn
            :icon="theme.name.value === 'dark' ? 'mdi-weather-sunny' : 'mdi-weather-night'"
            :title="`Switch to ${theme.name.value === 'dark' ? 'light' : 'dark'} theme`"
            @click="theme.toggle()"
          />
        </v-col>
      </v-row>

      <v-row class="mb-4">
        <v-col>
          <v-card>
            <v-card-title>Command Palette Demo</v-card-title>
            <v-card-text>
              <p>This playground demonstrates the VCommandPalette component.</p>
              <p class="text-caption text-disabled mb-2"><strong>Keyboard shortcuts:</strong></p>
              <ul class="text-caption" style="list-style-position: inside;">
                <li class="mb-1">
                  <v-hotkey keys="ctrl+shift+p" size="x-small" inline /> - Open command palette
                </li>
                <li class="mb-1">
                  <v-hotkey keys="arrowup arrowdown" size="x-small" inline /> - Navigate items
                </li>
                <li class="mb-1">
                  <v-hotkey keys="enter" size="x-small" inline /> - Execute selected item
                </li>
                <li class="mb-1">
                  <v-hotkey keys="escape" size="x-small" inline /> - Close palette
                </li>
                <li class="mb-1">
                  <v-hotkey keys="ctrl+s" size="x-small" inline /> - Save (item hotkey)
                </li>
                <li class="mb-1">
                  <v-hotkey keys="ctrl+t" size="x-small" inline /> - Toggle theme (item hotkey)
                </li>
                <li>
                  <v-hotkey keys="ctrl+f" size="x-small" inline /> - Find (item hotkey)
                </li>
              </ul>
            </v-card-text>
            <v-card-actions>
              <v-btn
                color="primary"
                prepend-icon="mdi-console"
                @click="model = !model"
              >
                Open Command Palette
              </v-btn>
              <v-spacer />
              <v-chip v-if="search" size="small">
                Search: {{ search }}
              </v-chip>
            </v-card-actions>
          </v-card>
        </v-col>
      </v-row>

      <v-row v-if="lastExecutedCommand">
        <v-col>
          <v-alert
            type="info"
            variant="tonal"
            closable
            @click:close="lastExecutedCommand = ''"
          >
            Last executed: <strong>{{ lastExecutedCommand }}</strong>
          </v-alert>
        </v-col>
      </v-row>
    </v-container>

    <!-- VCommandPalette Component -->
    <v-command-palette
      v-model="model"
      v-model:search="search"
      :items="items"
      hotkey="ctrl+shift+p"
      max-height="450px"
      max-width="700px"
      placeholder="Type a command or search..."
    >
      <template #prepend>
        <div class="pa-2 text-center text-caption text-disabled">
          <strong>💡 Tip:</strong> Use
          <v-hotkey keys="ctrl+shift+p" size="small" inline />
          to open anytime
        </div>
      </template>

      <template #append>
        <v-divider class="mt-2" />
        <div class="pa-2 text-center text-caption text-disabled">
          <v-hotkey keys="arrowup arrowdown" size="x-small" inline />
          to navigate •
          <v-hotkey keys="enter" size="x-small" inline />
          to select •
          <v-hotkey keys="escape" size="x-small" inline />
          to close
        </div>
      </template>
    </v-command-palette>

    <!-- Snackbar for feedback -->
    <v-snackbar
      v-model="showSnack"
      :timeout="2000"
      location="bottom"
    >
      {{ lastExecutedCommand }}
    </v-snackbar>
  </v-app>
</template>

@MatthewAry MatthewAry requested a review from johnleider October 17, 2025 19:33
@MatthewAry MatthewAry requested a review from J-Sek October 17, 2025 21:14
MatthewAry and others added 7 commits November 4, 2025 08:19
- Introduced `navigationMode` prop to determine keyboard navigation behavior (focus/track).
- Added `navigationIndex` prop for tracking the currently selected item index.
- Updated event emissions for navigation index changes.
- Enhanced keyboard handling in `VList` to support new navigation features.
- Updated documentation in VList.json to reflect new props and their usage.
@J-Sek J-Sek added the C: New Component This issue would need a new component to be developed. label Nov 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C: New Component This issue would need a new component to be developed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants