0.1.9 ā€¢ Published 2 years ago

vue-cmd-menu v0.1.9

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Build beautiful and extensible āŒ˜ + k menus with Vue

šŸ”® Demo - šŸš€ Get started - šŸ“š Docs

image

šŸ‘‹ Introduction

vue-cmd-menu lets you built a beatiful, fast and extensible command menu like the ones you may know from Vercel, GitHub and Linear or MacOS's Spotlight/Raycast. It can be invoked anywhere in your app via Command + k to perform actions users would typically be able to do via an interface.

  • šŸ’… Beautiful by default, easy to style to your liking
  • šŸ”Ž Built-in filtering with Fuse.js
  • āŒØļø Keyboard shortcuts for registering keystrokes to specific actions
  • šŸ§­ Easy navigation with your keyboard or mouse
  • šŸ“ Nested actions for folder like navigation experience
  • šŸ”© Simple data structure to define and customise actions

šŸš€ Get started

Install it via NPM:

npm install vue-cmd-menu

It currently only supports Vue 2

Global Usage

import Vue from 'vue';
import CommandMenu from 'vue-cmd-menu';

Vue.component('CommandMenu', CommandMenu);

In Single File Components

import CommandMenu from 'vue-cmd-menu';

export default {
  // ...
  components: {
    CommandMenu,
  },
  // ...
};

šŸ“š Usage

While command menus sound easy to build in theory, in practice handling the different interactions, nagivation options and state management can quickly become complicated. vue-cmd-menu provides a simple abstraction over this, simply pass it the actions you want your users to be able to perform and it will handle the rest.

<template>
  <div id="app">
    <CommandMenu :actions="commandItems"></CommandMenu>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import CommandMenu from 'vue-cmd-menu';

export default Vue.extend({
  components: {
    CommandMenu
  },
  computed: {
    actions() {
      return [
        {
          id: 'new',
          text: 'Create New Page',
          tag: 'New',
          childPlaceholder: 'Page title',
          action: (value) => {
            // Handle the action, value contains the entered title
            console.log(value)
          }
        },
        {
          id: 'docs',
          text: 'Documentation',
          keybindings: [ '?' ],
          childPlaceholder: 'Search Docs',
          childActions: [
            {
              id: 'overview',
              text: 'Overview',
              action: () => {}
            },
            {
              id: 'get-started',
              text: 'Get Started',
              action: () => {}
            }
          ]
        }
      ]
    }
  }
})
</script>

Available Props

  • name: string | boolean | null - namespace the modal and the open event (default: '')
  • actions: Array<any | Record<string, any>> - the available actions to filter and execute (required)
  • keybinding: Array<string> | null - combination of keys that need to be pressed (default: ['meta', 'k'])
  • placeholder: string - placeholder to show for the root command (default: Type a command or search)
  • shadow: boolean - add a shadow to the view box (default: true)
  • overlay: boolean - show an overlay under the view box (default: true)
  • theme: string - which theme to use, dark or light (default: light)
  • blur: boolean - enable background blur (default: true)
  • animations: boolean - enable animations (default: true)
  • nestedSearch: boolean - search/filter nested actions
  • fuseOptions: Fuse.IFuseOptions<string> - options to pass to Fuse.js (see options page) (default: {})

Actions

There are different type of actions you can define. They all require at least a id, text and if it doesn't have any child actions, a action handler:

const action ={
  id: 'ID',
  text: 'Title',
  action: () => {}
}

Here are all the options available to an action:

namedescriptiontyperequired
idInternal ID of the actionstringtrue
textText which will be shown for each action in the liststringtrue
actionHanlder which will be called when the action is selectedfunctiontrue
iconIcon to show before the text in the UIVue Component/string (only when slot is used)false
sectionSection to group the action instringfalse
keybindingsKeystrokes to attach to the actionstring[]false
tagTag to show before the input field after the action is selectedstringfalse
childPlaceholderPlaceholder to show when waiting for user input after the parent action is selectedstringfalse
valueValue to insert into the search field when the action is selectedstringfalse
hiddenShow the action in the result listbooleanfalse
childTitleDisplay a title instead of the input field when the action is selectedstringfalse
childActionsArray of child actions to show once the action is selectedActions[]false

Icons

The icon property either excepts a Vue component or a string which will be passed to the icon slot:

<template v-slot:icon="{ icon }">
  <Icon :name="icon" />
</template>

You can use the provided value with different icon libraries or with your custom one.

Inputs

If your action needs an input, specify a tag which will be shown before the input. When the user hits enter your action handler will be executed with the value as its parameter.

Nested actions

Each of your actions can also have nested/child actions which will be shown once the parent action is selected. Specify them with the childActions parameter:

 [{
    id: 'docs',
    text: 'Documentation',
    tag: 'Docs',
    childPlaceholder: 'Search Docs',
    childActions: [
      {
        id: 'overview',
        text: 'Overview',
        action: () => {}
      },
      {
        id: 'get-started',
        text: 'Get Started',
        action: () => {}
      },
    ]
  }]

You can also define a childPlaceholder which will be shown in the input field before any child action is selected and a tag which will be shown before the input to indicate what type of actions are being shown (kind of like a breadcrumb).

Keybindings

Each of your actions can either be triggered by selecting it in the menu, or directly with a keyboard shortcut. You can define the keys to attach to a action with the keybindings parameter:

[{
  id: 'help',
  keybindings: [ '?' ],
  text: 'Help',
  action: () => {}
}]

The shortcut by default listens to the meta key (CMD on Mac, Win on Windows) and then the defined keybinding.

Opening Programmatically

<!-- the `openCommandMenu` event can be called anyway and will trigger the modal to open -->
<button type="button" @click.prevent="$root.$emit('openCommandMenu')">Show Omnibar</button>
<!-- if there is a `name`, the event will have the name appended: `'openCommandMenu.myName'` -->
<button type="button" @click.prevent="$root.$emit('openCommandMenu.myName')">Show "myName" modal</button>
<!-- you can also close the modal with the opposite event: `'closeCommandMenu.myName'` -->
<button type="button" @click.prevent="$root.$emit('closeCommandMenu.myName')">Close "myName" modal</button>

Filtering

vue-cmd-menu uses Fuse.js under the hood to filter the specified actions. This allows for fuzzy searching i.e. the search term doesn't have to be a exact match. By default it searches the action's title and keys defined with the keywords property.

If you want to search nested actions as well, enable it with the nestedSearch prop.

You can further fine-tune fuse.js with the fuseOptions prop.

šŸ“– Examples

Here are some examples for different type of actions:

[
  {
    id: 'home',
    keybindings: [ 'backspace' ],
    text: 'Go home',
    action: () => {
      window.location.pathname = '/'
    }
  },
  {
    id: 'copy',
    keybindings: [ 'c' ],
    text: 'Copy',
    action: () => {}
  },
  {
    id: 'help',
    keybindings: [ '?' ],
    icon: HelpIcon,
    text: 'Help',
    action: () => {
      window.location.pathname = '/'
    }
  },
  {
    id: 'new',
    text: 'Create New Page',
    tag: 'New',
    childPlaceholder: 'Page title',
    action: (value) => {
      window.location.pathname = '/'
    }
  },
  {
    id: 'docs',
    icon: DocsIcon,
    text: 'Documentation',
    childPlaceholder: 'Search Docs',
    childActions: [
      {
        id: 'overview',
        text: 'Overview',
        action: () => {}
      },
      {
        id: 'get-started',
        text: 'Get Started',
        action: () => {}
      },
    ]
  },

šŸ’» Development

Issues and PRs are very welcome!

The actual source code of this library is in the src folder.

# install dependencies
yarn install

# serve app with hot reload
yarn run dev

# build electron application for production
yarn run build

# lint all JS/Vue component files in `src/`
yarn run lint

šŸ“‹ To Do

  • Standardize parameter of Action handler
  • Don't hard code meta key for keybindings
  • Add options to change styling
  • Sections
  • Light mode
  • Animations
  • Hook to add actions dynamicly
  • Improve naming of properties and options
  • Keep focus on input when navigating through list
  • Limit modal height and add scrollbar
  • Async actions and loading states
  • Use slot to customize the rendering of the results
  • Vue 3 support

ā” About

This project was developed by me (@betahuhn) in my free time. If you want to support me:

Donate via PayPal

ko-fi

Credits

This Action was inspired by:

šŸ“„ License

Copyright 2022 Maximilian Schiller

This project is licensed under the MIT License - see the LICENSE file for details.

0.1.9

2 years ago

0.1.8

2 years ago

0.1.7

2 years ago

0.1.6

2 years ago

0.1.5

2 years ago

0.1.4

2 years ago

0.1.3

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago