Skip to content

Conversation

@MatthewAry
Copy link
Contributor

@MatthewAry MatthewAry commented Oct 17, 2025

Description

Adds a new VCommandPalette component

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
/**
* Auto-select first item when items change
*/
watch(() => options.filteredItems.value.length, newLength => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
watch(() => options.filteredItems.value.length, newLength => {
watch(() => options.filteredItems, newLength => {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants