news 2026/9/6 15:57:13

Ghost Shade 设计系统:新增组件的验收清单与实现规范(命名、cva 变体、Story 约定与 Token 纪律)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ghost Shade 设计系统:新增组件的验收清单与实现规范(命名、cva 变体、Story 约定与 Token 纪律)

Ghost Shade 设计系统:新增组件的验收清单与实现规范(命名、cva 变体、Story 约定与 Token 纪律)

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

Ghost 的 Shade 设计系统(apps/shade)为仓库中的各个微前端应用(apps/adminapps/activitypub等)提供统一的 UI 组件层。仓库内置了一份名为shade-new-component的 Agent 技能文档 SKILL.md,它规定了在apps/shade/src/components下新增或编辑组件/模式文件时必须通过的验收清单:命名规则、Storybook 标题前缀、基于class-variance-authority的组件标准形态、四种必需交互状态以及语义 Token 纪律。读完本文,你将掌握在 Shade 中交付一个合格组件的完整流程,并能对照仓库源码理解每条规则背后的实际实现。

技能文档的定位与触发条件

这份清单文档带有标准的技能 frontmatter,明确了它的适用范围与自动触发条件:

name: Shade new component description: Acceptance checklist for adding or editing a Shade component or pattern file — naming, sibling story, className forwarding, cva variants, required states, recipe usage. autoTrigger: - fileEdit: "apps/shade/src/components/**/*.{ts,tsx}"

即:只要发生对apps/shade/src/components下任意.ts/.tsx文件的编辑,就该清单自动生效。它的定位是"验收"而非"教学"——在把组件标记为完成之前,下列各项必须全部通过。Shade 的 Agent 指引文档 AGENTS.md 也要求:先使用shade-component-decision技能判断代码应落在哪一层,再按shade-new-component走验收清单,两者配合使用。

文件与命名约定

文档规定的命名规则与仓库源码一一对应:

对象约定仓库实例
文件名kebab-casedropdown-menu.tsx),与 ShadCN CLI 输出保持一致,不要因大小写而改名apps/shade/src/components/ui/dropdown-menu.tsxbutton.tsx
导出标识符PascalCaseDropdownMenuexport { Button, buttonVariants }(button.tsx)
钩子、函数、变量camelCasecndebounce(ds-utils.ts)
同目录 Story必需<name>.stories.tsx兄弟文件button.stories.tsxbutton.tsx同目录
包内导入统一走@/别名(@/lib/utils@/components/ui/input-surfaceimport { cn } from '@/lib/utils'

值得注意的是@/lib/utils本身是一个再导出层:utils.ts 只有一行export * from './ds-utils',真正的工具函数实现在 ds-utils.ts。Shade 的人类文档 contributing.mdx 中的命名表格与本清单完全一致,说明这是同一套约定的两个表达面(清单面向机器触发,文档面向人类阅读)。

Storybook 标题前缀与 Story 文件要求

Shade 按"层"组织 Storybook 的顶层目录,组件的title必须匹配其所处的层:

标题前缀
Primitive(布局原语)Primitives / <Name>
Component(通用控件)Components / <Name>
Recipe(纯类名配方)Recipes / <Name>
Pattern(产品组合)Patterns / <Name>
Token gallery(Token 展示)Tokens / <Topic>

这五个前缀正好对应apps/shade/src/components/下的目录划分(primitives/ui/patterns/page-templates/)以及根部的components.tsprimitives.tspatterns.tspage-templates.ts各层入口桶文件(见 package.json 的exports字段,各层均有独立子路径导出)。

每个 story 文件还必须具备以下要素:

  • tags: ['autodocs']—— 启用自动文档页;
  • parameters.docs.description.component—— 一行的组件级摘要;
  • 每个 story 的parameters.docs.description.story—— 一句话说明该变体在何时使用;
  • 每个重要变体/状态各写一个 story:宁要多个小而聚焦的 story,不要一个长文案的 story。

button.stories.tsx 是这些要求的标准范本:

const meta = { title: 'Components / Button', component: Button, tags: ['autodocs'], parameters: { docs: { description: { component: 'Reusable button for interactive actions across the UI. ...', }, }, }, } satisfies Meta<typeof Button>;

其中title: 'Components / Button'使用了 Component 层前缀;随后DestructiveOutlineSecondaryGhostLinkVariant等每个变体各自一个 story,并逐个附带description.story(如 destructive 的说明是"用于危险或不可逆操作(删除、移除、重置)")。

组件标准形态:cva 变体 + forwardRef + className 透传

文档给出了组件的标准骨架,这是一个"可复制即可运行"的模板:

import * as React from 'react'; import {cva, type VariantProps} from 'class-variance-authority'; import {cn} from '@/lib/utils'; const thingVariants = cva('base-classes-here', { variants: { variant: {default: '...', destructive: '...'}, size: {default: '...', sm: '...'} }, defaultVariants: {variant: 'default', size: 'default'} }); export interface ThingProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof thingVariants> {} const Thing = React.forwardRef<HTMLDivElement, ThingProps>( ({className, variant, size, ...props}, ref) => ( <div ref={ref} className={cn(thingVariants({variant, size, className}))} {...props} /> ) ); Thing.displayName = 'Thing'; export {Thing, thingVariants};

对照真实实现 button.tsx 可以看到该骨架的完整落地:buttonVariantscva()声明了variant(default/destructive/outline/secondary/ghost/link/dropdown)与size(default/sm/lg/icon)两组变体并设置defaultVariantsButton通过React.forwardRef定义(L43),classNamecn(buttonVariants({...}))合并后落到 DOM(L60),末尾Button.displayName = 'Button'并导出组件与变体函数两个符号(L66-L68)。

文档在此骨架上提出三条硬性要求:

  1. className必须透传并用cn()合并——绝不覆盖、绝不丢弃,适用于每个渲染 DOM 的组件。cn()的实际实现是twMerge(clsx(inputs))(ds-utils.ts),即先做布尔/数组类归一化,再用 Tailwind 的冲突合并规则解决同类冲突(例如消费方传入的h-8能正确覆盖组件内部的h-9),这正是"透传不覆盖"的底层保障。
  2. 只允许视觉/交互类 propsvariantsizeloading),禁止工作流类 props(如isMembersPagelayoutMode)——一旦出现这类 props,说明你需要的是一个 Pattern 包装层而不是通用 Component。
  3. 多区域组件用复合子组件.Title.Actions.Body)暴露结构,而不是堆一袋 props。

forwardRef、cva 与 Recipe 的适用边界

文档对三个易误用的机制给出了明确的"用 / 不用"判据:

  • forwardRef:组件渲染单个 DOM 元素且消费方可能需要 ref 时使用(大多数 UI 控件)。以下场景跳过:纯 provider/context 包装器、本身不渲染 DOM 的 Radix root 再导出、ref 语义已由子组件处理的组件。
  • cva():组件有变体或状态化类名分支(variantsizetone)时使用;单一样式且一行cn(...)更清晰的简单组件可不用。
  • Recipe(<name>.ts,无 JSX):不写forwardRef、不写cva()、不引入 React,只返回类名字符串。

仓库中的 input-surface.ts 是 Recipe 的范例:它是纯.ts文件,导出inputSurfaceClasses原子类名(base、focusSelf、focusWithin、invalidSelf 等)和inputSurface(mode)函数,注释明确说明它"拥有"边框、背景、圆角、过渡、焦点环与失效态,消费方只负责尺寸、内边距与排版。

四种必需状态:default / hover / focus-visible / disabled

文档要求每个交互组件在以下四种状态下都必须正常工作,且每种状态都要在 story 中可见:

  • default
  • hover
  • focus-visible(使用focus-visible:前缀,绝不用focus:
  • disabled

active、loading、error、empty 属于可选状态,仅在适用时提供。button.tsx的基础类串即体现了这条纪律:focus-visible:ring-1 focus-visible:ring-focus-ring focus-visible:outline-hidden负责焦点环,hover:bg-primary/90负责悬停,disabled:pointer-events-none disabled:opacity-50负责禁用态。

对表单控件,文档要求通过inputSurfacerecipe驱动外观,而不是各组件自己拼 border/focus ring。这正是 input-surface.ts 的设计:'self'模式直接作用于可聚焦元素(<input><textarea>),'within'模式作用于包含可聚焦子元素的包装器(通过:has()从任意聚焦后代派生焦点/失效样式);确有特殊场景时再手动组合inputSurfaceClasses原子。相关的深度说明在姊妹技能 shade-input-surface-recipe 中。

Token 纪律:无 hex、无裸灰阶、无dark:颜色变体

文档的 Token 章节只有两条规则,但都是硬性约束:

  • 禁止 hex 值,禁止bg-gray-200这类裸调色板工具类用于 UI 外观;
  • 禁止dark:颜色变体——深色模式由语义 Token 统一处理。

这与 Shade 的 CSS 架构直接对应:根部的 tokens.css 是"仅 Token"的入口,它只做两件事——@import './tailwind.theme.css'(Tailwind 主题映射与原始 Token)和@import './theme-variables.css'(语义 Token 及其深色模式取值)。也就是说,深色模式不是靠组件里写dark:bg-xxx实现的,而是在theme-variables.css中为同一组语义变量提供深色值。button.tsx中使用的bg-primarytext-primary-foregroundborder-control-borderring-focus-ring全部是这类语义 Token。更详细的规则见 shade-tokens-not-hex 与 shade-no-dark-variants 两份技能文档。

完成前验收清单(逐项继承原文档)

以下清单必须逐项通过,才允许把组件/模式标记为完成:

  • 位于正确的层(由 shade-component-decision 判定)
  • kebab-case 文件名、PascalCase 导出、同目录<name>.stories.tsx
  • className已透传并用cn()合并(始终如此)
  • 若组件渲染的 DOM 可能被消费方持有 ref,则使用forwardRef
  • 若组件有变体,则使用cva()并设置defaultVariants
  • 四种必需状态均正常工作且在 story 中可见
  • 仅用语义 Token——无 hex、无裸灰色、无dark:颜色变体
  • 通用 Component 上不携带产品特异的 props
  • Story 具备tags: ['autodocs']、组件级描述与每个 story 的一行描述
  • pnpm lintpnpm test与 Storybook 全部干净

最后一条对应 package.json 中的真实脚本定义:pnpm test= 类型检查 + 带覆盖率的 Vitest(pnpm test:types && vitest run --coverage),pnpm lint= ESLint 检查src/test/pnpm storybook= 在 6006 端口启动 Storybook 查看src/docs/下的文档。在独立应用中消费 Shade 时注意:Shade 当前是私有包,嵌入 Ghost Admin 的应用(apps/adminapps/activitypub)不应重复导入@tryghost/shade/styles.css或再包一层ShadeApp,应从层级子路径导入组件(如import {Button} from '@tryghost/shade/components'),CSS 与应用包装由 Admin 集中提供(见 apps/shade/README.md)。

唯一事实来源(Source of Truth)

清单文档末尾声明:规则的权威出处是Storybook 的 Overview / Contributing 页(contributing.mdx)与各组件自身的 stories。换言之,这份 SKILL.md 是"机器可触发的验收快照",当约定演进时,应以 Storybook 人类文档为准同步更新——这也是 AGENTS.md 中"当共享约定变化时,更新 Storybook 人类文档,不要只让本文件成为唯一来源"这条工作流要求。配合阅读同组技能文档(shade-component-decisionshade-importsshade-shadcn-installshade-use-primitives)可以覆盖"选层 → 安装/引入 → 实现 → 验收"的完整链路。

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 15:56:32

K210+STM32智能门禁系统搭建:双芯片协作、串口协议与稳定运行实战

简介&#xff1a;面向嵌入式开发者的智能小区门禁系统完整设计方案&#xff0c;融合K210与STM32双核心&#xff0c;适合具备嵌入式基础、希望实战人脸识别与车牌识别项目的研发人员和技术爱好者。系统由Maix Bit K210完成图像采集与人脸/车牌识别&#xff0c;STM32通过串口通信…

作者头像 李华
网站建设 2026/9/6 15:55:03

从ISO9001到质量手册:体系文件编写与docx格式兼容实操指南

简介&#xff1a;ISO9000质量管理体系及质量管理手册&#xff08;2025版&#xff09;是一份依据GB/T19001-2008标准编制的质量手册完整范本&#xff0c;面向企业质量管理人员、体系推进专员及内审员&#xff0c;助力解决体系建设中文件模板缺失、结构不完整等问题。文档为一个W…

作者头像 李华
网站建设 2026/9/6 15:41:42

基于SpringBoot+Vue喀纳斯旅游网站的设计与实现

1. 项目背景与意义喀纳斯景区位于新疆阿勒泰地区&#xff0c;以其壮丽的自然风光和独特的图瓦人文化闻名于世。然而&#xff0c;传统旅游信息获取渠道分散&#xff0c;游客往往需要辗转多个平台才能完成景点查询、路线规划、住宿预订等操作&#xff0c;体验割裂且效率低下。与此…

作者头像 李华
网站建设 2026/9/6 15:40:37

STM32+EC200S+MQTT:4G Cat 1物联网终端开发实战

简介&#xff1a;面向具备嵌入式C开发基础、熟悉STM32与UART通信的物联网开发者&#xff0c;该资源围绕移远EC200S 4G Cat.1模块直连MQTT服务器&#xff0c;提供一套完整的物联网终端解决方案。内容涵盖系统架构设计、硬件引脚连接、AT指令驱动开发、MQTT协议封装、主程序集成、…

作者头像 李华