新轮子又来了,Vue Command Palette
是一个为 Vue 而生的快速、无样式、可组合的 Command Palette ( CMDK )组件库。
这个组件的诞生的灵感来自上个月观察到一个比较火的 React
类库项目 cmdk
cmdk
是一个为 React
而生的快速、无样式、可组合的 CMDK
组件,由 Linear
工程师 Paco Coursey
和他的设计师小伙伴合力开发的,一周内获得 3k Star 。
其特点是无样式的,只提供基础的功能框架,可组合的组件 API ,便于扩展,这样一来,你可以基于它二次开发,编写成任何你想要的样子。
官网也做的比较用心,编写了四种样式作为例子,有 Raycast
、Linear
、Vercel
、Framer
发现 Vue 生态内缺少一款好用的 CMDK 类库,于是决定自己造一个( chaoxi )。
如果你还不知道什么是 CMDK ,这里简单介绍下。
CMDK
是一种用户体验CMDK 是 CMD + K
的缩写,CMD 代表 Mac 系统中的键位 ⌘ ,对应 Command 。CMD + K 是组合键,需要同时按下或者先后按下。
其实 CMDK 这种用户体验我们或多或少都接触过,Mac 自带的 聚焦搜索
就是这样的一个工具 ⌘ + Space
即可唤起它进行搜索,或者作为开发者查阅一些文档的时候,都会带有搜索的功能,有时候会发现都是嵌入的 algolia search
, 再或者在使用 VSCode 的时候打开的命令面板(⇧ + ⌘ + P )
在去年发现 Raycast
这个 App 之后,生产力明显上升,Raycast 可以自定义很多快捷方式,可以结合一些工具打造顺滑的工作流,比如 Raycast 结合 GitHub 去 Create Issue ,结合 Linear 去 Create Issue 等等。
所以我认为一个好的工具类、文档类的站点,应当内置一个好用的 CMDK 功能,可以大幅提升效率,以下工具都是一些实现比较好的代表。
Vercel
GitHub
Raycast
Linear
Framer
Algolia
你不妨也去试试,没准儿在哪个你正在访问的网站悄悄的支持着 CMDK ,你敲一下 ⌘ + K 就能唤起呢。
这次的组件设计有别于以往的组件开发方式,使用了 Vue 中的命名空间组件
的编写方式,在了解命名空间组件之前,我们先了解一下复合组件
。
cmdk
类库提到了它的组件设计借鉴了《 React Hooks: Compound Components 》这篇文章中提到的 React 中的 复合组件
设计模式。
那什么是 复合组件
呢,它是一种组件的设计模式,一般适用于有两个或者多个组件一起工作,通常一个组件是父组件、而其他的是子组件。
我们在使用的大部分 UI 类库都会采用复合组件的设计模式去编写复杂组件,比如我们常用的 Select
、Menu
、Table
等等组件到实现方式都是复合组件。
令我好奇的是 cmdk
这个 React 类库中采用的是 <父组件.子组件 />
的引入方式,例如 cmdk
官网的例子:
import { Command } from 'cmdk';
<Command.Dialog open={open} onOpenChange={setOpen}>
<Command.Input />
<Command.List>
{loading && <Command.Loading>Hang on…</Command.Loading>}
<Command.Empty>No results found.</Command.Empty>
<Command.Group heading="Fruits">
<Command.Item>Apple</Command.Item>
<Command.Item>Orange</Command.Item>
<Command.Separator />
<Command.Item>Pear</Command.Item>
<Command.Item>Blueberry</Command.Item>
</Command.Group>
<Command.Item>Fish</Command.Item>
</Command.List>
</Command.Dialog>
上面代码中出现的 <Command.Dialog>
、<Command.List>
等组件引入方式,是在 Vue 中很少采用的方式,我在想为什么不可以呢,于是想去试试。
举个例子:
例如 HTML 中的 <select>
和 <option>
标签:
<select>
<option value="value1">key1</option>
<option value="value2">key2</option>
<option value="value3">key3</option>
</select>
通常在 Vue 中实现,我们需要编写两个组件,假设是 MySelect
作为父级组件,MyOption
作为子组件
<template>
<fieldset>
<legend>currentValue: {{selected}}</legend>
<MySelect v-model="selected">
<MyOption :value="1">One</MyOption>
<MyOption :value="2">Two</MyOption>
<MyOption :value="3">Three</MyOption>
</MySelect>
</fieldset>
</template>
<script setup>
import { ref } from 'vue'
import MySelect from './MySelect.vue'
import MyOption from './MyOption.vue'
const selected = ref('1')
</script>
其实 Vue 官方文档也有说明,把带 .
的组件叫做命名空间组件( Namespaced Components )。
命名空间组件:可以使用带
.
的组件标签,例如<Foo.Bar>
来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用
假设将上面例子中的组件改为命名空间组件,会是这样写:
<template>
<fieldset>
<legend>currentValue: {{selected}}</legend>
<NSelect v-model="selected">
<NSelect.Option :value="1">One</NSelect.Option>
<NSelect.Option :value="2">Two</NSelect.Option>
<NSelect.Option :value="3">Three</NSelect.Option>
</NSelect>
</fieldset>
</template>
<script setup>
import { ref } from 'vue'
import { NSelect } from './packages.js'
const selected = ref('1')
</script>
在验证了可行后,于是决定以 命名空间组件
的方式编写 Vue Command Palette
命名空间组件特点:
vue-command-palette 组件就是以 命名空间组件
方式编写的,基本上实现了 cmdk
中大部分的功能。
yarn add vue-command-palette
# or
pnpm add vue-command-palette
注意,此组件提供的是具有 CMDK 功能的骨架,没有任何样式,需要单独引入样式文件。
!-- <template> -->
<Command.Dialog :visible="visible" theme="custom">
<template #header>
<Command.Input placeholder="Type a command or search..." />
</template>
<template #body>
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Group heading="Letters">
<Command.Item>a</Command.Item>
<Command.Item>b</Command.Item>
<Command.Separator />
<Command.Item>c</Command.Item>
</Command.Group>
<Command.Item>Apple</Command.Item>
</Command.List>
</template>
</Command.Dialog>
引入一个 Command
根组件,即可选择性的去组合使用其他子组件
// <script lang="ts" setup>
import { ref } from 'vue'
import { Command } from 'vue-command-palette'
const visible = ref(false)
和 cmdk
同样,组件库不提供任何样式,每个组件都以特殊声明的以 command-
开头的 data-attribute
命名,你可以使用它作为选择器来定制样式。
比如:
div[command-root=""] {
// your style
}
vue-command-palette
可以传递一个名为 theme
的 Props ,这样你可以在主题的 class 选择器
中编写你的样式。
比如:提供的 theme 为 my-theme
// my-theme.css
.my-theme {
// your theme
}
组件库虽然是无样式的,但是这样一来你就可以随心所欲的定制主题,考虑到对伸手党不太友好,这里提供了几种主题仅供参考:
有时候有一种需求是在命令面板中需要下钻访问子菜单中的项目,这时候需要手动控制,建议使用动态组件的方式实现
在 Command.Item
的实现中提供了 @select
事件绑定,便于触发选中事件
快捷键的绑定实现,这里参考了另一个 cmdk 类库 kbar 的实现方式,主动声明快捷键 shortcut
和响应事件 perform
的关系
const preferenceItems = [
{
icon: SunIcon,
label: 'Toggle Dark Mode',
shortcut: ['G', 'T'],
perform: () => toggleDarkmode()
}
]
感谢上面的组件库提供的灵感,实际上我可能是一个“组件库翻译者”,将 React 生态的 cmdk
搬运成了 Vue 生态的 vue-command-palette
,或许以后你的 Vue 项目有好用的 CMDK 组件可以用了呢。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.