Skip to content

Conversation

@MatthewAry
Copy link
Contributor

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>

- Added VCommandPalette component for a keyboard-driven command interface.
- Implemented props for items, search, hotkeys, and dialog configuration.
- Included examples and documentation for usage, API, and accessibility features.
- Enhanced navigation and filtering capabilities
@MatthewAry MatthewAry changed the base branch from master to dev December 4, 2025 15:38
- Removed unnecessary comments in VCommandPalette and its related files.
- Updated slot descriptions in VList.json for better guidance.
- Changes to useCommandPaletteNavigation for improved item selection.
- Simplified props handling in VCommandPaletteItem
- Simplified item rendering logic in VCommandPaletteItemComponent.
- Enhanced navigation logic to prevent unnecessary index resets in useCommandPaletteNavigation.
- Revised localization strings for clarity and conciseness in VCommandPalette.json.
- Updated examples and documentation to reflect changes in hotkey usage and item properties.
- Improved descriptions for props, events, and slots in the command palette documentation.
@MatthewAry MatthewAry marked this pull request as ready for review December 5, 2025 15:24
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.

2 participants