贡献指南
了解如何为 shadcn/vue 项目做出贡献。
引言
感谢您有兴趣为 shadcn-vue.com 做出贡献。我们很高兴您的加入。
在提交第一个拉取请求之前,请花点时间阅读本文档。我们还强烈建议您查看开放的问题和拉取请求,以了解是否有人正在处理类似的内容。
如果您需要任何帮助,请随时通过 Discord 联系核心团队。
本指南提供详细信息,以帮助新的贡献者。
关于此仓库
此仓库是一个单体仓库(monorepo)。
- 我们使用 pnpm 和
workspaces进行开发。
项目结构
GitHub 仓库包含多个文件夹。以下是快速概览。
packages - 包含支持
nuxt作为模块的源代码以及用于添加新组件的cli。apps/www - 存放网站源代码和每个
shadcn/vue组件的主要文件夹。此文件夹包含重要的子文件夹,并且是一个具有自己的package.json的子项目。.vitepress - 包含
vitepress和shadcn/vue网站的配置和源代码。src - 存放每个
shadcn/vue组件或演示及其在网站上的文档的主要源代码。__registry__ - 存放由
scripts/build-registry.ts生成的注册表文件,以便为cli提供组件。此文件夹的内容是自动生成的,不应手动编辑。scripts - 包含各种辅助脚本,例如
build-registry.ts,它会自动生成__registry__文件夹。content - 此文件夹存放
/docs路由的所有文档。每个组件都有一个.md文件,记录组件的安装和使用方法。examples - 存放所有不属于
/docs的示例,例如主页。registry - 主要文件夹存放每个组件不同风格的源代码。这可能是您将要更改的主要文件夹。
我们在 shadcn/vue 中为每个组件支持两种不同的风格:
- 默认风格
- 纽约风格
添加到仓库的每个组件都必须支持这两个版本,包括主要源代码和相关的演示。
添加或修改组件时,请确保:
- 您为每种风格都进行了更改。
- 您更新了文档。
- 您运行
pnpm build:registry以更新注册表。
开发
首先克隆仓库:
git clone git@github.com:unovue/shadcn-vue.git安装依赖
pnpm install运行工作区
您可以使用 pnpm --filter=[WORKSPACE] 命令来启动工作区的开发过程,或者使用我们设置的一些快捷命令。
示例
- 要运行
shadcn-vue.com网站:
pnpm dev- 要运行
shadcn-vuecli 包:
pnpm dev:cli文档
此项目的文档位于 www 工作区。您可以通过运行以下命令在本地运行文档:
pnpm dev文档使用 md 编写。您可以在 apps/www/src/content 目录中找到文档文件。
CLI
shadcn-vue 包是一个用于向项目添加组件的 CLI。您可以在此处找到 CLI 的文档。
对 CLI 的任何更改都应在 packages/cli 目录中进行。如果可以的话,最好为您的更改添加测试。
测试
测试使用 Vitest 编写。您可以从仓库的根目录运行所有测试。
pnpm test提交拉取请求时,请确保测试通过。如果您添加了新功能,请包含测试。
提交约定
在创建拉取请求之前,请检查您的提交是否符合此仓库中使用的提交约定。
创建提交时,我们恳请您在提交消息中遵循 category(scope or module): message 的约定,并使用以下类别之一:
feat / feature:所有引入全新代码或新功能的更改fix:修复错误的更改(理想情况下,您还应引用相关的问题,如果存在)refactor:任何与代码相关的更改,既不是修复也不是功能docs:更改现有或创建新文档(例如 README、库使用文档或 CLI 使用文档)build:所有与软件构建相关的更改、依赖项的更改或新依赖项的添加test:所有与测试相关的更改(添加新测试或更改现有测试)ci:所有与持续集成配置相关的更改(例如 GitHub Actions、CI 系统)chore:所有不适合上述任何类别的仓库更改例如:
feat(components): 向头像组件添加新属性
如果您对详细规范感兴趣,可以访问 Conventional Commits。
SFC - 单文件组件
在 shadcn/ui(shadcn 的 React 版本)中,多个组件集成在一个文件中,而 Vue 仅支持每个文件一个组件,因此称为单文件组件(SFC)。在这种情况下,您需要为每个组件部分创建单独的文件,然后在 index.ts 文件中导出它们。
请参阅 Accordion 源代码作为示例。
包装 Reka UI 组件
Reka UI 托管了许多用于构建可重用组件的低级 UI 组件。在许多情况下,您需要包装 Reka UI 组件。
属性与事件
所有 Reka UI 组件都公开了它们的属性和事件类型。我们需要将来自外部的任何属性/事件转发到 Reka UI 组件。
为此,我们有一个名为 useForwardPropsEmits 的辅助函数,它结合了必须绑定到子 radix 组件的属性和事件。
更清楚地说,函数 useForwardPropsEmits 接收属性和一个可选的 emit 函数,并返回一个计算对象,该对象结合了解析后的属性和事件作为属性。
以下是来自 Accordion 根组件的示例。
<script setup lang="ts">
import type { AccordionRootEmits, AccordionRootProps } from 'reka-ui'
import {
AccordionRoot,
useForwardPropsEmits
} from 'reka-ui'
const props = defineProps<AccordionRootProps>()
const emits = defineEmits<AccordionRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<AccordionRoot v-bind="forwarded">
<slot />
</AccordionRoot>
</template>如您所见,AccordionRootEmits 和 AccordionRootProps 类型是从 radix 导入的,使用 useForwardPropsEmits 组合,然后使用 v-bind 语法绑定。
CSS 类
在某些情况下,我们希望在我们的 shadcn/vue 组件中接受 class 作为属性,然后通过 cn 实用函数将其与我们 Reka UI 组件上的默认 tailwind 类组合。
在这些情况下,我们不能使用 v-bind,因为这会导致双重类绑定。
请查看 DrawerDescription.vue。
<script lang="ts" setup>
import type { DrawerDescriptionProps } from 'vaul-vue'
import type { HTMLAttributes } from 'vue'
import { DrawerDescription } from 'vaul-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DrawerDescriptionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
</script>
<template>
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</DrawerDescription>
</template>如您所见,我们创建了一个名为 delegatedProps 的计算属性,以从属性中移除 class,然后仅将返回值绑定到我们的 radix 组件(在此例中为 DrawerDescription)。
至于我们的类,我们首先将其声明为 HTMLAttributes['class'] 类型,并使用 cn 来合并来自 class 属性的 tailwind 类和我们自己的类。
仅当需要 cn 实用程序时才需要应用此模式。对于不需要将默认 Tailwind 类与用户提供的类合并的情况,不需要此模式。SelectValue.vue 组件就是一个很好的例子。
<script setup lang="ts">
import type { SelectValueProps } from 'reka-ui'
import { SelectValue } from 'reka-ui'
const props = defineProps<SelectValueProps>()
</script>
<template>
<SelectValue v-bind="props">
<slot />
</SelectValue>
</template>布尔属性
当您为组件构建包装器时,在某些情况下,您希望忽略 Vue 的属性布尔转换。 您可以将所有布尔字段的默认值设置为 undefined,或者使用 useForwardProps 组合式函数。
请查看 AccordionItem.vue
<script setup lang="ts">
import type { AccordionItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { AccordionItem, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<AccordionItem
v-bind="forwardedProps"
:class="cn('border-b', props.class)"
>
<slot />
</AccordionItem>
</template>由于 AccordionItemProps 类型至少有一个布尔属性,我们需要在整个属性对象上使用 useForwardProps。
请注意,useForwardPropsEmits 在底层使用了 useForwardProps。
组件作为根
每当您的根组件是来自 vue 的 Component 基元时,使用 Primitive 会更容易。
让我们看看 Button.vue
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { ButtonVariants } from '.'
import { Primitive } from 'reka-ui'
import { cn } from '@/lib/utils'
import { buttonVariants } from '.'
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>
</template>您需要在您的属性中扩展 PrimitiveProps 以支持 Primitive 组件。在大多数情况下,您还需要为 as 属性设置默认值。
与 shadcn/ui 同步更新
shadcn/vue 是 shadcn/ui 的一个非官方、社区主导的 Vue 移植版本,随着时间的推移,它们可能会不同步。
截至目前,我们与 shadcn/ui 的此提交保持同步。
点击以下链接检查是否有我们应该同步的较新提交。
- 没有更改 - 如果您看到“There isn’t anything to compare”,则无需执行任何操作,因为我们已与最新版本同步。
- 如果有更改,您应该查看这些更改,并尝试将其应用到
shadcn/vue代码库中,并创建一个 PR,记得同时更新此文件中的latestSyncCommitTag。
调试
以下是一些工具和技巧,可以帮助您在为 shadcn/vue 做出贡献或开发自己的项目时更有效地进行调试。
安装 Vue Dev Tools
要轻松检查组件属性、属性、事件等,您可以利用 Vue DevTools 浏览器扩展。此扩展提供了一个用户友好的界面,用于调试 Vue 组件,并可以改善您的开发体验。
启用自定义格式化程序
Vue 将以某种方式包装存储在 ref 中的值,当记录时,会导致嵌套对象,并需要手动检查以访问存储在 ref 中的值。
您可以在浏览器中启用自定义格式化程序来自动化此过程。