news 2026/9/15 22:16:32

Nango 设计系统(@nangohq/design-system)深度指南:设计 Token、Storybook 与 React 组件实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nango 设计系统(@nangohq/design-system)深度指南:设计 Token、Storybook 与 React 组件实战

Nango 设计系统(@nangohq/design-system)深度指南:设计 Token、Storybook 与 React 组件实战

【免费下载链接】nangoBuild product integrations with AI.项目地址: https://gitcode.com/GitHub_Trending/na/nango

导读

本文以 Nango 开源仓库中的packages/design-system包为核心,系统讲解这套共享设计系统的三大支柱:设计 Token 流水线(Figma → Tokens Studio → Style Dictionary → CSS 变量 → Tailwind v4 工具类)、Storybook 组件工作台(含 MCP 服务与无障碍审计)以及React 组件库(Radix UI + CVA + tailwind-merge 模式)。读完本文,你将掌握如何在本地启动 Storybook、如何同步并重建设计 Token、如何按约定新增一个组件变体或新组件,以及这套流水线在 Nango Web 应用中的消费方式。

本文所有路径均以仓库根目录为基准,涉及的核心文件见 packages/design-system/README.md。


一、包结构与定位

@nangohq/design-system是 Nango 应用(packages/webapp)共享的前端设计系统,涵盖三部分内容:

  • 设计 Token(design tokens):由 Figma 中的 Tokens Studio 插件创作,编译为 CSS 自定义属性(custom properties);
  • React 组件:按需添加、开箱即用的 UI 组件;
  • Storybook:承载上述 token 与组件的“活样式指南”(living style guide)。

包的导出配置定义在 packages/design-system/package.json,主入口与 token CSS 通过exports字段暴露:

"exports": { ".": { "import": "./src/index.ts", "types": "./src/index.ts" }, "./tokens/tokens.generated.css": "./tokens/tokens.generated.css" }

包名对外暴露两样东西:组件(从src/index.ts这个 barrel 文件导出)和编译好的 token CSS。所有公开导出集中在 packages/design-system/src/index.ts,包括ButtonIconButtonbuttonVariantsAlertBadgeDialogFieldInputInputGroupTextareaTooltipCard等组件及其variants对象,以及供消费方复用的dsTwMergeConfig

从源码结构看,组件全部平铺在src/components/ui/目录下(shadcn 惯例),配合同名的*.stories.tsx故事文件;src/lib/cn.ts提供类名合并工具;tokens/目录存放 token 的“双源文件”。


二、Storybook:本地运行、主题切换与无障碍审计

Storybook 是这套设计系统的活样式指南,可在本地启动后浏览 token 与组件在亮色/暗色两种主题下的表现。

2.1 启动方式

# 从仓库根目录 npm run storybook # 或者直接在包目录内 cd packages/design-system npm run storybook

启动后访问http://localhost:6006。点击右上角的Themes工具栏按钮,即可在亮色与暗色主题之间切换。

注意storybook脚本使用NODE_PATH=./node_modules来规避 Vite 7 的 CJS 模块解析问题(脚本定义见 packages/design-system/package.json)。该设置安全无害,应予以保留,不要移除。

仓库还提供了静态构建命令npm run build-storybook(对应build-storybook脚本)。与storybook dev不同的是,build-storybook急切(eagerly)转换每一个故事,因此任何损坏的 import 都会导致构建失败——这在 dev server 下可能不会被加载到。AGENTS.md建议:当某个故事的 import 发生变化时,推送前先在本地运行npm run -w @nangohq/design-system build-storybook验证。

2.2 Storybook MCP:让 AI 助手直接读懂组件库

Storybook 自带 MCP 服务器(@storybook/addon-mcp,见 package.json 的 devDependencies),把故事的文档暴露给 AI 助手。这样可以让 Claude 在充分了解现有 stories、props 与用法示例的前提下构建或修改组件,无需手动复制粘贴文档。

MCP 服务默认关闭,以避免未运行 Storybook 的工程师遇到连接错误。需要启用时,在仓库根目录执行一次:

claude mcp add --transport http storybook http://localhost:6006/mcp

该命令把配置写入.claude/settings.local.json(已被 gitignore)。然后启动 Storybook(npm run storybook)并重载 Claude Code,下面的工具就会自动可用:

工具作用
list-all-documentation列出所有 story ID 与组件名
get-documentation返回某个组件的 props、变体与用法
preview-stories渲染一个 story 并返回预览 URL

2.3 无障碍(Accessibility)审计

@storybook/addon-a11y会对每一个 story运行基于 axe-core 的自动化无障碍审计(依赖版本axe-core 4.12.0记录在 packages/design-system/package.json)。打开 Storybook UI 底部的Accessibility面板,即可看到当前渲染 story 的违规项(violations)、未完成检查(incomplete)与通过规则(passing)。组件发布前应修复所有违规项。


三、设计 Token:从 Figma 到 CSS 变量再到 Tailwind 工具类

3.1 创作与同步链路

设计 Token 在 Figma 中通过 Tokens Studio 插件创作,随后编译为 CSS 自定义属性。Tokens Studio 配置了GitHub sync,指向本仓库的design/tokens分支——这意味着设计师可以直接从 Figma 插件推送 token 变更,任何连接到同一 sync 配置的 Figma 文件都共享同一套 token。

3.2 目录结构

packages/design-system/ tokens/ tokens.json Tokens Studio 导出(事实源,提交到 git) tokens.generated.css 生成的 CSS 自定义属性(提交到 git) scripts/ tokens-fetch.mjs token 流水线脚本

tokens.json是 Tokens Studio 的原始导出;tokens.generated.css是编译产物。两者都提交到 git。除 README 中列出的结构外,实际目录中还包含:

  • tokens/types.tstokens/stories/ColorPalette.stories.tsxTypography.stories.tsx),用于在 Storybook 中可视化展示色板与字体;
  • scripts/tokens-check-removed.mjs(对应npm run tokens:check),用于检查 token 是否被移除。

3.3 同步工作流(Designer → Code)

设计师 → 代码:

  1. 在 Figma 中通过 Tokens Studio 插件编辑 token;
  2. 推送到design/tokens分支(插件 Settings → Sync provider → GitHub);
  3. 通知开发者运行npm run tokens:fetch

开发者拉取最新 token:

cd packages/design-system npm run tokens:fetch git add tokens/ git commit -m "chore(design-system): update tokens"

仅重建 CSS、不拉取(例如解决了tokens.json的合并冲突之后):

npm run tokens:build

这两个命令对应 packages/design-system/package.json 中的脚本:

"tokens:fetch": "node scripts/tokens-fetch.mjs", "tokens:build": "node scripts/tokens-fetch.mjs --build-only", "tokens:check": "node scripts/tokens-check-removed.mjs"

3.4 流水线底层实现(源码解读)

packages/design-system/scripts/tokens-fetch.mjs 是整个 token 流水线的核心,它用 Style Dictionary v5(style-dictionary 5.4.4)配合@tokens-studio/sd-transforms完成编译。几个关键实现细节值得注意:

  • 拉取来源tokens:fetch直接从 GitHub raw 地址https://raw.githubusercontent.com/NangoHQ/nango/design/tokens/packages/design-system/tokens/tokens.json拉取最新tokens.json
  • --build-only模式tokens:build从本地已有的tokens.json读取,不访问网络;若tokens.json不存在会直接报错并提示先运行tokens:fetch
  • --strict模式:脚本支持--strict参数,遇到未解析的 token 别名(SD 会保留为原始{alias}字符串)时直接抛错,而不是静默跳过,避免产出无效 CSS;
  • boxShadow 序列化:SD v5 的 DTCG token 把处理后的值放在$value中,内置css/variables格式对 boxShadow 数组会退化为"[object Object]",因此脚本自定义了formatTokenValue,把innerShadow/outerShadowx/y/blur/spread/color逐项拼成 CSSbox-shadow字符串;
  • 原子化写入:脚本先构建 CSS,若 Style Dictionary 抛错则保持磁盘上的tokens.json原封不动,保证工作区始终处于“旧 tokens.json + 旧 tokens.generated.css”的一致状态。

从脚本的PRIM_MAPPINGS常量可以看出原始 token 到 Tailwind 命名空间的映射策略(这也是生成 CSS 中各类工具类的前缀来源):

原始 token 前缀CSS 变量Tailwind 工具类
radius---ds-radius-*rounded-ds-*
border-width---ds-border-width-*border-ds-*
typography-font-size---ds-typography-font-size-*text-ds-*
typography-font-weight---ds-typography-font-weight-*font-ds-*
typography-line-height---ds-typography-line-height-*leading-ds-*
typography-letter-spacing---ds-typography-letter-spacing-*tracking-ds-*

间距(spacing)被刻意排除在映射之外:Tailwind 默认的 4px 间距刻度与--ds-space-*token 完全一致,所以gap-2就等于--ds-space-2(8px),无需额外注册。

3.5 权威方向(Canonical direction)

Token 变更应当始终发源于 Figma——设计师是事实源的拥有者。直接编辑tokens.json并推送到design/tokens在技术上可行(GitHub sync 是双向的),但这不是预期工作流,且存在与 Figma 文件产生分叉的风险。

3.6 生成的 CSS 结构

tokens.generated.css由五段内容组成(真实文件见 packages/design-system/tokens/tokens.generated.css):

/* Primitives — --ds- 前缀,原始刻度值 */ :root { --ds-color-neutral-50: #f9fafb; --ds-radius-sm: 4px; --ds-typography-font-size-md: 14px; ... } /* Semantic tokens — light (默认) */ :root { --surface-canvas: #f9fafb; --text-strong: #111827; ... } /* Semantic tokens — dark */ [data-theme="dark"] { --surface-canvas: #0f172a; --text-strong: #f8fafc; ... } /* Tailwind @theme — 语义 token → bg-*、text-*、border-*、shadow-* 工具类 */ @theme { --color-surface-canvas: var(--surface-canvas); --shadow-focus-outline-default: var(--focus-outline-default); ... } /* Tailwind @theme — 原始 token → rounded-ds-*、text-ds-*、font-ds-* 等 */ @theme { --radius-ds-sm: var(--ds-radius-sm); --text-ds-md: var(--ds-typography-font-size-md); --font-weight-ds-medium: var(--ds-typography-font-weight-medium); ... }

对照真实文件可以看到完整的分层:原始 token 段(--ds-*前缀)包含颜色(neutral/brand/info/success/warning/danger 全色阶、alpha 透明度、icon stroke)、间距、圆角、边框宽度、排版、动效时长与缓动曲线、阴影、透明度、模糊、布局断点等;语义 token 段按 light(:root)与 dark([data-theme="dark"])分别定义--icon-*--surface-*--text-*--border-*--interactive-*--status-*--chart-*--focus-ring-*等;随后是@theme注册段与.type-*复合排版类(例如.type-heading-lg一次性设置 font-family、font-size、font-weight、line-height、letter-spacing)。

关于 boxShadow 的注册有一个细节:语义阴影 token 使用@theme inline注册,使生成的工具类直接引用语义变量(如var(--focus-outline-default)),而不是经过中间--shadow-*CSS 属性,从而保证暗色主题对底层语义变量的覆盖始终生效。

3.7 Token 命名与工具类映射

TokenCSS 变量Tailwind 工具类
Primitives.color.neutral.50--ds-color-neutral-50
Primitives.radius.sm--ds-radius-smrounded-ds-sm
Primitives.typography.font-size.md--ds-typography-font-size-mdtext-ds-md
Primitives.typography.font-weight.medium--ds-typography-font-weight-mediumfont-ds-medium
Semantic/Light.surface.canvas--surface-canvasbg-surface-canvas
Semantic/Light.text.strong--text-strongtext-text-strong
Semantic/Light.focus-outline-default--focus-outline-defaultshadow-focus-outline-default

原始颜色 token(--ds-color-*)被刻意排除在@theme之外,目的是强制组件只使用语义 token;而原始尺寸/排版 token 则以ds-前缀进入@theme,使它们可以作为普通工具类使用,无需[var(...)]这种任意值写法。

3.8 在 webapp 中消费

packages/webapp/src/index.css通过包名直接引入生成的 CSS:

@import '@nangohq/design-system/tokens/tokens.generated.css';

同时 packages/webapp/package.json 以"@nangohq/design-system": "file:../design-system"的形式本地引用该包。搜索packages/webapp可以看到大量组件消费示例:providers.tsx引入TooltipProvidercomponents/patterns/下的CriticalErrorAlert.tsxDestructiveActionModal.tsxSecretInput.tsxScopesInput.tsxEditableInput.tsxFilterMultiSelect.tsxKeyValueInput.tsxPeriodSelector.tsxConditionalTooltip.tsx等模式组件都从@nangohq/design-system导入ButtonIconButtonBadgeAlertInputGroupTooltip等。


四、组件:按需添加,遵循统一模式

组件按需添加——只有当产品页面真正需要时才新增,不做投机性添加(AGENTS.md中明确写明)。添加新组件的完整指南见 packages/design-system/AGENTS.md。

4.1 消费方式与约束

import { Button, IconButton, buttonVariants } from '@nangohq/design-system';

消费者还必须在应用根部导入一次 token CSS(见 3.8 节)。组件只使用语义 CSS 变量——不允许出现裸 hex 色值、硬编码尺寸或硬编码间距。

消费方应用不能直接给组件套className/style做外观定制(有 lint 规则拦截),因此当某个页面需要现有 props/变体覆盖不到的外观时,修复点就在本包内:新增或扩展一个变体。动手前应先与设计师确认该变体确实缺失,并在Figma 设计系统中同步添加对应变体,保持代码与设计一致(详见stories/StylingAndCustomization.mdx)。

4.2 以 Button 为参照的组件模式

packages/design-system/src/components/ui/button.tsx 是仓库中的参照实现,完整展示了这套模式:

  1. 导出cva变体对象,供消费者组合复用(buttonVariants);
  2. 导出 props 接口,继承React.HTMLAttributesVariantProps<typeof variants>
  3. forwardRef,保证消费方可以挂 ref;
  4. asChild(基于@radix-ui/react-slotSlot),支持把组件渲染为链接、路由<Link>等任意元素;
  5. cn()合并类名cn(variants(...), className)

buttonVariants定义了 8 个变体——primarysecondaryoutlineghostdanger,以及三个链接类变体link-accentlink-dangerlink-neutral——每个变体都严格走“Figma token → CSS 变量 → Tailwind 类”的映射,例如:

primary: [ 'bg-interactive-primary text-text-on-accent border-transparent', 'hover:bg-interactive-primary-hover', 'active:bg-interactive-primary-active', 'disabled:bg-interactive-disabled disabled:text-text-disabled disabled:border-transparent', 'focus-visible:shadow-focus-outline-default' ]

尺寸变体覆盖2xs(20px 方形图标按钮,配合IconButton)到lgButton还支持loading属性,加载时渲染内部Spinner并隐藏行内图标,同时设置aria-busyIconButton则要求必传label作为可访问名称(同时应用为aria-labeltitle)。

新增组件时,可以用 shadcn CLI 生成基础文件:

cd packages/design-system npx shadcn@latest add <component-name>

生成的文件落在src/components/ui/<component-name>.tsx,但其中使用的 shadcn 默认 CSS 变量(bg-primarytext-muted-foreground等)在本包中不存在,必须替换为tokens/tokens.generated.css中的 token 工具类或var(--token-name)

4.3 用 token 工具类还是原生 Tailwind?

规则一句话总结:外观(appearance)——设计师拥有决策权的值——必须来自 DS token 工具类;布局、尺寸与动效使用原生 Tailwind,其默认刻度与 token 一一对应。

设计师指定的值用ds-*/ 语义工具类:

类别用法示例
颜色语义工具类bg-surface-canvastext-text-defaultborder-border-default
焦点环shadow-*语义类shadow-focus-outline-default
字号text-ds-*text-ds-md而非text-sm
字重font-ds-*font-ds-medium而非font-medium
行高leading-ds-*leading-ds-normal而非leading-normal
字距tracking-ds-*tracking-ds-tight而非tracking-tight
圆角rounded-ds-*rounded-ds-sm而非rounded
边框宽度border-ds-*border-ds-1而非border

允许直接使用原生 Tailwind 的类别(默认刻度与 token 相等,无需 DS 工具类):

类别用法示例
间距原生刻度gap-2px-3(对应--ds-space-*4px 刻度)
图标尺寸原生刻度size-4(16px)、size-3.5(14px)
动效原生刻度duration-100=--ds-motion-duration-fastease-in-out=--ds-motion-easing-standard
布局/结构原生flexgriditems-centerrelativetruncatez-10

禁止className中以[var(--ds-*)]任意值形式内联 token——应优先使用已注册的工具类或上述原生类;若某个 token 没有对应工具类,则把它加进@theme块以生成工具类,而不是退回var()

另外有一个已知陷阱:border-ds-*工具类只设置四边边框,方向形式(border-t-ds-1border-b-ds-hairline)在 Tailwind v4 下不会渲染(根因未解)。单边边框的正确做法是:1px 用原生border-t等加颜色工具类(如border-t border-border-default);0.5px hairline 用任意值border-t-[0.5px]

4.4 焦点环(Focus rings)

组件通过box-shadow应用焦点环,使用--focus-outline-default(破坏性操作使用--focus-outline-danger),并自行用focus-visible:outline-none抑制原生 outline:

'focus-visible:outline-none focus-visible:shadow-focus-outline-default' // 破坏性操作: 'focus-visible:outline-none focus-visible:shadow-focus-outline-danger'

不需要全局*:focus-visiblereset——webapp 通过其focus-default工具类在组件级别作用相同的行为。真实文件中的语义阴影变量(--focus-outline-default--focus-outline-danger--container-inset等)都定义在tokens.generated.css的语义段中。

4.5cn()类名合并助手

所有类名合并都经过 packages/design-system/src/lib/cn.ts 的cn():它把clsx(条件/数组语法)与tailwind-merge(冲突消解)组合起来,例如cn('h-8', 'h-10')会得到'h-10'而不是'h-8 h-10'。其中dsTwMergeConfig(定义于 packages/design-system/src/lib/twMergeConfig.ts,并从 barrel 导出)教会tailwind-merge识别本包@theme生成的text-ds-*border-ds-*等 token 工具类,使它们被归入正确的冲突组、正确地去重——消费方可以在自己的cn中复用同一份配置。

4.6 新增组件五步法(来自 AGENTS.md)

  1. shadcn CLI 生成基础npx shadcn@latest add <component-name>,写入src/components/ui/。注意components.json@/*别名在 tsconfig 中刻意缺失(会与 Storybook 的@→ webapp 别名冲突),脚手架期间可临时添加、完成后移除;
  2. 替换 shadcn CSS 变量为设计 token:所有颜色/圆角/间距/动效必须来自tokens.generated.css
  3. 遵循组件模式cva变体对象 +forwardRef+asChild+cn()+ 导出variants
  4. 添加 Storybook 故事:与组件同目录的<name>.stories.tsx,标题为Design System/Components/<ComponentName>,展示所有需要“冻结”的变体与状态(默认、hover、disabled);focus 状态无需专门 story,评审者直接在默认/交互故事中点击即可触发;
  5. 从 barrel 导出:在src/index.ts追加export { MyComponent, type MyProps, myVariants } from './components/ui/my-component';

4.7 暗色模式

token 文件同时定义了亮色与暗色两套值:亮色是:root,暗色是[data-theme="dark"]。组件不需要暗色变体——CSS 变量自动完成主题切换。因此在 Storybook 中用Themes按钮切换后,务必在两种主题下逐一检查每个组件。


五、Tokens Studio GitHub sync 配置

5.1 配置项

字段
仓库(Repository)NangoHQ/nango
分支(Branch)design/tokens
文件路径(File path)packages/design-system/tokens/tokens.json

5.2 Figma 插件首次设置

  1. 安装 Tokens Studio for Figma 插件;
  2. 插件应能检测到已有 GitHub sync 并预填除 access token 外的所有字段;
  3. 对于Personal Access Token字段,从 1Password 获取:
    • Vault(保险库):Eng
    • Item(条目):GitHub PAT - Figma Tokens Studio Sync
  4. 点击Save——插件会从design/tokens分支加载既有 token。

六、工程实践小结

  • 设计单一事实源在 Figma,代码侧以tokens.json提交入库、tokens.generated.css由流水线产出并提交,二者缺一不可;
  • 同步纪律npm run tokens:fetch拉取 + 重建,npm run tokens:build仅重建;合并冲突后优先用tokens:build
  • 组件纪律:只使用语义 token 工具类,禁止硬编码外观值;新外观通过新增变体解决,且需与 Figma 设计系统同步;
  • Storybook 是验收现场:每个组件都要有 story,每个 story 都要过 axe 审计,PR 级联的托管 Storybook 便于分享 WIP 组件;
  • AI 友好:通过@storybook/addon-mcp启用 MCP 后,AI 助手可以直接查询组件文档与 props,降低跨包开发的上下文成本。

这套设计系统以"Figma 出 token、Style Dictionary 编译、Tailwind v4 生成工具类、Radix + CVA + tailwind-merge 产出组件、Storybook 统一验收"的闭环,为 Nango 的 Web 应用(packages/webapp)提供了跨页面一致的外观基础。需要深入某一环节时,建议直接阅读 packages/design-system/AGENTS.md(组件新增全指南)、packages/design-system/scripts/tokens-fetch.mjs(token 流水线源码)与 packages/design-system/tokens/tokens.generated.css(token 全量清单)。

【免费下载链接】nangoBuild product integrations with AI.项目地址: https://gitcode.com/GitHub_Trending/na/nango

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

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

CMake与XMake深度对比:C++构建系统的演进、差异与生态现实

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 22:14:59

企业办公用家用宽带凑合?这笔账到底怎么算

创业团队、小企业主、甚至有几年经营经验的老板&#xff0c;几乎都会在某个阶段动过这个念头&#xff1a;公司不就上个网、发个邮件、开个视频会议吗&#xff0c;家里那根宽带套餐便宜又快&#xff0c;为什么非得多花好几倍的钱去办企业专线&#xff1f;我见过太多公司在这个选…

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

网站风格一般具有哪三大特征从零搭建

告别模板丑站:网站风格三大特征对比评测与避坑指南 做网站最头疼的是什么?不是代码写不出来,而是做出来的东西看着像“五毛特效”,客户一看脸就黑。很多设计师转前端的朋友,手里攥着几张精美的UI图,结果一落地到Web,全是千疮百孔的模板味儿。这种 模板网站太丑不够用 的尴尬,咱们在行业里见得太多了。…

作者头像 李华
网站建设 2026/9/15 22:12:57

Python+Django美容院管理系统开发实战:从需求分析到部署交付

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华