@spree/dashboard-ui 设计系统解析:Spree Dashboard 的无头组件、设计令牌与源码即交付模式
spree/dashboard-ui 设计系统解析Spree Dashboard 的无头组件、设计令牌与源码即交付模式【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree导读spree/dashboard-ui是 Spree Dashboard 管理后台的官方设计系统包采用shadcn 基础组件 无头headless组合组件 设计令牌design tokens三层架构并遵循源码即交付source-only的发布模式。本文以 packages/dashboard-ui/README.md 为主线结合 packages/dashboard-ui/src 的实际源码与 packages/dashboard-core/src/vite/index.ts 的接线实现讲清楚这个包内部到底装了什么、无头原则如何落地、为什么只发 TypeScript 源码、以及插件作者/卖家面板开发者/第三方应用如何在自己的 Vite Tailwind v4 工程中直接复用这些组件。读完你既能直接上手接入也能理解 Spree 仪表盘生态设计系统与框架分离的架构边界。注意该包当前处于Developer Preview阶段版本号以0.x演进仓库中当前为0.13.1API 可能在小版本之间发生变化接入时请锁定版本。一、定位设计系统dashboard-ui与框架dashboard-core的分工Spree 仓库采用 pnpm workspace 组织前端包仪表盘侧主要由两个包协作spree/dashboard-ui本包——设计系统负责长什么样提供可复用的 UI 原语、组合组件与设计令牌spree/dashboard-core——框架负责数据从哪来、路由怎么走承载 auth/store/theme 等 Provider、TanStack Query hooks 以及 Admin SDK 的对接。README 用一句话划清边界dashboard-ui is the design system, dashboard-core is the framework。凡是需要 provider认证、商店上下文、主题、需要 TanStack Query hook、需要 Admin SDK的能力一律不在本包内而是放到 dashboard-core。这个分层直接决定了本包的无头设计因为 UI 包不依赖任何业务数据源插件作者、卖家面板开发者、甚至只使用spree/dashboard-ui的第三方都可以用各自的数据源实例化同一套组件。二、Whats inside四层目录结构README 将包内容划分为四个区域与源码目录一一对应目录内容定位src/ui/shadcn 基础原语Button、Input、Dialog、Sheet、Table 等Copy-paste 拥有制可按需 fork 进自己的项目src/spree/基于原语组合的业务组件PageHeader、ResourceTable、AppSidebar 等全部无头数据与回调通过 props 传入src/styles.css设计令牌 Tailwind 主题从 Vite 应用入口导入一次即可src/lib/通用工具cn、formatter 等零依赖小工具2.1 ui/shadcn 原语层src/ui/下现有 40 个原语文件覆盖了后台管理界面的基础控件面packages/dashboard-ui/src/ui 中包含button.tsx、input.tsx、dialog.tsx、sheet.tsx、table.tsx、dropdown-menu.tsx、select.tsx、combobox.tsx、tabs.tsx、switch.tsx、checkbox.tsx、radio-group.tsx、date-picker.tsx、date-range-picker.tsx、calendar.tsx、pagination.tsx、sidebar.tsx、breadcrumb.tsx、toast.tsx、tooltip.tsx、popover.tsx、rich-text-editor.tsx、data-table.tsx、chart.tsx、map.tsx、skeleton.tsx、progress.tsx、avatar.tsx、badge.tsx、card.tsx、field.tsx等。README 特别说明这一层是copy-paste owned复制粘贴拥有制。这与 shadcn 的社区惯例一致——原语源码直接放在你的项目里随你改造。依赖清单见 packages/dashboard-ui/package.json也印证了这一点class-variance-authority、clsx、tailwind-merge正是 shadcn 组件变体与类名合并的标准三件套而base-ui/reactv1.4.x是底层无头交互引擎dnd-kit/*支撑拖拽排序cmdk支撑命令面板。2.2 spree/无头组合组件层src/spree/是基于原语组合出的 Spree 业务组件覆盖订单、履约、售后、商品、元数据、批量编辑等后台高频场景例如页面骨架类resource-layout.tsx、back-button.tsx、form-section.tsx、form-actions.tsx、route-error-boundary.tsx列表与表格类data-grid/含data-grid.tsx、cells.tsx、fill-handle.tsx、use-data-grid-keyboard.ts等、resource-combobox.tsx、resource-multi-autocomplete.tsx、resource-name-cell.tsx、row-actions.tsx、bulk-price-table.tsx、bulk-variants-table.tsx对话框类confirm-dialog.tsx、bulk-dialog.tsx、order-cancel-dialog.tsx、return-dialogs.tsx、claim-resolve-dialog.tsx、delivery-form-dialog.tsx、fulfillment-edit-dialog.tsx、label-upload-dialog.tsx、json-preview-drawer.tsx履约/售后类fulfill-items-form.tsx、fulfillment-panel.tsx、fulfillment-item-list.tsx、post-sale-create-dialogs.tsx、post-sale-fields.tsx其他领域组件metadata/metadata-card.tsx、products/status-card.tsx、quantity-tier-editor.tsx、tax-identifiers-card.tsx、address-block.tsx、address-map.tsx、country-flag.tsx、color-picker.tsx、secret-input.tsx、tag-list.tsx、wizard.tsx、relative-time.tsx、theme-toggle.tsx2.3 lib/ 与 hooks/通用工具packages/dashboard-ui/src/lib/utils.ts 提供了贯穿全包的cn(...)工具twMerge(clsx(inputs))用于合并类名并去重冲突的 Tailwind utility 类另有date-locale.ts日期本地化、same-rich-text.ts富文本比较、validation-messages.ts表单校验文案src/hooks/提供use-copy-to-clipboard、use-mobile、use-prefers-reduced-motion、use-scrolled等轻量 hooks。2.4 index.ts桶文件barrel与两种导入路径packages/dashboard-ui/src/index.ts 是包的总出口支持两种导入方式// 扁平导入barrel从 index.ts 一次拿齐 import { Button, Card, PageHeader } from spree/dashboard-ui // 深路径导入deep import按需加载、便于 fork 或跳过 barrel import { Button } from spree/dashboard-ui/ui/button一个值得注意的工程细节JsonPreviewDrawer与JsonValueView刻意不从 barrel 导出因为二者依赖uiw/react-json-view约 30KB gzip。只有消费者通过spree/dashboard-ui/spree/json-preview-drawer深导入时代码分割code-splitting才能生效。类型也走同样的路径import { type JsonPreviewDrawerProps } from spree/dashboard-ui/spree/json-preview-drawer。这是深导入路径设计目的的最好例证。三、无头原则数据与回调全部走 propsREADME 对src/spree/组合组件的核心约束是All headless: data and callbacks come in via props. No provider imports, no hook calls, no SDK access.意思是这些组件不依赖任何业务 Provider、不调用业务数据 hooks、不接触 Admin SDK——调用方负责把数据和回调以 props 形式注入。这样组件就能在spree/dashboard之外被自由组合插件作者、卖家面板开发者、只用本包的第三方都能用自己的数据源实例化同一套组件。3.1 源码佐证ResourceLayout 的纯 props 骨架packages/dashboard-ui/src/spree/resource-layout.tsx 是无头组合组件的典型代表。它的 props 全部是ReactNodeinterface ResourceLayoutProps { /** Rendered above the two columns. Typically PageHeader /. */ header?: ReactNode /** Left column content (8/12 on lg). */ main: ReactNode /** Right column content (4/12 on lg). Optional — when omitted, main spans full width. */ sidebar?: ReactNode }渲染逻辑也很纯粹header放在顶部sidebar存在时按 12 栅格排成 8/4 两列lg及以上否则main占满全宽。源码注释还说明了它替代了此前在商品/订单详情页重复手写的栅格布局以及旧的_edit_resource.html.erb骨架——这是后台详情页的标准脚手架。3.2 关于no hook calls的精确边界从源码看部分组件为了实现导航、翻译等框架行为仍会调用路由与 i18n 的 hooks。例如 packages/dashboard-ui/src/spree/back-button.tsx 内部使用了useRouter、useParams({ strict: false })与useTranslation()其注释还专门处理了一个多租户细节操作员后台的路由参数是$storeId而卖家面板是$sellerId因此它同时读取params.storeId ?? params.sellerId作为租户 id避免把卖家导航到/undefined/...。可以这样理解 README 的表述业务数据订单、商品、客户等实体数据与回调绝不通过内部 hook 获取一律由 props 注入而导航、翻译这类组件自身行为所需的框架能力组件内部自行消费。读者在封装自己的无头组件时可参照同样的边界。四、源码即交付Source-only为什么只发 TypeScript 源码README 明确指出该包交付的是 TypeScript 源码而不是编译后的 bundle消费方通过自己的 Vite Tailwind 配置来编译它。4.1 根本原因Tailwind v4 的源码扫描Tailwind v4 会在消费应用的源码文件中扫描 utility class 并生成对应的 CSS。如果 dashboard-ui 预编译一个 CSS bundle 交付那么只被源码导入的组件用到的 class就会丢失——预编译产物无法感知消费方实际导入了哪些组件。所以唯一可靠的方案是把源码交出去让 Tailwind 在消费方构建时扫描这些源码。4.2 styles.css 内的自扫描机制packages/dashboard-ui/src/styles.css全文件约 895 行顶部就实现了这一机制import tailwindcss; import tw-animate-css; import shadcn/tailwind.css; import fontsource-variable/geist; import fontsource-variable/geist-mono; /* Tailwind v4 默认只扫描消费项目自己的源码 source 让它额外扫描本包源码路径相对本 CSS 文件。 */ source ./ui/**/*.{ts,tsx}; source ./spree/**/*.{ts,tsx};这里的路径是相对packages/dashboard-ui/src/styles.css的因此无论本包是通过 workspace 符号链接node_modules/spree/dashboard-ui→packages/dashboard-ui还是将来从 npm tarball 安装相对布局都一致source都能正确解析。4.3 vite 插件的接线spreeDashboardPlugin消费方并不需要手工写这些source。spree/dashboard-core/vite导出的spreeDashboardPlugin会替你做掉接线其实现位于 packages/dashboard-core/src/vite/index.ts它同时处理三件事插件激活提供virtual:spree-dashboard-plugins虚拟模块汇总自动发现或白名单内的 dashboard 插件并副作用导入——安装一个插件只需pnpm add无需改宿主代码源码扫描向宿主 CSS 入口注入source指令让 Tailwind v4 扫描spree/dashboard-core、spree/dashboard-ui以及每个插件包。由于 Tailwind 的source只接受文件系统路径不接受裸包名插件通过 Node 模块解析把包名解析为绝对路径从而兼容 workspace 符号链接、npm tarball、pnpm 的.pnpm/hoisting 等任意包布局打包 Tailwind 本身按正确顺序返回tailwindcss/vite宿主无需再单独添加。插件选项定义如下摘自源码 packages/dashboard-core/src/vite/index.tsexport interface SpreeDashboardPluginOptions { /** * 宿主应用 CSS 入口文件路径即执行 * import spree/dashboard-ui/styles.css 的那个文件 * 相对宿主项目根目录解析。默认 ./src/styles.css。 */ cssEntry?: string /** * 除 spree/dashboard-core 与 spree/dashboard-ui 之外 * Tailwind 还应扫描的 shell 包。默认 [spree/dashboard]运营后台。 * 卖家面板或宿主自研 shell 传入自己的包名无法解析的名字会被跳过而非报错。 */ shellPackages?: string[] /** * 宿主安装的 dashboard 插件包名列表。省略时常见情况插件会自动发现 * 遍历宿主 package.json 的 dependencies * 拾取声明了 spree: { dashboard: { plugin: true } } 的包。 * 传入显式数组可退出自动发现白名单模式。 */ plugins?: string[] }4.4 package.json 中的佐证packages/dashboard-ui/package.json 的多处字段直接印证了源码即交付main/module/types全部指向./src/index.ts——没有dist产物files只包含[src, README.md, LICENSE]exports映射把子路径精确暴露出来./icons、./styles.css、./ui/*、./spree/*、./lib/*、./hooks/*scripts里是biome check srclint与tsc --noEmittypecheck说明质量门禁运行在源码层而非构建产物层。五、设计令牌与主题styles.css 的架构细节src/styles.css不只是一层 CSS而是一套完整的令牌系统。它先以 CSS 变量定义设计令牌再通过 Tailwind v4 的theme机制映射到工具类。整体遵循以下可读出的设计决策源码注释中均有明确说明5.1 中性色统一使用 stone 色阶所有中性色都取自 Tailwind 的stone暖灰阶梯而非 zinc/slate/neutral。理由在 styles.css 的注释中写得很直白stone 在 0.001–0.013 的极低色度上带了暖色倾向足以避免大面积白/灰区域看起来发蓝发冷同时让蓝色强调色成为调色板里唯一读起来像交互的颜色。令牌之间用var()相互引用而非重复数值防止同类令牌漂移例如--popover: var(--card)、--muted: var(--background)、--accent: var(--secondary)。5.2 品牌色与主操作色:root { --brand-blue: oklch(0.5967 0.2212 258); /* #0077ff — 站点强调色 */ --brand-blue-strong: oklch(0.5106 0.1868 257.7); /* #0060ce — 链接 hover */ --link: oklch(0.5106 0.1868 257.7); --link-hover: oklch(0.44 0.16 257.7); --primary: var(--foreground); /* stone-950 近黑主按钮 */ --primary-foreground: var(--card); }注释说明了一个反直觉的设计主 CTA 用近黑色而非站点蓝。因为在密集的管理界面里蓝色必须保留为选中/聚焦信号一旦每个页面都渲染一颗饱和蓝按钮蓝色就失去了这种语义。5.3 边框令牌的职责细分边框被拆成多个令牌各自承担不同的可访问性任务--border-basestone-200--border90% 混合--border-subtle50% 混合容器的 1px 边缘与容器内分隔线--border-controlcheckbox/radio 这类只有边框能标识自身的控件需满足 WCAG 1.4.11 的 3:1 对比度浅色模式 3.25:1--border-field文本输入框、select、combobox 这类边框只是轮廓的控件可更轻stone-300避免十个字段的表格变成一堵框墙。5.4 状态色 ramp跟随 Vercel Geist 色彩系统四组状态色绿/琥珀/红/蓝按 Geist 系统的分工组织低阶步为组件背景、中阶步为边框、高阶步为文字与图标每组是浅色填充 同色系深色文字的三件套--status-green-bg: var(--color-green-100); --status-green-border: var(--color-green-200); --status-green-fg: var(--color-green-700); /* amber / red 同理blue 走 sky 阶梯避免与品牌蓝混淆 */注释里还记录了对比度微调Tailwind 的 green-700 比同级略浅落在 green-100 上只有 4.497:1低于 12px badge 文字需要的 4.5:1因此绿色填充降一档。蓝色 badge 刻意从sky阶梯取色因为--color-blue-500/600已被重映射到品牌强调色直接使用会把它读成品牌标识而非中性状态。5.5 明暗双主题浅色模式:root背景为 stone-50、卡片纯白oklch(1 0 0)、文字 stone-950控件与卡片同色靠边框勾勒深色模式.dark通过custom-variant dark (:is(.dark *))挂接采用 Vercel 式true black基底卡片为 stone-900表面靠变亮来呈现抬升感——注释特别强调暗色 ramp 不是亮色的简单变暗而是独立取色如--muted-foreground暗色取 stone-400因为旧值在 accent 上只有 4.20:1。令牌体系还包括--card-container承载记录的容器卡片用页面底色、--nested-raised容器内抬升的记录表面、--track-recessedtab 轨道/分段控件的凹槽、--ring聚焦环明暗模式统一为品牌蓝、--chart-1..5图表色板以及一整套--sidebar-*令牌侧边栏令牌全部指向对应的页面令牌而非复制数值。5.6 字体与图标字体通过fontsource-variable/geist与geist-mono打包变量字体Geist / Geist Mono国旗图标通过import flag-icons/css/flag-icons.min.css引入CountryFlag isoUS /会解析为.fi.fi-us以 background-image 按需懒加载flags/4x3/us.svg——只有实际渲染的国旗才会产生网络请求。六、依赖矩阵消费方要提供什么README 明确消费方应用需要提供react、react-dom、tanstack/react-router、tanstack/react-hotkeys、react-hook-form、i18next、react-i18next。精确版本范围在 packages/dashboard-ui/package.json 的peerDependencies中当前仓库版本peer 依赖版本范围react/react-dom^19.2.6tanstack/react-router^1.169.2tanstack/react-hotkeys^0.10.0react-hook-form^7.75.0i18next^26.2.0react-i18next^17.0.8tailwindcss^4.2.4而包自身的dependencies则内置了实现细节base-ui/react无头交互、tanstack/react-table表格逻辑、tiptap/*富文本编辑器、dnd-kit/*拖拽排序、recharts图表、maplibre-gl地图、react-day-picker日期选择、react-colorful取色器、date-fns/date-fns-tz日期处理、lucide-react图标、uiw/react-json-viewJSON 查看等。这套依赖分布说明交互逻辑与渲染原语由本包自带而框架级能力路由、表单、i18n、React 运行时由消费方提供。七、Whats NOT here与 dashboard-core 的边界README 用一节专门划清这里没有的东西任何需要 providerauth、store、theme、TanStack Query hook 或 Admin SDK 的组件都住在spree/dashboard-core。拆分结论可以总结为一张职责表能力归属shadcn 原语、组合组件、设计令牌、通用工具spree/dashboard-ui认证/商店/主题 Provider、数据获取 hooks、Admin SDK 对接、插件激活、Tailwind 接线spree/dashboard-core对本包而言这既是约束也是承诺只要你不注入业务 provider组件就永远可组合、可移植。八、动手接入在 Vite Tailwind v4 应用中使用基于源码与配置一个最小接入流程如下以标准 Vite 布局为例1. 安装依赖pnpm add spree/dashboard-ui spree/dashboard-core # 并按 peerDependencies 提供 react、tanstack/react-router、react-hook-form、i18next 等2. 在 CSS 入口导入一次样式/* src/styles.css */ import spree/dashboard-ui/styles.css;3. 在 vite.config.ts 中启用 spreeDashboardPlugin// vite.config.ts import { spreeDashboardPlugin } from spree/dashboard-core/vite export default defineConfig({ plugins: [ spreeDashboardPlugin({ // cssEntry 默认为 ./src/styles.css通常无需配置 // shellPackages 默认 [spree/dashboard]独立面板传自己的包名 // plugins 省略时自动发现声明了 spree.dashboard.plugin 的依赖包 }), ], })该插件会替你把tailwindcss/vite与source注入一次性接好见上文 4.3 节。4. 在组件中使用import { Button, Card, PageHeader, ResourceLayout } from spree/dashboard-ui export function ProductDetailPage() { return ( ResourceLayout header{PageHeader title商品详情 /} main{Card商品主体信息/Card} sidebar{Card右侧栏价格、库存等/Card} / ) }5. 需要 fork 时直接复制源码shadcn 原语走 copy-paste 模式把 packages/dashboard-ui/src/ui 下对应文件复制进自己的src/components/ui随项目自由修改。九、版本状态与演进提示当前仓库中本包版本为0.13.1MIT License作者 Vendo Connect Inc.处于 Developer PreviewREADME 明确提示 API 可能在0.x版本之间变化仓库根目录 pnpm-workspace.yaml 将本包与其他 Spree 前端包组织为 workspace本地开发时spree/dashboard-ui会以符号链接形式出现在消费方的node_modules中源码即交付的模式天然兼容这种开发方式若要在自己的项目里做更深入的定制可同时研读 packages/dashboard-ui/src/index.ts完整导出清单与代码分割策略、packages/dashboard-ui/src/styles.css完整令牌体系以及 packages/dashboard-core/src/vite/index.ts插件系统的完整实现。结语spree/dashboard-ui用一个包回答了后台设计系统的三个问题长什么样设计令牌与 shadcn 原语、怎么组织无头组合组件数据全部走 props、怎么交付TypeScript 源码 Tailwind v4source扫描。它与spree/dashboard-core的设计系统/框架二分法让同一套 UI 能被运营后台、卖家面板、插件作者和纯第三方应用共用。理解这套设计是你在 Spree 生态中构建自定义仪表盘、插件或独立管理界面的起点。【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考