Skip to content

Conversation

@MatthewAry
Copy link
Contributor

@MatthewAry MatthewAry commented Dec 4, 2025

Description

Adds a new VCommandPalette component

Markup:

<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
@J-Sek J-Sek added the C: New Component This issue would need a new component to be developed. label Dec 6, 2025
Copy link
Contributor

@J-Sek J-Sek left a comment

Choose a reason for hiding this comment

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

I like how slim it got. Great job.
These are just some quick comments.
Hopefully, I will have some time next week to go through examples and maybe add some more.

- Added `inputIcon` property to customize the search input icon in VCommandPalette.
- Introduced custom input slot for more flexible search input implementations.
- Updated styles for input container and no-data display for improved UI consistency.
- Implemented cleanup for the hotkey listener to prevent memory leaks.
- Added `onUnmounted` lifecycle hook to clear the DOM reference and unsubscribe from hotkey events.
@MatthewAry MatthewAry requested a review from J-Sek December 9, 2025 15:29
- Utilized requestAnimationFrame to ensure the search input is focused after rendering.
- Added value binding to VCommandPaletteItem for better item representation.
- Updated tests to reflect changes in search input handling and aria attributes.
@J-Sek
Copy link
Contributor

J-Sek commented Dec 19, 2025

When I type in search, the height of the dialog changes and (because it is trying to be centered vertically) field moves around and I have a hard time tracking what I actually type. It does not feel like a thing exclusive to VCommandPalette. I wonder if there is something we can do about it right now 🤔 Except having static height... Otherwise, we should split an enhancement for later. It would definitely be mitigated by the support for static anchor (that we could then set as default to have a typical UX).

@J-Sek

This comment was marked as resolved.

@J-Sek

This comment was marked as resolved.

- Removed the `location` prop from VCommandPalette and its related usage in examples.
- Updated documentation to reflect changes in dialog configuration and removed references to the `location` prop.
- Changed the default `inputIcon` for VCommandPalette from 'mdi-magnify' to '$search'.
- Added new icons to the icon set, including 'mdiArrowRightBold', 'mdiConsole', 'mdiContentSaveAll', 'mdiFilePlus', 'mdiFindReplace', 'mdiFolderPlus', 'mdiKeyboard', 'mdiMagnifyMinus', 'mdiMagnifyPlus', and 'mdiPageLayoutSidebarLeft'.
- Updated the icon alias for 'search' to map to 'mdi-magnify'.
- Removed `activator` and `dialogProps` from VCommandPalette props, streamlining the component's API.
- Updated documentation to clarify that unrecognized props are passed to the underlying `v-dialog`.
- Added 'search' icon to various icon sets, including Font Awesome and Material Design Icons.
@MatthewAry
Copy link
Contributor Author

When I type in search, the height of the dialog changes and (because it is trying to be centered vertically) field moves around and I have a hard time tracking what I actually type. It does not feel like a thing exclusive to VCommandPalette. I wonder if there is something we can do about it right now 🤔 Except having static height... Otherwise, we should split an enhancement for later. It would definitely be mitigated by the support for static anchor (that we could then be set as default to have a typical UX).

Noted.

@MatthewAry MatthewAry requested a review from J-Sek January 6, 2026 15:24
MatthewAry and others added 8 commits January 6, 2026 07:32
- Introduced a `max-width` prop set to 500 for the VCommandPalette in multiple examples to enhance layout control.
- Updated `prop-dialog.vue`, `prop-hotkey.vue`, and `prop-items.vue` to reflect this new prop for better UI consistency.
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.

2 participants