├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── .vscode └── extensions.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api-extractor.json ├── components.d.ts ├── index.html ├── og:image.png ├── package.json ├── packages ├── CommandDialog.vue ├── CommandGroup.vue ├── CommandInput.vue ├── CommandItem.vue ├── CommandList.vue ├── CommandProvider.vue ├── CommandRoot.vue ├── index.ts ├── types.ts ├── useCommandEvent.ts ├── useCommandState.ts └── utils.ts ├── pnpm-lock.yaml ├── public └── logo.svg ├── src ├── App.vue ├── assets │ └── scss │ │ ├── algolia.scss │ │ ├── framer.scss │ │ ├── global.scss │ │ ├── highlight.scss │ │ ├── linear.scss │ │ ├── raycast.scss │ │ └── vercel.scss ├── components │ ├── command │ │ ├── Linear.vue │ │ ├── Self.vue │ │ ├── raycast │ │ │ ├── Home.vue │ │ │ ├── Item.vue │ │ │ └── Raycast.vue │ │ └── vercel │ │ │ ├── Home.vue │ │ │ ├── Item.vue │ │ │ ├── Projects.vue │ │ │ └── Vercel.vue │ ├── common │ │ └── CmdkPlaceholder.vue │ └── icons │ │ ├── FigmaIcon.vue │ │ ├── LinearAssignToIcon.vue │ │ ├── LinearAssignToMeIcon.vue │ │ ├── LinearChangeLabelsIcon.vue │ │ ├── LinearChangePriorityIcon.vue │ │ ├── LinearChangeStatusIcon.vue │ │ ├── LinearIcon.vue │ │ ├── LinearRemoveLabelIcon.vue │ │ ├── LinearSetDueDateIcon.vue │ │ ├── Logo.vue │ │ ├── MoonIcon.vue │ │ ├── RaycasTerminalIcon.vue │ │ ├── RaycastClipboardIcon.vue │ │ ├── RaycastDarkIcon.vue │ │ ├── RaycastFinderIcon.vue │ │ ├── RaycastHammerIcon.vue │ │ ├── RaycastIcon.vue │ │ ├── RaycastLightIcon.vue │ │ ├── RaycastStarIcon.vue │ │ ├── RaycastWindowIcon.vue │ │ ├── SlackIcon.vue │ │ ├── SunIcon.vue │ │ ├── VercelContactIcon.vue │ │ ├── VercelCopyIcon.vue │ │ ├── VercelDocsIcon.vue │ │ ├── VercelFeedbackIcon.vue │ │ ├── VercelIcon.vue │ │ ├── VercelPlusIcon.vue │ │ ├── VercelProjectsIcon.vue │ │ ├── VercelTeamsIcon.vue │ │ └── YouTubeIcon.vue ├── composables │ └── useDarkmode.ts ├── main.ts ├── plugins │ └── highlight.ts └── vite-env.d.ts ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.node.json ├── unocss.config.ts ├── vite.config.ts └── vue-command-palette.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [xiaoluoboding] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | lib 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | temp 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "semi": false, 6 | "trailingComma": "none", 7 | "printWidth": 80 8 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.2.3](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.2.2...v0.2.3) (2023-09-20) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * fixed the debounce search bug [#18](https://github.com/xiaoluoboding/vue-command-palette/issues/18) ([5d72166](https://github.com/xiaoluoboding/vue-command-palette/commit/5d72166779778a88df790b6dc7175319918e2a2f)) 7 | 8 | 9 | 10 | ## [0.2.2](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.2.1...v0.2.2) (2023-09-12) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * fixed blank when filter text ([08da9ef](https://github.com/xiaoluoboding/vue-command-palette/commit/08da9ef7d1c28fcd411dfabde40c56485a55ac9b)) 16 | 17 | 18 | 19 | ## [0.2.1](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.2.0...v0.2.1) (2023-09-12) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * filter the item when rerender ([f26308f](https://github.com/xiaoluoboding/vue-command-palette/commit/f26308f964b5ec50f767bac98e0f833a5eae6d44)) 25 | * fixed the bug [#14](https://github.com/xiaoluoboding/vue-command-palette/issues/14) ([f8e4ac8](https://github.com/xiaoluoboding/vue-command-palette/commit/f8e4ac86b3d1074bbe0ea78de259b26f2372b1ea)) 26 | * fixed the bug of [#14](https://github.com/xiaoluoboding/vue-command-palette/issues/14) ([9df6def](https://github.com/xiaoluoboding/vue-command-palette/commit/9df6def24bd0414232ae7f1bd0aef0d46277aac0)) 27 | 28 | 29 | ### Features 30 | 31 | * allow user reset or update the search value ([5f140ed](https://github.com/xiaoluoboding/vue-command-palette/commit/5f140edfabc776571d2b67270ab3cdc84eafea59)) 32 | 33 | 34 | 35 | # [0.2.0](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.1.4...v0.2.0) (2023-08-22) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * fixed the type error ([70bf1f5](https://github.com/xiaoluoboding/vue-command-palette/commit/70bf1f53469aa9e0ecf0bf32383afb1ffab3988e)) 41 | 42 | 43 | ### Features 44 | 45 | * add types for command component ([e619b5e](https://github.com/xiaoluoboding/vue-command-palette/commit/e619b5ed560a924716381ce06a2e8d6fa2ca54c9)) 46 | * close on clicking outside ([ca71816](https://github.com/xiaoluoboding/vue-command-palette/commit/ca71816ea4b5f6750a7388b068782629c02ede7d)) 47 | 48 | 49 | 50 | ## [0.1.4](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.1.3...v0.1.4) (2022-12-14) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * bottom scroll bar ([5c38a00](https://github.com/xiaoluoboding/vue-command-palette/commit/5c38a00fef77c386b158a1e601229995b3e3783b)) 56 | 57 | 58 | 59 | ## [0.1.3](https://github.com/xiaoluoboding/vue-command-palette/compare/v0.1.2...v0.1.3) (2022-09-15) 60 | 61 | 62 | 63 | ## [0.1.2](https://github.com/xiaoluoboding/vue-command-palette/compare/fec87bf5042b5bfd389421524ff3ec3226c39879...v0.1.2) (2022-09-15) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * fixed the input event ([810ba79](https://github.com/xiaoluoboding/vue-command-palette/commit/810ba79da3fdbceb03deba11fd6b7de28dc32ef0)) 69 | * fixed the list component types ([f60e20a](https://github.com/xiaoluoboding/vue-command-palette/commit/f60e20a117e6197c9b922e5aa24a1090937ef34f)) 70 | 71 | 72 | ### Features 73 | 74 | * add fuzzy search items in group ([684f1e8](https://github.com/xiaoluoboding/vue-command-palette/commit/684f1e8aa0aa02663e99bc2d7db059daf0960507)) 75 | * add rerender list event ([db85fb9](https://github.com/xiaoluoboding/vue-command-palette/commit/db85fb9cb16d65f36785bd7733c80333e51e403f)) 76 | * add the cmdk it's self ([b8dc650](https://github.com/xiaoluoboding/vue-command-palette/commit/b8dc6500b19f5746669f49b8e6b725529e1f8f03)) 77 | * add the code demo ([0cfb97b](https://github.com/xiaoluoboding/vue-command-palette/commit/0cfb97b3e5cb40888b928d389143aa820aff51de)) 78 | * add the demo of linear ([8e98e98](https://github.com/xiaoluoboding/vue-command-palette/commit/8e98e98545dc5c749d22e900aff82036985d1930)) 79 | * add the fuzzy search feature ([48969d9](https://github.com/xiaoluoboding/vue-command-palette/commit/48969d918ff6885556c1d7dac2db555685c6610c)) 80 | * add the input v-model:value attribute ([1536795](https://github.com/xiaoluoboding/vue-command-palette/commit/153679526f68cdb1d52a06620893430241b86694)) 81 | * add the keyboard event feature ([585ca53](https://github.com/xiaoluoboding/vue-command-palette/commit/585ca533c1ece2dcf985ff8cc1b8d76990e83424)) 82 | * add the list height animation ([278ba13](https://github.com/xiaoluoboding/vue-command-palette/commit/278ba131c07ecabfdf9fc31bb433253c943d5b60)) 83 | * add the perform action ([9837cc6](https://github.com/xiaoluoboding/vue-command-palette/commit/9837cc6177f93cce0965ff68ead9304a423cd6b3)) 84 | * add the raycast cmdk demo ([754f7fe](https://github.com/xiaoluoboding/vue-command-palette/commit/754f7fe5d339fd5c971699615783333719b4ae01)) 85 | * add the select item event on cmdk component ([1dc7b2a](https://github.com/xiaoluoboding/vue-command-palette/commit/1dc7b2a6ab07b49f1fa5a645dc825240175063fc)) 86 | * add the toggle dark mode action ([8633f95](https://github.com/xiaoluoboding/vue-command-palette/commit/8633f95bc59ae0c7acff40fc7530c6ceb7113455)) 87 | * add the vercel cmdk demo ([1d59805](https://github.com/xiaoluoboding/vue-command-palette/commit/1d5980503f7921c09f54ea751a8dc35a817acfe9)) 88 | * add transition for self dialog ([fb2ed08](https://github.com/xiaoluoboding/vue-command-palette/commit/fb2ed08513ca56a69ba6c51a4ba43e049e899d1d)) 89 | * change the demo page ([1d2b16b](https://github.com/xiaoluoboding/vue-command-palette/commit/1d2b16b6bb370d6c1ee2987d55509d7822767236)) 90 | * create the lib ([fec87bf](https://github.com/xiaoluoboding/vue-command-palette/commit/fec87bf5042b5bfd389421524ff3ec3226c39879)) 91 | * deal with the event emitter ([5e36efd](https://github.com/xiaoluoboding/vue-command-palette/commit/5e36efdb833f68cfc4e0bcf688c9021d1c8a6982)) 92 | * fixed the dark mode toggle ([923f178](https://github.com/xiaoluoboding/vue-command-palette/commit/923f178faed6b78d8b22999c99ace78f0440ae55)) 93 | * fixed the empty node ([624dc08](https://github.com/xiaoluoboding/vue-command-palette/commit/624dc081a72d693e5db17a9fddbf6d3d8607233a)) 94 | * handle dispatch the event on item ([71081a4](https://github.com/xiaoluoboding/vue-command-palette/commit/71081a4f74af7cdd5406d604c9a83b995aa8ff33)) 95 | * hidden the empty node ([7cae74f](https://github.com/xiaoluoboding/vue-command-palette/commit/7cae74f480019d532f7df530ad967fe9c4222777)) 96 | * navigate to the group first item ([ae5a3d0](https://github.com/xiaoluoboding/vue-command-palette/commit/ae5a3d02ff9007206a78abfb3e575046f9dc459c)) 97 | * no valid idx, then go to the first/last of item ([a9c6db4](https://github.com/xiaoluoboding/vue-command-palette/commit/a9c6db4d279212acad118d47e138f52226605f74)) 98 | * rename cmdk to command ([9850592](https://github.com/xiaoluoboding/vue-command-palette/commit/985059217e81092094e32a97d8d8b2a0d96c5f73)) 99 | * use the provide/inject api ([07868b7](https://github.com/xiaoluoboding/vue-command-palette/commit/07868b78e510f1916063c9baa64e524bfe88ddbe)) 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yunwei Xiao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Command Palette for Vue 2 | 3 | [![NPM][npmBadge]][npmUrl] 4 | [![Minzip Package][bundlePhobiaBadge]][bundlePhobiaUrl] 5 | [![NPM Download][npmDtBadge]][npmDtUrl] 6 | 7 | [npmBadge]: https://img.shields.io/npm/v/vue-command-palette.svg?maxAge=2592000 8 | [npmUrl]: https://www.npmjs.com/package/vue-command-palette 9 | [npmDtBadge]: https://img.shields.io/npm/dt/vue-command-palette.svg 10 | [npmDtUrl]: https://www.npmjs.com/package/vue-command-palette 11 | [bundlePhobiaBadge]: https://img.shields.io/bundlephobia/minzip/vue-command-palette 12 | [bundlePhobiaUrl]: https://bundlephobia.com/package/vue-command-palette@latest 13 | 14 | > A fast, composable, unstyled `Command` + `K` interface (Command Palette) for Vue. 15 | 16 | ## Preview 17 | 18 | ![Preview](vue-command-palette.gif) 19 | 20 | ## Concepts 21 | 22 | Command palette interfaces are used to create a web experience where users can quickly get in charge with keyboard shortcuts, rather than using the mouse. 23 | 24 | With macOS's `Spotlight` and `Raycast`'s command palette experience in mind, `vue-command-palette` is designed to provide a fast, composable, unstyled command palette to your site. 25 | 26 | ## Table of Contents 27 | 28 |
29 | 30 | TOC 31 | 32 | - [Command Palette for Vue](#command-palette-for-vue) 33 | - [Preview](#preview) 34 | - [Concepts](#concepts) 35 | - [Table of Contents](#table-of-contents) 36 | - [Features](#features) 37 | - [Install](#install) 38 | - [Usage](#usage) 39 | - [Command + K or ?](#command--k-or-) 40 | - [Events](#events) 41 | - [Styling](#styling) 42 | - [Animation](#animation) 43 | - [Command.Dialog](#commanddialog) 44 | - [List Item Height](#list-item-height) 45 | - [Namespaced components](#namespaced-components) 46 | - [Command `[command-root=""]`](#command-command-root) 47 | - [Command.Dialog `[command-dialog=""]`](#commanddialog-command-dialog) 48 | - [Command.Input `[command-input=""]`](#commandinput-command-input) 49 | - [Command.List `[command-list=""]`](#commandlist-command-list) 50 | - [Command.Group `[command-group=""]`](#commandgroup-command-group) 51 | - [Command.Item `[command-item=""]`](#commanditem-command-item) 52 | - [Command.Separator `[command-separator=""]`](#commandseparator-command-separator) 53 | - [Command.Empty `[command-empty=""]`](#commandempty-command-empty) 54 | - [Command.Loading `[command-loading=""]`](#commandloading-command-loading) 55 | - [Inspiration](#inspiration) 56 | - [License](#license) 57 | 58 |
59 | 60 | ## Features 61 | 62 | - 🧩 [Compound Component](https://kentcdodds.com/blog/compound-components-with-react-hooks) + [Namespaced Components](https://vuejs.org/api/sfc-script-setup.html#using-components) Design 63 | - 💄 Completely unstyled, but more customizable 64 | - 🔍 Fuzzy search support to find relevant command 65 | - ⌨️ keyboard shortcut support to bind custom keybindings to your command 66 | 67 | ## Install 68 | 69 | ```bash 70 | yarn add vue-command-palette 71 | # or 72 | pnpm add vue-command-palette 73 | ``` 74 | 75 | ## Usage 76 | 77 | Then you can import the `Command` [Compound Component](https://kentcdodds.com/blog/compound-components-with-react-hooks) in your project. 78 | 79 | ```vue 80 | 84 | 85 | 102 | 103 | 107 | ``` 108 | 109 | or in a dialog: 110 | 111 | ```vue 112 | 118 | 119 | 140 | 141 | 145 | ``` 146 | 147 | ### Command + K or ? 148 | 149 | Do I have to use command + K? No, it's just a convention that you can use any key binding to perform the Command Palette. 150 | 151 | > Tips, we use `@vueuse/core` to bind the keybindings 152 | 153 | ```html 154 | 168 | ``` 169 | 170 | ### Events 171 | 172 | | Name | Description | Parameters | 173 | | :-----------: | --------------------------------------------------------------------- | :--------------- | 174 | | `select-item` | Every time an item is being selected in `Command` or `Command.Dialog` | `(item) => void` | 175 | 176 | ### Styling 177 | 178 | All namespaced components have a specific `data-attribute` starting with `command-` that can be used for styling. 179 | 180 | eg: 181 | 182 | ```css 183 | div[command-root=''] { 184 | background: #ffffff; 185 | } 186 | ``` 187 | 188 | ### Animation 189 | 190 | #### Command.Dialog 191 | 192 | `Command.Dialog` wraped by built-in components [Transition](https://vuejs.org/guide/built-ins/transition.html), you can customize the animation using the name `command-dialog` . 193 | 194 | [Example Code](https://github.com/xiaoluoboding/vue-command-palette/blob/main/src/assets/scss/algolia.scss#L193) 195 | 196 | #### List Item Height 197 | 198 | Animate height using the `--command-list-height` CSS variable. 199 | 200 | [Example Code](https://github.com/xiaoluoboding/vue-command-palette/blob/main/src/assets/scss/algolia.scss#L26) 201 | 202 | ## Namespaced components 203 | 204 | With [Namespaced components](https://vuejs.org/api/sfc-script-setup.html#using-components), You can use component tags with dots like `` to refer to components nested under object properties. This is useful when you import multiple components from a single file. 205 | 206 | ### Command `[command-root=""]` 207 | 208 | The root component, Passes the `theme` props to set your own style. 209 | 210 | ```vue 211 | 212 | 213 | 214 | ``` 215 | 216 | ### Command.Dialog `[command-dialog=""]` 217 | 218 | The root component with a dialog interface, [Teleport](https://vuejs.org/guide/built-ins/teleport.html) dialog to `body` tag. Passes the `theme` props to set your own style, and `visible` props control whether render it. 219 | 220 | ```vue 221 | 222 | 223 | 224 | 225 | 226 | 227 | ``` 228 | 229 | `data-attribute` within dialog 230 | 231 | - `[command-dialog-mask]` - the mask is always rendered. 232 | - `[command-dialog-wrapper]` - the wrapper on top of mask. 233 | - `[command-dialog-header]` - the parent of dialog header slot. 234 | - `[command-dialog-body]` - the parent of dialog body slot. 235 | - `[command-dialog-footer]` - the parent of dialog footer slot. 236 | 237 | ### Command.Input `[command-input=""]` 238 | 239 | Usually we need a input in the command palette to search sth. 240 | 241 | ```vue 242 | 246 | ``` 247 | 248 | ### Command.List `[command-list=""]` 249 | 250 | Contains items and groups. Animate height using the `--command-list-height` CSS variable. 251 | 252 | ```css 253 | [command-list] { 254 | min-height: 300px; 255 | height: var(--command-list-height); 256 | max-height: 500px; 257 | transition: height 100ms ease; 258 | } 259 | ``` 260 | 261 | ```vue 262 | 263 | 264 | 265 | ``` 266 | 267 | ### Command.Group `[command-group=""]` 268 | 269 | Group items (`[command-group-items]`) together with the given `heading` (`[command-group-heading]`) 270 | 271 | ```vue 272 | 273 | Toggle Dark Mode 274 | Change Language 275 | 276 | ``` 277 | 278 | ### Command.Item `[command-item=""]` 279 | 280 | Passed the `data-value`, we use `data-value` to fetch the value. 281 | 282 | ```vue 283 | 291 | {{ item.label }} 292 | 293 | ``` 294 | 295 | ### Command.Separator `[command-separator=""]` 296 | 297 | Usually used to distinguish between different groups 298 | 299 | ### Command.Empty `[command-empty=""]` 300 | 301 | Automatically renders when there are no results for the search query. 302 | 303 | ### Command.Loading `[command-loading=""]` 304 | 305 | Your should manually control `loading` 306 | 307 | ## Inspiration 308 | 309 | - [cmdk](https://github.com/pacocoursey/cmdk) - Fast, unstyled command menu React component. 310 | - [kbar](https://github.com/timc1/kbar) - fast, portable, and extensible cmd+k interface for your site. 311 | 312 | ## License 313 | 314 | MIT [@xiaoluoboding](https://github.com/xiaoluoboding) 315 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "/temp/index.d.ts", 4 | "bundledPackages": [], 5 | "compiler": { 6 | "tsconfigFilePath": "/tsconfig.build.json" 7 | }, 8 | "apiReport": { 9 | "enabled": false 10 | }, 11 | "docModel": { 12 | "enabled": false 13 | }, 14 | "dtsRollup": { 15 | "enabled": true, 16 | "untrimmedFilePath": "/lib/.d.ts" 17 | }, 18 | "tsdocMetadata": { 19 | "enabled": false 20 | }, 21 | "messages": { 22 | "extractorMessageReporting": { 23 | "ae-forgotten-export": { 24 | "logLevel": "none" 25 | }, 26 | "ae-missing-release-tag": { 27 | "logLevel": "none" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | CmdkPlaceholder: typeof import('./src/components/common/CmdkPlaceholder.vue')['default'] 11 | FigmaIcon: typeof import('./src/components/icons/FigmaIcon.vue')['default'] 12 | Home: typeof import('./src/components/command/raycast/Home.vue')['default'] 13 | Item: typeof import('./src/components/command/raycast/Item.vue')['default'] 14 | Linear: typeof import('./src/components/command/Linear.vue')['default'] 15 | LinearAssignToIcon: typeof import('./src/components/icons/LinearAssignToIcon.vue')['default'] 16 | LinearAssignToMeIcon: typeof import('./src/components/icons/LinearAssignToMeIcon.vue')['default'] 17 | LinearChangeLabelsIcon: typeof import('./src/components/icons/LinearChangeLabelsIcon.vue')['default'] 18 | LinearChangePriorityIcon: typeof import('./src/components/icons/LinearChangePriorityIcon.vue')['default'] 19 | LinearChangeStatusIcon: typeof import('./src/components/icons/LinearChangeStatusIcon.vue')['default'] 20 | LinearIcon: typeof import('./src/components/icons/LinearIcon.vue')['default'] 21 | LinearRemoveLabelIcon: typeof import('./src/components/icons/LinearRemoveLabelIcon.vue')['default'] 22 | LinearSetDueDateIcon: typeof import('./src/components/icons/LinearSetDueDateIcon.vue')['default'] 23 | Logo: typeof import('./src/components/icons/Logo.vue')['default'] 24 | MoonIcon: typeof import('./src/components/icons/MoonIcon.vue')['default'] 25 | Projects: typeof import('./src/components/command/vercel/Projects.vue')['default'] 26 | Raycast: typeof import('./src/components/command/raycast/Raycast.vue')['default'] 27 | RaycastClipboardIcon: typeof import('./src/components/icons/RaycastClipboardIcon.vue')['default'] 28 | RaycastDarkIcon: typeof import('./src/components/icons/RaycastDarkIcon.vue')['default'] 29 | RaycasTerminalIcon: typeof import('./src/components/icons/RaycasTerminalIcon.vue')['default'] 30 | RaycastFinderIcon: typeof import('./src/components/icons/RaycastFinderIcon.vue')['default'] 31 | RaycastHammerIcon: typeof import('./src/components/icons/RaycastHammerIcon.vue')['default'] 32 | RaycastIcon: typeof import('./src/components/icons/RaycastIcon.vue')['default'] 33 | RaycastLightIcon: typeof import('./src/components/icons/RaycastLightIcon.vue')['default'] 34 | RaycastStarIcon: typeof import('./src/components/icons/RaycastStarIcon.vue')['default'] 35 | RaycastWindowIcon: typeof import('./src/components/icons/RaycastWindowIcon.vue')['default'] 36 | Self: typeof import('./src/components/command/Self.vue')['default'] 37 | SlackIcon: typeof import('./src/components/icons/SlackIcon.vue')['default'] 38 | SunIcon: typeof import('./src/components/icons/SunIcon.vue')['default'] 39 | Vercel: typeof import('./src/components/command/vercel/Vercel.vue')['default'] 40 | VercelContactIcon: typeof import('./src/components/icons/VercelContactIcon.vue')['default'] 41 | VercelCopyIcon: typeof import('./src/components/icons/VercelCopyIcon.vue')['default'] 42 | VercelDocsIcon: typeof import('./src/components/icons/VercelDocsIcon.vue')['default'] 43 | VercelFeedbackIcon: typeof import('./src/components/icons/VercelFeedbackIcon.vue')['default'] 44 | VercelIcon: typeof import('./src/components/icons/VercelIcon.vue')['default'] 45 | VercelPlusIcon: typeof import('./src/components/icons/VercelPlusIcon.vue')['default'] 46 | VercelProjectsIcon: typeof import('./src/components/icons/VercelProjectsIcon.vue')['default'] 47 | VercelTeamsIcon: typeof import('./src/components/icons/VercelTeamsIcon.vue')['default'] 48 | YouTubeIcon: typeof import('./src/components/icons/YouTubeIcon.vue')['default'] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Command Palette 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /og:image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoluoboding/vue-command-palette/9daf5aa2c26f33fba65d2c1de05720fe769126dd/og:image.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-command-palette", 3 | "type": "module", 4 | "version": "0.2.3", 5 | "author": "xiaoluoboding ", 6 | "homepage": "https://github.com/xiaoluoboding/vue-command-palette", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/xiaoluoboding/vue-command-palette.git" 10 | }, 11 | "exports": { 12 | ".": { 13 | "types": "./lib/index.d.ts", 14 | "import": "./lib/vue-command-palette.js", 15 | "require": "./lib/vue-command-palette.cjs" 16 | } 17 | }, 18 | "main": "./lib/vue-command-palette.cjs", 19 | "module": "./lib/vue-command-palette.js", 20 | "types": "./lib/index.d.ts", 21 | "files": [ 22 | "lib" 23 | ], 24 | "scripts": { 25 | "dev": "vite", 26 | "build:docs": "vite build --mode docs", 27 | "build:lib": "vite build --mode lib && pnpm run build:types", 28 | "build:types": "vue-tsc -p tsconfig.build.json && api-extractor run", 29 | "preview": "vite preview", 30 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" 31 | }, 32 | "dependencies": { 33 | "fuse.js": "^7.0.0", 34 | "nanoid": "^5.0.6" 35 | }, 36 | "devDependencies": { 37 | "@iconify/json": "^2.2.189", 38 | "@microsoft/api-extractor": "^7.42.3", 39 | "@types/node": "^20.11.24", 40 | "@unocss/reset": "^0.58.5", 41 | "@vitejs/plugin-vue": "^5.0.4", 42 | "@vue/tsconfig": "^0.5.1", 43 | "@vueuse/core": "^10.9.0", 44 | "highlight.js": "^11.9.0", 45 | "sass": "^1.71.1", 46 | "typescript": "^5.3.3", 47 | "unocss": "^0.58.5", 48 | "unplugin-icons": "^0.18.5", 49 | "unplugin-vue-components": "^0.26.0", 50 | "vite": "^5.1.5", 51 | "vue": "^3.4.21", 52 | "vue-tsc": "^2.0.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/CommandDialog.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 62 | -------------------------------------------------------------------------------- /packages/CommandGroup.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | -------------------------------------------------------------------------------- /packages/CommandInput.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | 34 | 50 | -------------------------------------------------------------------------------- /packages/CommandItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 66 | 67 | 82 | -------------------------------------------------------------------------------- /packages/CommandList.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /packages/CommandProvider.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/CommandRoot.vue: -------------------------------------------------------------------------------- 1 | 371 | 372 | 384 | -------------------------------------------------------------------------------- /packages/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, h } from 'vue' 2 | import Root from './CommandRoot.vue' 3 | import Dialog from './CommandDialog.vue' 4 | import Group from './CommandGroup.vue' 5 | import Input from './CommandInput.vue' 6 | import Item from './CommandItem.vue' 7 | import List from './CommandList.vue' 8 | import { useCommandState } from './useCommandState' 9 | 10 | /** 11 | * Command Empty Node 12 | */ 13 | const Empty = defineComponent({ 14 | name: 'Command.Empty', 15 | setup(props, { attrs, slots }) { 16 | const { filtered } = useCommandState() 17 | const isRender = computed(() => filtered.value.count === 0) 18 | return () => 19 | isRender.value 20 | ? h( 21 | 'div', 22 | { 23 | 'command-empty': '', 24 | 'role': 'presentation', 25 | ...attrs, 26 | }, 27 | slots, 28 | ) 29 | : h('div', { 30 | 'command-empty': 'hidden', 31 | 'role': 'presentation', 32 | 'style': { 33 | display: 'none', 34 | }, 35 | ...attrs, 36 | }) 37 | }, 38 | }) 39 | 40 | /** 41 | * Command Loading Node 42 | */ 43 | const Loading = defineComponent({ 44 | name: 'Command.Loading', 45 | setup(props, { attrs, slots }) { 46 | return () => 47 | h( 48 | 'div', 49 | { 50 | 'command-loading': '', 51 | 'role': 'progressbar', 52 | ...attrs, 53 | }, 54 | slots, 55 | ) 56 | }, 57 | }) 58 | 59 | /** 60 | * Command Separator Node 61 | */ 62 | const Separator = defineComponent({ 63 | name: 'Command.Separator', 64 | setup(props, { attrs, slots }) { 65 | return () => 66 | h('div', { 67 | 'command-separator': '', 68 | 'role': 'separator', 69 | ...attrs, 70 | }) 71 | }, 72 | }) 73 | 74 | const Command = Object.assign(Root, { 75 | Root, 76 | Dialog, 77 | Empty, 78 | Group, 79 | Input, 80 | Item, 81 | List, 82 | Loading, 83 | Separator, 84 | }) as typeof Root & { 85 | readonly Root: typeof Root 86 | readonly Dialog: typeof Dialog 87 | readonly Empty: typeof Empty 88 | readonly Group: typeof Group 89 | readonly Input: typeof Input 90 | readonly Item: typeof Item 91 | readonly List: typeof List 92 | readonly Loading: typeof Loading 93 | readonly Separator: typeof Separator 94 | } 95 | 96 | export { Command, useCommandState } 97 | export type { CommandGroupProps, CommandInputEmits, CommandInputProps, CommandItemEmits, CommandItemProps, CommandRootEmits, CommandRootProps } from './types' 98 | -------------------------------------------------------------------------------- /packages/types.ts: -------------------------------------------------------------------------------- 1 | import type { IFuseOptions } from 'fuse.js' 2 | 3 | export interface ItemInfo { 4 | key: string 5 | value: string 6 | } 7 | 8 | export type Noop = () => void 9 | 10 | export interface CommandRootProps { 11 | theme?: string 12 | fuseOptions?: IFuseOptions 13 | } 14 | 15 | export type CommandRootEmits = { 16 | selectItem: [item: ItemInfo] 17 | } 18 | 19 | export interface CommandItemProps { 20 | shortcut?: string[] 21 | perform?: Noop 22 | } 23 | 24 | export type CommandItemEmits = { 25 | select: [item: ItemInfo] 26 | } 27 | 28 | export interface CommandInputProps { 29 | placeholder?: string 30 | value?: string 31 | } 32 | 33 | export type CommandInputEmits = { 34 | input: [ie: InputEvent] 35 | 'update:value': [val: any] 36 | } 37 | 38 | export interface CommandGroupProps { 39 | heading: string 40 | } 41 | -------------------------------------------------------------------------------- /packages/useCommandEvent.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import type { ItemInfo } from './types' 3 | 4 | export interface Events { 5 | selectItem: ItemInfo 6 | rerenderList: boolean 7 | } 8 | 9 | function useCommandEvent() { 10 | const itemInfo = ref() 11 | const rerenderList = ref(false) 12 | 13 | return { 14 | itemInfo, 15 | rerenderList, 16 | } 17 | } 18 | 19 | export { useCommandEvent } 20 | -------------------------------------------------------------------------------- /packages/useCommandState.ts: -------------------------------------------------------------------------------- 1 | import { computed, reactive, toRefs } from 'vue' 2 | 3 | interface FilteredItem { 4 | count: number 5 | items: Map 6 | groups: Set 7 | } 8 | 9 | interface State { 10 | // Event State 11 | selectedNode: string 12 | selectedGroup: string 13 | shouldRerender: boolean 14 | // Input State 15 | search: string 16 | filtered: FilteredItem 17 | } 18 | 19 | const state = reactive({ 20 | selectedNode: '', 21 | selectedGroup: '', 22 | shouldRerender: false, 23 | /** Value of the search query. */ 24 | search: '', 25 | filtered: { 26 | /** The count of all visible items. */ 27 | count: 0, 28 | /** Map from visible item id. */ 29 | items: new Map(), 30 | /** Set of groups with at least one visible item. */ 31 | groups: new Set(), 32 | }, 33 | }) 34 | 35 | function useCommandState() { 36 | const isSearching = computed(() => state.search !== '') 37 | 38 | const resetStore = () => { 39 | // reset the command state 40 | state.search = '' 41 | state.filtered.count = 0 42 | state.filtered.items = new Map() 43 | state.filtered.groups = new Set() 44 | } 45 | 46 | return { 47 | isSearching, 48 | resetStore, 49 | ...toRefs(state), 50 | } 51 | } 52 | 53 | export { useCommandState } 54 | -------------------------------------------------------------------------------- /packages/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helpers 3 | */ 4 | 5 | export function findNextSibling(el: Element, selector: string) { 6 | let sibling = el.nextElementSibling 7 | 8 | while (sibling) { 9 | if (sibling.matches(selector)) 10 | return sibling 11 | sibling = sibling.nextElementSibling 12 | } 13 | } 14 | 15 | export function findPreviousSibling(el: Element, selector: string) { 16 | let sibling = el.previousElementSibling 17 | 18 | while (sibling) { 19 | if (sibling.matches(selector)) 20 | return sibling 21 | sibling = sibling.previousElementSibling 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 224 | -------------------------------------------------------------------------------- /src/assets/scss/algolia.scss: -------------------------------------------------------------------------------- 1 | .algolia { 2 | [command-root] { 3 | padding: 8px; 4 | } 5 | 6 | [command-input] { 7 | font-family: var(--font-sans); 8 | width: 100%; 9 | font-size: 18px; 10 | padding: 12px; 11 | outline: none; 12 | background: var(--bg); 13 | color: var(--gray12); 14 | border-bottom: 1px solid var(--gray6); 15 | border-radius: 4px; 16 | caret-color: var(--vcp-c-brand); 17 | margin: 0; 18 | border: 1px solid var(--vcp-c-brand); 19 | 20 | &::placeholder { 21 | color: var(--gray9); 22 | } 23 | } 24 | 25 | [command-list] { 26 | height: var(--command-list-height); 27 | max-height: 360px; 28 | overflow: auto; 29 | overscroll-behavior: contain; 30 | transition: 100ms ease; 31 | transition-property: height; 32 | } 33 | 34 | [command-item] { 35 | position: relative; 36 | content-visibility: auto; 37 | cursor: pointer; 38 | height: 56px; 39 | font-size: 14px; 40 | display: flex; 41 | align-items: center; 42 | gap: 12px; 43 | padding: 0px 16px; 44 | color: var(--gray12); 45 | user-select: none; 46 | will-change: background, color; 47 | transition: all 150ms ease; 48 | transition-property: none; 49 | border-radius: 4px; 50 | margin-top: 4px; 51 | 52 | &:first-child { 53 | margin-top: 0px; 54 | } 55 | 56 | &[aria-selected='true'], 57 | &:hover { 58 | background: var(--vcp-c-brand); 59 | color: #fff; 60 | 61 | svg { 62 | color: #fff; 63 | } 64 | 65 | [command-linear-shortcuts] { 66 | display: flex; 67 | margin-left: auto; 68 | gap: 8px; 69 | 70 | kbd { 71 | font-family: var(--font-sans); 72 | font-size: 13px; 73 | color: var(--gray11); 74 | } 75 | } 76 | } 77 | 78 | &[aria-disabled='true'] { 79 | color: var(--gray8); 80 | cursor: not-allowed; 81 | } 82 | 83 | &:active { 84 | transition-property: background; 85 | background: var(--gray4); 86 | } 87 | 88 | svg { 89 | width: 16px; 90 | height: 16px; 91 | color: var(--gray10); 92 | } 93 | } 94 | 95 | [command-empty=''] { 96 | font-size: 14px; 97 | display: flex; 98 | align-items: center; 99 | justify-content: center; 100 | height: 64px; 101 | white-space: pre-wrap; 102 | color: var(--gray11); 103 | } 104 | 105 | [command-dialog-mask] { 106 | background-color: rgba(75, 75, 75, 0.8); 107 | } 108 | 109 | [command-dialog-header] { 110 | padding: 12px; 111 | } 112 | 113 | [command-dialog-body] { 114 | padding: 0 12px 12px; 115 | } 116 | 117 | [command-dialog-footer] { 118 | align-items: center; 119 | border-radius: 0 0 8px 8px; 120 | box-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12); 121 | display: flex; 122 | flex-direction: row-reverse; 123 | flex-shrink: 0; 124 | height: 44px; 125 | justify-content: space-between; 126 | padding: 0 12px; 127 | position: relative; 128 | user-select: none; 129 | width: 100%; 130 | z-index: 300; 131 | } 132 | 133 | [command-group-heading] { 134 | color: var(--vcp-c-brand); 135 | font-size: 0.85em; 136 | font-weight: 600; 137 | line-height: 32px; 138 | margin: 0 -4px; 139 | padding: 8px 4px 0; 140 | top: 0; 141 | z-index: 10; 142 | width: 100%; 143 | } 144 | 145 | .command-palette-commands { 146 | color: var(--docsearch-muted-color); 147 | display: flex; 148 | list-style: none; 149 | margin: 0; 150 | padding: 0; 151 | 152 | li { 153 | display: flex; 154 | align-items: center; 155 | } 156 | li:not(:last-of-type) { 157 | margin-right: 0.8em; 158 | } 159 | } 160 | 161 | .command-palette-logo { 162 | a { 163 | display: flex; 164 | align-items: center; 165 | gap: 8px; 166 | } 167 | svg { 168 | height: 24px; 169 | width: 24px; 170 | } 171 | } 172 | 173 | .command-palette-commands-key { 174 | align-items: center; 175 | background: var(--gray3); 176 | border-radius: 2px; 177 | display: flex; 178 | height: 18px; 179 | justify-content: center; 180 | margin-right: 0.4em; 181 | padding: 0 0 1px; 182 | color: var(--gray11); 183 | border: 0; 184 | width: 20px; 185 | } 186 | } 187 | 188 | .dark .algolia [command-dialog-footer] { 189 | box-shadow: none; 190 | } 191 | 192 | // transition for command-dialog 193 | .command-dialog-enter-active, 194 | .command-dialog-leave-active { 195 | transition: opacity 0.2s ease-in-out; 196 | } 197 | 198 | .command-dialog-enter-from, 199 | .command-dialog-leave-to { 200 | opacity: 0; 201 | } 202 | 203 | .command-dialog-enter-active { 204 | transition-duration: 0.5s; 205 | } 206 | 207 | // transition for command-dialog-wrapper 208 | .command-dialog-enter-active [command-dialog-wrapper] { 209 | transition-delay: 0.2s; 210 | } 211 | 212 | .command-dialog-enter-from [command-dialog-wrapper], 213 | .command-dialog-leave-to [command-dialog-wrapper] { 214 | opacity: 0; 215 | transform: translateY(10px) scale(0.95); 216 | } 217 | 218 | .command-dialog-enter-active [command-dialog-wrapper] { 219 | transition: transform ease-out 0.3s; 220 | } 221 | 222 | .command-dialog-enter-to [command-dialog-wrapper], 223 | .command-dialog-leave-from [command-dialog-wrapper] { 224 | opacity: 1; 225 | transform: translateY(0) scale(1); 226 | } 227 | 228 | .command-dialog-leave-active [command-dialog-wrapper] { 229 | transition: transfrom ease-in 0.2s; 230 | } 231 | -------------------------------------------------------------------------------- /src/assets/scss/framer.scss: -------------------------------------------------------------------------------- 1 | .framer { 2 | [command-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 16px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--command-shadow); 12 | 13 | .dark & { 14 | background: var(--gray2); 15 | } 16 | } 17 | 18 | [command-framer-header] { 19 | display: flex; 20 | align-items: center; 21 | gap: 8px; 22 | height: 48px; 23 | padding: 0 8px; 24 | border-bottom: 1px solid var(--gray5); 25 | margin-bottom: 12px; 26 | padding-bottom: 8px; 27 | 28 | svg { 29 | width: 20px; 30 | height: 20px; 31 | color: var(--gray9); 32 | transform: translateY(1px); 33 | } 34 | } 35 | 36 | [command-input] { 37 | font-family: var(--font-sans); 38 | border: none; 39 | width: 100%; 40 | font-size: 16px; 41 | outline: none; 42 | background: var(--bg); 43 | color: var(--gray12); 44 | 45 | &::placeholder { 46 | color: var(--gray9); 47 | } 48 | } 49 | 50 | [command-item] { 51 | content-visibility: auto; 52 | 53 | cursor: pointer; 54 | border-radius: 12px; 55 | font-size: 14px; 56 | display: flex; 57 | align-items: center; 58 | gap: 12px; 59 | color: var(--gray12); 60 | padding: 8px 8px; 61 | margin-right: 8px; 62 | font-weight: 500; 63 | transition: all 150ms ease; 64 | transition-property: none; 65 | 66 | &[aria-selected='true'] { 67 | background: var(--blue9); 68 | color: #ffffff; 69 | 70 | [command-framer-item-subtitle] { 71 | color: #ffffff; 72 | } 73 | } 74 | 75 | &[aria-disabled='true'] { 76 | color: var(--gray8); 77 | cursor: not-allowed; 78 | } 79 | 80 | & + [command-item] { 81 | margin-top: 4px; 82 | } 83 | 84 | svg { 85 | width: 16px; 86 | height: 16px; 87 | color: #ffffff; 88 | } 89 | } 90 | 91 | [command-framer-icon-wrapper] { 92 | display: flex; 93 | align-items: center; 94 | justify-content: center; 95 | min-width: 32px; 96 | height: 32px; 97 | background: orange; 98 | border-radius: 8px; 99 | } 100 | 101 | [command-framer-item-meta] { 102 | display: flex; 103 | flex-direction: column; 104 | gap: 4px; 105 | } 106 | 107 | [command-framer-item-subtitle] { 108 | font-size: 12px; 109 | font-weight: 400; 110 | color: var(--gray11); 111 | } 112 | 113 | [command-framer-items] { 114 | min-height: 308px; 115 | display: flex; 116 | } 117 | 118 | [command-framer-left] { 119 | width: 40%; 120 | } 121 | 122 | [command-framer-separator] { 123 | width: 1px; 124 | border: 0; 125 | margin-right: 8px; 126 | background: var(--gray6); 127 | } 128 | 129 | [command-framer-right] { 130 | display: flex; 131 | align-items: center; 132 | justify-content: center; 133 | border-radius: 8px; 134 | margin-left: 8px; 135 | width: 60%; 136 | 137 | button { 138 | width: 120px; 139 | height: 40px; 140 | background: var(--blue9); 141 | border-radius: 6px; 142 | font-weight: 500; 143 | color: white; 144 | font-size: 14px; 145 | } 146 | 147 | input[type='text'] { 148 | height: 40px; 149 | width: 160px; 150 | border: 1px solid var(--gray6); 151 | background: #ffffff; 152 | border-radius: 6px; 153 | padding: 0 8px; 154 | font-size: 14px; 155 | font-family: var(--font-sans); 156 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.08); 157 | 158 | &::placeholder { 159 | color: var(--gray9); 160 | } 161 | 162 | @media (prefers-color-scheme: dark) { 163 | background: var(--gray3); 164 | } 165 | } 166 | 167 | [command-framer-radio] { 168 | display: flex; 169 | align-items: center; 170 | gap: 4px; 171 | color: var(--gray12); 172 | font-weight: 500; 173 | font-size: 14px; 174 | accent-color: var(--blue9); 175 | 176 | input { 177 | width: 20px; 178 | height: 20px; 179 | } 180 | } 181 | 182 | img { 183 | width: 40px; 184 | height: 40px; 185 | border-radius: 9999px; 186 | border: 1px solid var(--gray6); 187 | } 188 | 189 | [command-framer-container] { 190 | width: 100px; 191 | height: 100px; 192 | background: var(--blue9); 193 | border-radius: 16px; 194 | } 195 | 196 | [command-framer-badge] { 197 | background: var(--blue3); 198 | padding: 0 8px; 199 | height: 28px; 200 | font-size: 14px; 201 | line-height: 28px; 202 | color: var(--blue11); 203 | border-radius: 9999px; 204 | font-weight: 500; 205 | } 206 | 207 | [command-framer-slider] { 208 | height: 20px; 209 | width: 200px; 210 | background: linear-gradient(90deg, var(--blue9) 40%, var(--gray3) 0%); 211 | border-radius: 9999px; 212 | 213 | div { 214 | width: 20px; 215 | height: 20px; 216 | background: #ffffff; 217 | border-radius: 9999px; 218 | box-shadow: 0 1px 3px -1px rgba(0, 0, 0, 0.32); 219 | transform: translateX(70px); 220 | } 221 | } 222 | } 223 | 224 | [command-list] { 225 | overflow: auto; 226 | } 227 | 228 | [command-separator] { 229 | height: 1px; 230 | width: 100%; 231 | background: var(--gray5); 232 | margin: 4px 0; 233 | } 234 | 235 | [command-group-heading] { 236 | user-select: none; 237 | font-size: 12px; 238 | color: var(--gray11); 239 | padding: 0 8px; 240 | display: flex; 241 | align-items: center; 242 | margin-bottom: 8px; 243 | } 244 | 245 | [command-empty] { 246 | font-size: 14px; 247 | padding: 32px; 248 | white-space: pre-wrap; 249 | color: var(--gray11); 250 | } 251 | } 252 | 253 | @media (max-width: 640px) { 254 | .framer { 255 | [command-framer-icon-wrapper] { 256 | } 257 | 258 | [command-framer-item-subtitle] { 259 | display: none; 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/assets/scss/global.scss: -------------------------------------------------------------------------------- 1 | @import 'highlight.scss'; 2 | 3 | html, 4 | body, 5 | #app { 6 | height: 100%; 7 | } 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | ul { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | :root { 19 | --font-sans: 'Inter', --apple-system, BlinkMacSystemFont, Segoe UI, Roboto, 20 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 21 | --app-bg: var(--gray1); 22 | --app-text: #000000; 23 | --command-shadow: 0 16px 70px rgb(0 0 0 / 20%); 24 | 25 | --lowContrast: #ffffff; 26 | --highContrast: #000000; 27 | --vcp-c-brand: #44bd87; 28 | --vcp-c-accent: #35495e; 29 | 30 | --gray1: hsl(0, 0%, 98%); 31 | --gray2: hsl(0, 0%, 97.3%); 32 | --gray3: hsl(0, 0%, 95.1%); 33 | --gray4: hsl(0, 0%, 93%); 34 | --gray5: hsl(0, 0%, 90.9%); 35 | --gray6: hsl(0, 0%, 88.7%); 36 | --gray7: hsl(0, 0%, 85.8%); 37 | --gray8: hsl(0, 0%, 78%); 38 | --gray9: hsl(0, 0%, 56.1%); 39 | --gray10: hsl(0, 0%, 52.3%); 40 | --gray11: hsl(0, 0%, 43.5%); 41 | --gray12: hsl(0, 0%, 9%); 42 | 43 | --grayA1: hsla(0, 0%, 0%, 0.012); 44 | --grayA2: hsla(0, 0%, 0%, 0.027); 45 | --grayA3: hsla(0, 0%, 0%, 0.047); 46 | --grayA4: hsla(0, 0%, 0%, 0.071); 47 | --grayA5: hsla(0, 0%, 0%, 0.09); 48 | --grayA6: hsla(0, 0%, 0%, 0.114); 49 | --grayA7: hsla(0, 0%, 0%, 0.141); 50 | --grayA8: hsla(0, 0%, 0%, 0.22); 51 | --grayA9: hsla(0, 0%, 0%, 0.439); 52 | --grayA10: hsla(0, 0%, 0%, 0.478); 53 | --grayA11: hsla(0, 0%, 0%, 0.565); 54 | --grayA12: hsla(0, 0%, 0%, 0.91); 55 | 56 | --blue1: hsl(206, 100%, 99.2%); 57 | --blue2: hsl(210, 100%, 98%); 58 | --blue3: hsl(209, 100%, 96.5%); 59 | --blue4: hsl(210, 98.8%, 94%); 60 | --blue5: hsl(209, 95%, 90.1%); 61 | --blue6: hsl(209, 81.2%, 84.5%); 62 | --blue7: hsl(208, 77.5%, 76.9%); 63 | --blue8: hsl(206, 81.9%, 65.3%); 64 | --blue9: hsl(206, 100%, 50%); 65 | --blue10: hsl(208, 100%, 47.3%); 66 | --blue11: hsl(211, 100%, 43.2%); 67 | --blue12: hsl(211, 100%, 15%); 68 | } 69 | 70 | .dark { 71 | --app-bg: var(--gray1); 72 | --app-text: #ffffff; 73 | 74 | --lowContrast: #000000; 75 | --highContrast: #ffffff; 76 | 77 | --gray1: hsl(0, 0%, 8.5%); 78 | --gray2: hsl(0, 0%, 11%); 79 | --gray3: hsl(0, 0%, 13.6%); 80 | --gray4: hsl(0, 0%, 15.8%); 81 | --gray5: hsl(0, 0%, 17.9%); 82 | --gray6: hsl(0, 0%, 20.5%); 83 | --gray7: hsl(0, 0%, 24.3%); 84 | --gray8: hsl(0, 0%, 31.2%); 85 | --gray9: hsl(0, 0%, 43.9%); 86 | --gray10: hsl(0, 0%, 49.4%); 87 | --gray11: hsl(0, 0%, 62.8%); 88 | --gray12: hsl(0, 0%, 93%); 89 | 90 | --grayA1: hsla(0, 0%, 100%, 0); 91 | --grayA2: hsla(0, 0%, 100%, 0.026); 92 | --grayA3: hsla(0, 0%, 100%, 0.056); 93 | --grayA4: hsla(0, 0%, 100%, 0.077); 94 | --grayA5: hsla(0, 0%, 100%, 0.103); 95 | --grayA6: hsla(0, 0%, 100%, 0.129); 96 | --grayA7: hsla(0, 0%, 100%, 0.172); 97 | --grayA8: hsla(0, 0%, 100%, 0.249); 98 | --grayA9: hsla(0, 0%, 100%, 0.386); 99 | --grayA10: hsla(0, 0%, 100%, 0.446); 100 | --grayA11: hsla(0, 0%, 100%, 0.592); 101 | --grayA12: hsla(0, 0%, 100%, 0.923); 102 | 103 | --blue1: hsl(212, 35%, 9.2%); 104 | --blue2: hsl(216, 50%, 11.8%); 105 | --blue3: hsl(214, 59.4%, 15.3%); 106 | --blue4: hsl(214, 65.8%, 17.9%); 107 | --blue5: hsl(213, 71.2%, 20.2%); 108 | --blue6: hsl(212, 77.4%, 23.1%); 109 | --blue7: hsl(211, 85.1%, 27.4%); 110 | --blue8: hsl(211, 89.7%, 34.1%); 111 | --blue9: hsl(206, 100%, 50%); 112 | --blue10: hsl(209, 100%, 60.6%); 113 | --blue11: hsl(210, 100%, 66.1%); 114 | --blue12: hsl(206, 98%, 95.8%); 115 | } 116 | 117 | body { 118 | background: var(--app-bg); 119 | } 120 | 121 | div [command-dialog-mask] { 122 | background-color: rgba(0, 0, 0, 0.3); 123 | height: 100vh; 124 | left: 0; 125 | position: fixed; 126 | top: 0; 127 | width: 100vw; 128 | z-index: 200; 129 | animation: 333ms cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running shown; 130 | } 131 | 132 | div [command-dialog-wrapper] { 133 | position: relative; 134 | background: var(--gray2); 135 | border-radius: 6px; 136 | box-shadow: none; 137 | flex-direction: column; 138 | margin: 20vh auto auto; 139 | max-width: 560px; 140 | animation: 333ms cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running shown; 141 | } 142 | 143 | div [command-dialog-footer] { 144 | border-top: 1px solid var(--gray6); 145 | align-items: center; 146 | background: var(--gray4); 147 | color: var(--gray11); 148 | border-radius: 0 0 8px 8px; 149 | box-shadow: none; 150 | display: flex; 151 | flex-direction: row-reverse; 152 | flex-shrink: 0; 153 | height: 44px; 154 | justify-content: space-between; 155 | padding: 0 12px; 156 | position: relative; 157 | user-select: none; 158 | width: 100%; 159 | z-index: 300; 160 | font-size: 12px; 161 | } 162 | -------------------------------------------------------------------------------- /src/assets/scss/highlight.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:meta'; 2 | 3 | html { 4 | @include meta.load-css('highlight.js/styles/github'); 5 | } 6 | html.dark { 7 | @include meta.load-css('highlight.js/styles/github-dark'); 8 | } 9 | 10 | code.hljs { 11 | border-radius: 16px; 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/scss/linear.scss: -------------------------------------------------------------------------------- 1 | .linear { 2 | margin: 0 auto; 3 | display: flex; 4 | justify-content: center; 5 | [command-root] { 6 | kbd { 7 | font-family: var(--font-sans); 8 | background: var(--gray3); 9 | color: var(--gray11); 10 | height: 20px; 11 | width: 24px; 12 | border-radius: 4px; 13 | padding: 0 4px; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | } 18 | } 19 | 20 | [command-linear-badge] { 21 | height: 24px; 22 | padding: 8px; 23 | font-size: 12px; 24 | color: var(--gray11); 25 | background: var(--gray3); 26 | border-radius: 4px; 27 | width: fit-content; 28 | display: flex; 29 | align-items: center; 30 | margin: 16px 16px 0; 31 | } 32 | 33 | [command-linear-shortcuts] { 34 | display: flex; 35 | margin-left: auto; 36 | gap: 8px; 37 | 38 | kbd { 39 | font-family: var(--font-sans); 40 | font-size: 13px; 41 | color: var(--gray11); 42 | } 43 | } 44 | 45 | [command-input] { 46 | font-family: var(--font-sans); 47 | border: none; 48 | width: 100%; 49 | font-size: 18px; 50 | padding: 20px; 51 | outline: none; 52 | background: var(--bg); 53 | color: var(--gray12); 54 | border-bottom: 1px solid var(--gray6); 55 | border-radius: 0; 56 | caret-color: #6e5ed2; 57 | margin: 0; 58 | 59 | &::placeholder { 60 | color: var(--gray9); 61 | } 62 | } 63 | 64 | [command-item] { 65 | content-visibility: auto; 66 | 67 | cursor: pointer; 68 | height: 48px; 69 | font-size: 14px; 70 | display: flex; 71 | align-items: center; 72 | gap: 12px; 73 | padding: 0 16px; 74 | color: var(--gray12); 75 | user-select: none; 76 | will-change: background, color; 77 | transition: all 150ms ease; 78 | transition-property: none; 79 | position: relative; 80 | 81 | &[aria-selected='true'], 82 | &:hover { 83 | background: var(--gray3); 84 | 85 | svg { 86 | color: var(--gray12); 87 | } 88 | 89 | &:after { 90 | content: ''; 91 | position: absolute; 92 | left: 0; 93 | z-index: 123; 94 | width: 3px; 95 | height: 100%; 96 | background: #5f6ad2; 97 | } 98 | } 99 | 100 | &[aria-disabled='true'] { 101 | color: var(--gray8); 102 | cursor: not-allowed; 103 | } 104 | 105 | &:active { 106 | transition-property: background; 107 | background: var(--gray4); 108 | } 109 | 110 | svg { 111 | width: 16px; 112 | height: 16px; 113 | color: var(--gray10); 114 | } 115 | } 116 | 117 | [command-list] { 118 | height: min(300px, var(--command-list-height)); 119 | max-height: 360px; 120 | overflow: auto; 121 | overscroll-behavior: contain; 122 | transition: 100ms ease; 123 | transition-property: height; 124 | } 125 | 126 | * + [command-group] { 127 | margin-top: 8px; 128 | } 129 | 130 | [command-group-heading] { 131 | user-select: none; 132 | font-size: 12px; 133 | color: var(--gray11); 134 | padding: 0 8px; 135 | display: flex; 136 | align-items: center; 137 | } 138 | 139 | [command-empty=''] { 140 | font-size: 14px; 141 | display: flex; 142 | align-items: center; 143 | justify-content: center; 144 | height: 64px; 145 | white-space: pre-wrap; 146 | color: var(--gray11); 147 | } 148 | 149 | [command-separator] { 150 | height: 1px; 151 | width: 100%; 152 | background: var(--gray5); 153 | margin: 4px 0; 154 | } 155 | 156 | [command-dialog-wrapper] { 157 | max-width: 640px; 158 | width: 100%; 159 | background: #ffffff; 160 | border-radius: 12px; 161 | overflow: hidden; 162 | padding: 0; 163 | font-family: var(--font-sans); 164 | box-shadow: var(--command-shadow); 165 | 166 | .dark & { 167 | background: linear-gradient( 168 | 136.61deg, 169 | rgb(39, 40, 43) 13.72%, 170 | rgb(45, 46, 49) 74.3% 171 | ); 172 | } 173 | } 174 | 175 | [command-dialog-footer] ul, 176 | [command-dialog-footer] ul li { 177 | display: flex; 178 | align-items: center; 179 | } 180 | 181 | [command-dialog-footer] ul li { 182 | gap: 4px; 183 | margin-left: 4px; 184 | margin-right: 4px; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/assets/scss/raycast.scss: -------------------------------------------------------------------------------- 1 | .raycast { 2 | [command-dialog-wrapper] { 3 | max-width: 640px; 4 | width: 100%; 5 | background: var(--gray1); 6 | border-radius: 12px; 7 | padding: 8px 0; 8 | font-family: var(--font-sans); 9 | box-shadow: var(--command-shadow); 10 | border: 1px solid var(--gray6); 11 | position: relative; 12 | 13 | .dark & { 14 | background: var(--gray2); 15 | border: 0; 16 | 17 | &:after { 18 | content: ''; 19 | background: linear-gradient( 20 | to right, 21 | var(--gray6) 20%, 22 | var(--gray6) 40%, 23 | var(--gray10) 50%, 24 | var(--gray10) 55%, 25 | var(--gray6) 70%, 26 | var(--gray6) 100% 27 | ); 28 | z-index: -1; 29 | position: absolute; 30 | border-radius: 12px; 31 | top: -1px; 32 | left: -1px; 33 | width: calc(100% + 2px); 34 | height: calc(100% + 2px); 35 | animation: shine 3s ease forwards 0.1s; 36 | background-size: 200% auto; 37 | } 38 | 39 | &:before { 40 | content: ''; 41 | z-index: -1; 42 | position: absolute; 43 | border-radius: 12px; 44 | top: -1px; 45 | left: -1px; 46 | width: calc(100% + 2px); 47 | height: calc(100% + 2px); 48 | box-shadow: 0 0 0 1px transparent; 49 | animation: border 1s linear forwards 0.5s; 50 | } 51 | } 52 | 53 | kbd { 54 | font-family: var(--font-sans); 55 | background: var(--gray3); 56 | color: var(--gray11); 57 | height: 20px; 58 | width: 20px; 59 | border-radius: 4px; 60 | padding: 0 4px; 61 | display: flex; 62 | align-items: center; 63 | justify-content: center; 64 | 65 | &:first-of-type { 66 | margin-left: 8px; 67 | } 68 | } 69 | } 70 | 71 | [command-input] { 72 | font-family: var(--font-sans); 73 | border: none; 74 | width: 100%; 75 | font-size: 15px; 76 | padding: 8px 16px; 77 | outline: none; 78 | background: var(--bg); 79 | color: var(--gray12); 80 | 81 | &::placeholder { 82 | color: var(--gray9); 83 | } 84 | } 85 | 86 | [command-raycast-top-shine] { 87 | .dark & { 88 | background: linear-gradient( 89 | 90deg, 90 | rgba(56, 189, 248, 0), 91 | var(--gray5) 20%, 92 | var(--gray9) 67.19%, 93 | rgba(236, 72, 153, 0) 94 | ); 95 | height: 1px; 96 | position: absolute; 97 | top: -1px; 98 | width: 100%; 99 | z-index: -1; 100 | opacity: 0; 101 | animation: showTopShine 0.1s ease forwards 0.2s; 102 | } 103 | } 104 | 105 | [command-raycast-loader] { 106 | --loader-color: var(--gray9); 107 | border: 0; 108 | width: 100%; 109 | width: 100%; 110 | left: 0; 111 | height: 1px; 112 | background: var(--gray6); 113 | position: relative; 114 | overflow: visible; 115 | display: block; 116 | margin-top: 12px; 117 | margin-bottom: 12px; 118 | 119 | &:after { 120 | content: ''; 121 | width: 50%; 122 | height: 1px; 123 | position: absolute; 124 | background: linear-gradient( 125 | 90deg, 126 | transparent 0%, 127 | var(--loader-color) 50%, 128 | transparent 100% 129 | ); 130 | top: -1px; 131 | opacity: 0; 132 | animation-duration: 1.5s; 133 | animation-delay: 1s; 134 | animation-timing-function: ease; 135 | animation-name: loading; 136 | } 137 | } 138 | 139 | [command-item] { 140 | content-visibility: auto; 141 | 142 | cursor: pointer; 143 | height: 40px; 144 | border-radius: 8px; 145 | font-size: 14px; 146 | display: flex; 147 | align-items: center; 148 | gap: 8px; 149 | padding: 0 8px; 150 | color: var(--gray12); 151 | user-select: none; 152 | will-change: background, color; 153 | transition: all 150ms ease; 154 | transition-property: none; 155 | 156 | &[aria-selected='true'] { 157 | background: var(--gray4); 158 | color: var(--gray12); 159 | } 160 | 161 | &[aria-disabled='true'] { 162 | color: var(--gray8); 163 | cursor: not-allowed; 164 | } 165 | 166 | &:active { 167 | transition-property: background; 168 | background: var(--gray4); 169 | } 170 | 171 | &:first-child { 172 | margin-top: 8px; 173 | } 174 | 175 | & + [command-item] { 176 | margin-top: 4px; 177 | } 178 | 179 | svg { 180 | width: 18px; 181 | height: 18px; 182 | } 183 | } 184 | 185 | [command-raycast-meta] { 186 | margin-left: auto; 187 | color: var(--gray11); 188 | font-size: 13px; 189 | } 190 | 191 | [command-list] { 192 | padding: 0 8px; 193 | height: var(--cmd-list-height); 194 | overflow: auto; 195 | overscroll-behavior: contain; 196 | scroll-padding-block-end: 48px; 197 | transition: 100ms ease; 198 | transition-property: height; 199 | padding-bottom: 40px; 200 | } 201 | 202 | [command-raycast-open-trigger], 203 | [command-raycast-subcommand-trigger] { 204 | color: var(--gray11); 205 | background: var(--grayA5); 206 | padding: 0px 4px 0px 8px; 207 | border-radius: 6px; 208 | font-weight: 500; 209 | font-size: 12px; 210 | height: 28px; 211 | letter-spacing: -0.25px; 212 | } 213 | 214 | [command-raycast-clipboard-icon], 215 | [command-raycast-hammer-icon] { 216 | width: 20px; 217 | height: 20px; 218 | border-radius: 6px; 219 | display: flex; 220 | align-items: center; 221 | justify-content: center; 222 | color: #ffffff; 223 | 224 | svg { 225 | width: 14px; 226 | height: 14px; 227 | } 228 | } 229 | 230 | [command-raycast-clipboard-icon] { 231 | background: linear-gradient(to bottom, #f55354, #eb4646); 232 | } 233 | 234 | [command-raycast-hammer-icon] { 235 | background: linear-gradient(to bottom, #6cb9a3, #2c6459); 236 | } 237 | 238 | [command-raycast-open-trigger] { 239 | display: flex; 240 | align-items: center; 241 | color: var(--gray12); 242 | } 243 | 244 | [command-raycast-subcommand-trigger] { 245 | display: flex; 246 | align-items: center; 247 | gap: 4px; 248 | right: 8px; 249 | bottom: 8px; 250 | 251 | svg { 252 | width: 14px; 253 | height: 14px; 254 | } 255 | 256 | hr { 257 | height: 100%; 258 | background: var(--gray6); 259 | border: 0; 260 | width: 1px; 261 | } 262 | 263 | &[aria-expanded='true'], 264 | &:hover { 265 | background: var(--gray4); 266 | 267 | kbd { 268 | background: var(--gray7); 269 | } 270 | } 271 | } 272 | 273 | [command-separator] { 274 | height: 1px; 275 | width: 100%; 276 | background: var(--gray5); 277 | margin: 4px 0; 278 | } 279 | 280 | * + [command-group] { 281 | margin-top: 8px; 282 | } 283 | 284 | [command-group-heading] { 285 | user-select: none; 286 | font-size: 12px; 287 | color: var(--gray11); 288 | padding: 0 8px; 289 | display: flex; 290 | align-items: center; 291 | } 292 | 293 | [command-dialog-footer] { 294 | display: flex; 295 | flex-direction: row; 296 | height: 40px; 297 | align-items: center; 298 | width: 100%; 299 | position: absolute; 300 | background: var(--gray1); 301 | bottom: 0; 302 | padding: 8px; 303 | border-top: 1px solid var(--gray6); 304 | border-radius: 0 0 12px 12px; 305 | 306 | svg { 307 | width: 20px; 308 | height: 20px; 309 | filter: grayscale(1); 310 | margin-right: auto; 311 | } 312 | 313 | hr { 314 | height: 12px; 315 | width: 1px; 316 | border: 0; 317 | background: var(--gray6); 318 | margin: 0 4px 0px 12px; 319 | } 320 | 321 | @media (prefers-color-scheme: dark) { 322 | background: var(--gray2); 323 | } 324 | } 325 | 326 | [command-popover] { 327 | z-index: var(--layer-portal); 328 | position: fixed; 329 | left: 50%; 330 | top: var(--page-top); 331 | transform: translateX(-50%); 332 | 333 | [command] { 334 | width: 640px; 335 | transform-origin: center center; 336 | animation: dialogIn var(--transition-fast) forwards; 337 | } 338 | 339 | &[data-state='closed'] [command] { 340 | animation: dialogOut var(--transition-fast) forwards; 341 | } 342 | } 343 | 344 | [command-empty] { 345 | font-size: 14px; 346 | display: flex; 347 | align-items: center; 348 | justify-content: center; 349 | height: 64px; 350 | white-space: pre-wrap; 351 | color: var(--gray11); 352 | } 353 | } 354 | 355 | @keyframes loading { 356 | 0% { 357 | opacity: 0; 358 | transform: translateX(0); 359 | } 360 | 361 | 50% { 362 | opacity: 1; 363 | transform: translateX(100%); 364 | } 365 | 366 | 100% { 367 | opacity: 0; 368 | transform: translateX(0); 369 | } 370 | } 371 | 372 | @keyframes shine { 373 | to { 374 | background-position: 200% center; 375 | opacity: 0; 376 | } 377 | } 378 | 379 | @keyframes border { 380 | to { 381 | box-shadow: 0 0 0 1px var(--gray6); 382 | } 383 | } 384 | 385 | @keyframes showTopShine { 386 | to { 387 | opacity: 1; 388 | } 389 | } 390 | 391 | .raycast-submenu { 392 | [command-root] { 393 | display: flex; 394 | flex-direction: column; 395 | width: 320px; 396 | border: 1px solid var(--gray6); 397 | background: var(--gray2); 398 | border-radius: 8px; 399 | } 400 | 401 | [command-list] { 402 | padding: 8px; 403 | overflow: auto; 404 | overscroll-behavior: contain; 405 | transition: 100ms ease; 406 | transition-property: height; 407 | } 408 | 409 | [command-item] { 410 | height: 40px; 411 | 412 | cursor: pointer; 413 | height: 40px; 414 | border-radius: 8px; 415 | font-size: 13px; 416 | display: flex; 417 | align-items: center; 418 | gap: 8px; 419 | padding: 0 8px; 420 | color: var(--gray12); 421 | user-select: none; 422 | will-change: background, color; 423 | transition: all 150ms ease; 424 | transition-property: none; 425 | 426 | &[aria-selected='true'] { 427 | background: var(--gray5); 428 | color: var(--gray12); 429 | 430 | [command-raycast-submenu-shortcuts] kbd { 431 | background: var(--gray7); 432 | } 433 | } 434 | 435 | &[aria-disabled='true'] { 436 | color: var(--gray8); 437 | cursor: not-allowed; 438 | } 439 | 440 | svg { 441 | width: 16px; 442 | height: 16px; 443 | } 444 | 445 | [command-raycast-submenu-shortcuts] { 446 | display: flex; 447 | margin-left: auto; 448 | gap: 2px; 449 | 450 | kbd { 451 | font-family: var(--font-sans); 452 | background: var(--gray5); 453 | color: var(--gray11); 454 | height: 20px; 455 | width: 20px; 456 | border-radius: 4px; 457 | padding: 0 4px; 458 | font-size: 12px; 459 | display: flex; 460 | align-items: center; 461 | justify-content: center; 462 | 463 | &:first-of-type { 464 | margin-left: 8px; 465 | } 466 | } 467 | } 468 | } 469 | 470 | [command-group-heading] { 471 | text-transform: capitalize; 472 | font-size: 12px; 473 | color: var(--gray11); 474 | font-weight: 500; 475 | margin-bottom: 8px; 476 | margin-top: 8px; 477 | margin-left: 4px; 478 | } 479 | 480 | [command-input] { 481 | padding: 12px; 482 | font-family: var(--font-sans); 483 | border: 0; 484 | border-top: 1px solid var(--gray6); 485 | font-size: 13px; 486 | background: transparent; 487 | margin-top: auto; 488 | width: 100%; 489 | outline: 0; 490 | border-radius: 0; 491 | } 492 | 493 | animation-duration: 0.2s; 494 | animation-timing-function: ease; 495 | animation-fill-mode: forwards; 496 | transform-origin: var(--radix-popover-content-transform-origin); 497 | 498 | &[data-state='open'] { 499 | animation-name: slideIn; 500 | } 501 | 502 | &[data-state='closed'] { 503 | animation-name: slideOut; 504 | } 505 | 506 | [command-empty] { 507 | display: flex; 508 | align-items: center; 509 | justify-content: center; 510 | height: 64px; 511 | white-space: pre-wrap; 512 | font-size: 14px; 513 | color: var(--gray11); 514 | } 515 | } 516 | 517 | @keyframes slideIn { 518 | 0% { 519 | opacity: 0; 520 | transform: scale(0.96); 521 | } 522 | 523 | 100% { 524 | opacity: 1; 525 | transform: scale(1); 526 | } 527 | } 528 | 529 | @keyframes slideOut { 530 | 0% { 531 | opacity: 1; 532 | transform: scale(1); 533 | } 534 | 535 | 100% { 536 | opacity: 0; 537 | transform: scale(0.96); 538 | } 539 | } 540 | 541 | @media (max-width: 640px) { 542 | .raycast { 543 | [command-input] { 544 | font-size: 16px; 545 | } 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /src/assets/scss/vercel.scss: -------------------------------------------------------------------------------- 1 | .vercel { 2 | [command-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 12px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--command-shadow); 12 | transition: transform 100ms ease; 13 | 14 | .dark & { 15 | background: rgba(22, 22, 22, 0.7); 16 | } 17 | } 18 | 19 | [command-input] { 20 | font-family: var(--font-sans); 21 | border: none; 22 | width: 100%; 23 | font-size: 17px; 24 | padding: 8px 8px 16px 8px; 25 | outline: none; 26 | background: var(--bg); 27 | color: var(--gray12); 28 | border-bottom: 1px solid var(--gray6); 29 | margin-bottom: 16px; 30 | border-radius: 0; 31 | 32 | &::placeholder { 33 | color: var(--gray9); 34 | } 35 | } 36 | 37 | [command-vercel-badge] { 38 | height: 20px; 39 | background: var(--grayA3); 40 | display: inline-flex; 41 | align-items: center; 42 | padding: 0 8px; 43 | font-size: 12px; 44 | color: var(--grayA11); 45 | border-radius: 4px; 46 | margin: 4px 0 4px 4px; 47 | user-select: none; 48 | text-transform: capitalize; 49 | font-weight: 500; 50 | } 51 | 52 | [command-item] { 53 | content-visibility: auto; 54 | 55 | cursor: pointer; 56 | height: 48px; 57 | border-radius: 8px; 58 | font-size: 14px; 59 | display: flex; 60 | align-items: center; 61 | gap: 8px; 62 | padding: 0 16px; 63 | color: var(--gray11); 64 | user-select: none; 65 | will-change: background, color; 66 | transition: all 150ms ease; 67 | transition-property: none; 68 | 69 | &[aria-selected='true'] { 70 | background: var(--grayA3); 71 | color: var(--gray12); 72 | } 73 | 74 | &[aria-disabled='true'] { 75 | color: var(--gray8); 76 | cursor: not-allowed; 77 | } 78 | 79 | &:active { 80 | transition-property: background; 81 | background: var(--gray4); 82 | } 83 | 84 | & + [command-item] { 85 | margin-top: 4px; 86 | } 87 | 88 | svg { 89 | width: 18px; 90 | height: 18px; 91 | } 92 | } 93 | 94 | [command-list] { 95 | height: min(330px, calc(var(--command-list-height))); 96 | max-height: 400px; 97 | overflow: auto; 98 | overscroll-behavior: contain; 99 | transition: 100ms ease; 100 | transition-property: height; 101 | } 102 | 103 | [command-vercel-shortcuts] { 104 | display: flex; 105 | margin-left: auto; 106 | gap: 8px; 107 | 108 | kbd { 109 | font-family: var(--font-sans); 110 | font-size: 12px; 111 | min-width: 20px; 112 | padding: 4px; 113 | height: 20px; 114 | border-radius: 4px; 115 | color: var(--gray11); 116 | background: var(--gray4); 117 | display: inline-flex; 118 | align-items: center; 119 | justify-content: center; 120 | text-transform: uppercase; 121 | } 122 | } 123 | 124 | [command-separator] { 125 | height: 1px; 126 | width: 100%; 127 | background: var(--gray5); 128 | margin: 4px 0; 129 | } 130 | 131 | *:not([hidden]) + [command-group] { 132 | margin-top: 8px; 133 | } 134 | 135 | [command-group-heading] { 136 | user-select: none; 137 | font-size: 12px; 138 | color: var(--gray11); 139 | padding: 0 8px; 140 | display: flex; 141 | align-items: center; 142 | margin-bottom: 8px; 143 | } 144 | 145 | [command-empty] { 146 | font-size: 14px; 147 | display: flex; 148 | align-items: center; 149 | justify-content: center; 150 | height: 48px; 151 | white-space: pre-wrap; 152 | color: var(--gray11); 153 | } 154 | 155 | [command-dialog-wrapper] { 156 | padding: 8px; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/components/command/Linear.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 86 | 87 | 90 | -------------------------------------------------------------------------------- /src/components/command/Self.vue: -------------------------------------------------------------------------------- 1 | 91 | 92 | 224 | 225 | 228 | -------------------------------------------------------------------------------- /src/components/command/raycast/Home.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 89 | 90 | 120 | -------------------------------------------------------------------------------- /src/components/command/raycast/Item.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /src/components/command/raycast/Raycast.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /src/components/command/vercel/Home.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 113 | -------------------------------------------------------------------------------- /src/components/command/vercel/Item.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /src/components/command/vercel/Projects.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /src/components/command/vercel/Vercel.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 86 | 87 | 109 | -------------------------------------------------------------------------------- /src/components/common/CmdkPlaceholder.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /src/components/icons/FigmaIcon.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/components/icons/LinearAssignToIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/LinearAssignToMeIcon.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/icons/LinearChangeLabelsIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/icons/LinearChangePriorityIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/LinearChangeStatusIcon.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/components/icons/LinearIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/components/icons/LinearRemoveLabelIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/icons/LinearSetDueDateIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/icons/MoonIcon.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/components/icons/RaycasTerminalIcon.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/icons/RaycastClipboardIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/components/icons/RaycastDarkIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/components/icons/RaycastFinderIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/icons/RaycastHammerIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/components/icons/RaycastIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/components/icons/RaycastLightIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/components/icons/RaycastStarIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/icons/RaycastWindowIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/icons/SlackIcon.vue: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /src/components/icons/SunIcon.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /src/components/icons/VercelContactIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /src/components/icons/VercelCopyIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/icons/VercelDocsIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/components/icons/VercelFeedbackIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/icons/VercelIcon.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/components/icons/VercelPlusIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/components/icons/VercelProjectsIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /src/components/icons/VercelTeamsIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /src/components/icons/YouTubeIcon.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /src/composables/useDarkmode.ts: -------------------------------------------------------------------------------- 1 | import { useDark, useToggle } from '@vueuse/core' 2 | 3 | export const isDark = useDark() 4 | export const toggleDarkmode = useToggle(isDark) 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | import 'uno.css' 5 | import '@unocss/reset/tailwind-compat.css' 6 | import './assets/scss/global.scss' 7 | import highlight from '~/plugins/highlight' 8 | 9 | const app = createApp(App) 10 | 11 | app.use(highlight) 12 | app.mount('#app') 13 | -------------------------------------------------------------------------------- /src/plugins/highlight.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vue' 2 | import { computed, defineComponent, h, ref, watch } from 'vue' 3 | import hljs from 'highlight.js/lib/core' 4 | import javascript from 'highlight.js/lib/languages/javascript' 5 | import xml from 'highlight.js/lib/languages/xml' 6 | 7 | hljs.registerLanguage('javascript', javascript) 8 | hljs.registerLanguage('xml', xml) 9 | 10 | function escapeHtml(value: string): string { 11 | return value 12 | .replace(/&/g, '&') 13 | .replace(//g, '>') 15 | .replace(/"/g, '"') 16 | .replace(/'/g, ''') 17 | } 18 | 19 | const component = defineComponent({ 20 | props: { 21 | code: { 22 | type: String, 23 | required: true, 24 | }, 25 | language: { 26 | type: String, 27 | default: '', 28 | }, 29 | autodetect: { 30 | type: Boolean, 31 | default: true, 32 | }, 33 | ignoreIllegals: { 34 | type: Boolean, 35 | default: true, 36 | }, 37 | }, 38 | setup(props) { 39 | const language = ref(props.language) 40 | watch( 41 | () => props.language, 42 | (newLanguage) => { 43 | language.value = newLanguage 44 | }, 45 | ) 46 | 47 | const autodetect = computed(() => props.autodetect || !language.value) 48 | const cannotDetectLanguage = computed( 49 | () => !autodetect.value && !hljs.getLanguage(language.value), 50 | ) 51 | 52 | const className = computed((): string => { 53 | if (cannotDetectLanguage.value) 54 | return '' 55 | else 56 | return `hljs ${language.value}` 57 | }) 58 | 59 | const highlightedCode = computed((): string => { 60 | // No idea what language to use, return raw code 61 | if (cannotDetectLanguage.value) { 62 | console.warn( 63 | `The language "${language.value}" you specified could not be found.`, 64 | ) 65 | return escapeHtml(props.code) 66 | } 67 | 68 | if (autodetect.value) { 69 | const result = hljs.highlightAuto(props.code) 70 | language.value = result.language ?? '' 71 | return result.value 72 | } 73 | else { 74 | const result = hljs.highlight(props.code, { 75 | language: language.value, 76 | ignoreIllegals: props.ignoreIllegals, 77 | }) 78 | return result.value 79 | } 80 | }) 81 | 82 | return { 83 | className, 84 | highlightedCode, 85 | } 86 | }, 87 | render() { 88 | return h('pre', {}, [ 89 | h('code', { 90 | class: this.className, 91 | innerHTML: this.highlightedCode, 92 | tabindex: '0', 93 | }), 94 | ]) 95 | }, 96 | }) 97 | 98 | const plugin: Plugin & { component: typeof component } = { 99 | install(app) { 100 | app.component('Highlight', component) 101 | }, 102 | component, 103 | } 104 | 105 | export default plugin 106 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "noEmit": false, 6 | "emitDeclarationOnly": true, 7 | "outDir": "lib", 8 | "declarationDir": "./temp", 9 | "moduleResolution": "Node", 10 | "baseUrl": "./", 11 | }, 12 | "vueCompilerOptions": { 13 | "skipTemplateCodegen": true, 14 | }, 15 | "include": [ 16 | "packages/**/*.ts", 17 | "packages/**/*.d.ts", 18 | "packages/**/*.tsx", 19 | "packages/**/*.vue" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "moduleResolution": "Node", 6 | "paths": { 7 | "@/*": ["packages/*"], 8 | "~/*": ["./src/*"] 9 | }, 10 | "sourceMap": true, 11 | }, 12 | "references": [{ "path": "./tsconfig.node.json" }], 13 | "include": [ 14 | "src/**/*.ts", 15 | "src/**/*.d.ts", 16 | "src/**/*.tsx", 17 | "src/**/*.vue", 18 | "packages/**/*.ts", 19 | "packages/**/*.d.ts", 20 | "packages/**/*.tsx", 21 | "packages/**/*.vue" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetAttributify, presetUno } from 'unocss' 2 | 3 | export default defineConfig({ 4 | presets: [presetUno(), presetAttributify()], 5 | shortcuts: [ 6 | { 7 | 'flex-center': 'flex justify-center items-center', 8 | 'flex-col-center': 'flex flex-col justify-center items-center', 9 | 'flex-between': 'flex justify-between items-center', 10 | 'text-neon': 11 | 'text-transparent bg-clip-text bg-gradient-to-r from-emerald-500 to-emerald-700 dark:to-emerald-300 ', 12 | }, 13 | ], 14 | }) 15 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { URL, fileURLToPath } from 'node:url' 2 | import { resolve } from 'node:path' 3 | import type { UserConfig } from 'vite' 4 | import { defineConfig } from 'vite' 5 | import vue from '@vitejs/plugin-vue' 6 | import UnoCSS from 'unocss/vite' 7 | import Icons from 'unplugin-icons/vite' 8 | import IconsResolver from 'unplugin-icons/resolver' 9 | import Components from 'unplugin-vue-components/vite' 10 | 11 | // https://vitejs.dev/config/ 12 | export default defineConfig(({ command, mode }) => { 13 | const userConfig: UserConfig = {} 14 | 15 | console.log(command) 16 | console.log(mode) 17 | 18 | if (mode === 'lib') { 19 | userConfig.build = { 20 | lib: { 21 | entry: resolve(__dirname, 'packages/index.ts'), 22 | name: 'VueCommandPalette', 23 | fileName: 'vue-command-palette', 24 | }, 25 | outDir: 'lib', 26 | emptyOutDir: true, 27 | sourcemap: true, 28 | rollupOptions: { 29 | external: ['vue', 'fuse.js'], 30 | output: [ 31 | { 32 | format: 'cjs', 33 | entryFileNames: 'vue-command-palette.cjs' 34 | }, 35 | { 36 | format: 'es', 37 | entryFileNames: 'vue-command-palette.js', 38 | preserveModules: false 39 | } 40 | ], 41 | }, 42 | } 43 | } 44 | 45 | return { 46 | resolve: { 47 | alias: { 48 | '@': fileURLToPath(new URL('./packages', import.meta.url)), 49 | '~': fileURLToPath(new URL('./src', import.meta.url)), 50 | }, 51 | }, 52 | plugins: [ 53 | vue(), 54 | UnoCSS(), 55 | Components({ 56 | resolvers: [ 57 | IconsResolver({ 58 | prefix: '', 59 | }), 60 | ], 61 | }), 62 | Icons(), 63 | ], 64 | ...userConfig, 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /vue-command-palette.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoluoboding/vue-command-palette/9daf5aa2c26f33fba65d2c1de05720fe769126dd/vue-command-palette.gif --------------------------------------------------------------------------------