撸了一款 Vue 生态缺失的 CMD+K 类库 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xiaoluoboding
V2EX    Vue.js

撸了一款 Vue 生态缺失的 CMD+K 类库

  •  1
     
  •   xiaoluoboding
    xiaoluoboding 2022-09-29 09:38:32 +08:00 2921 次点击
    这是一个创建于 1136 天前的主题,其中的信息可能已经有所发展或是发生改变。

    撸了一款 Vue 生态缺失的 CMD+K 类库

    新轮子又来了,Vue Command Palette 是一个为 Vue 而生的快速、无样式、可组合的 Command Palette ( CMDK )组件库。

    灵感来源

    这个组件的诞生的灵感来自上个月观察到一个比较火的 React 类库项目 cmdk

    cmdk 是一个为 React 而生的快速、无样式、可组合的 CMDK 组件,由 Linear 工程师 Paco Coursey 和他的设计师小伙伴合力开发的,一周内获得 3k Star 。

    其特点是无样式的,只提供基础的功能框架,可组合的组件 API ,便于扩展,这样一来,你可以基于它二次开发,编写成任何你想要的样子。

    官网也做的比较用心,编写了四种样式作为例子,有 RaycastLinearVercelFramer

    发现 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 中的命名空间组件

    这次的组件设计有别于以往的组件开发方式,使用了 Vue 中的命名空间组件 的编写方式,在了解命名空间组件之前,我们先了解一下复合组件

    复合组件

    cmdk 类库提到了它的组件设计借鉴了《 React Hooks: Compound Components 》这篇文章中提到的 React 中的 复合组件 设计模式。

    那什么是 复合组件 呢,它是一种组件的设计模式,一般适用于有两个或者多个组件一起工作,通常一个组件是父组件、而其他的是子组件。

    我们在使用的大部分 UI 类库都会采用复合组件的设计模式去编写复杂组件,比如我们常用的 SelectMenuTable 等等组件到实现方式都是复合组件。

    令我好奇的是 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

    举个例子:

    例如 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

    vue-command-palette 组件就是以 命名空间组件 方式编写的,基本上实现了 cmdk 中大部分的功。

    预览

    Preview

    安装

    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 } 

    组件库虽然是无样式的,但是这样一来你就可以随心所欲的定制主题,考虑到对伸手党不太友好,这里提供了几种主题仅供参考:

    嵌套子面板

    有时候有一种需求是在命令面板中需要下钻访问子菜单中的项目,这时候需要手动控制,建议使用动态组件的方式实现

    详见 Vercel 的例子

    事件处理

    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 组件可以用了呢。

    本年度其他独立项目

    9 条回复    2022-09-30 17:53:54 +08:00
    doommm
        1
    doommm  
       2022-09-29 11:00:16 +08:00
    已 star
    lizhenda
        2
    lizhenda  
       2022-09-29 11:26:22 +08:00
    windows 不支持吗?
    magichacker
        3
    magichacker  
       2022-09-29 11:33:18 +08:00
    是真能卷啊
    dufu1991
        4
    dufu1991  
       2022-09-29 11:44:50 +08:00
    应该贴个链接,放图片我还要手动输入项目地址。
    xiaoluoboding
        5
    xiaoluoboding  
    OP
       2022-09-29 13:18:08 +08:00
    xiaoluoboding
        6
    xiaoluoboding  
    OP
       2022-09-29 13:18:37 +08:00
    @magichacker #3 全当你在夸我,就是喜欢折腾,
    xiaojun1994
        7
    xiaojun1994  
       2022-09-29 13:54:19 +08:00
    向大佬学习
    wensonsmith
        8
    wensonsmith  
       2022-09-29 17:59:26 +08:00
    666 ,star 了
    EndureBlaze
        9
    EndureBlaze  
       2022-09-30 17:53:54 +08:00
    我在演示页面里面按下 K 之后浏览器的搜索框也会同步获取到焦点,也许是一个 bug ?我的浏览器是 Firefox 106 macOS
    关于     帮助文档     自助推广系统     博客 &nbs;   API     FAQ     Solana     834 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 21:26 PVG 05:26 LAX 13:26 JFK 16:26
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86