Files
stash-ui/README.md
T
CNWei ebe4af3dab fix(ui): PasswordInput 眼睛移入输入框,隐藏 Edge 原生显隐按钮
- Input 新增 suffix 插槽,眼睛不再并排在外
- 隐藏 ::-ms-reveal / ::-ms-clear,避免 Edge 聚焦时出现两个眼睛
- 展示页补 suffix 示例;COMPONENTS.md / README 同步;版本 0.3.1
2026-10-30 08:59:22 +08:00

14 KiB
Raw Blame History

Stash UI

Stash 的设计系统与组件库:设计令牌 + 基础组件 + 组件展示站。 独立于 stash 应用仓库,后续页面(控制台、详情页、后台)都从这里取组件拼装。

  • 视觉标准来自 Alright 主题(与 stash/ALRIGHT-DESIGN.md 一致);
  • 零运行时依赖(只 peer 依赖 react / react-dom);
  • 样式用 CSS Modules + 设计令牌,换主题只改变量;
  • 自带一个可点开的展示站,每个组件都有变体、状态、props 与代码片段。

快速开始

npm install
npm run dev          # 文档站:http://127.0.0.1:5199
                     # 整页示例:http://127.0.0.1:5199/demo.html(?scheme=dark 直接进深色)
npm run typecheck    # tsc --noEmit
npm run build        # 构建库:dist/stash-ui.js + dist/stash-ui.css + dist/types
npm run build:site   # 构建静态站点:dist-site/(index.html + demo.html)
npm run lint         # oxlint

# 截图核对(需要本机 Chrome / Edge;playwright-core 不自带浏览器)
node scripts/screenshot.mjs                                        # 首屏
node scripts/screenshot.mjs http://127.0.0.1:5199/#buttons shots/buttons.png
node scripts/screenshot.mjs http://127.0.0.1:5199/ shots/cards.png '#cards'   # 只截某一节

环境要求:Node 24(package.json 里用 volta 固定了版本)。

文档(先读这个)

四份文档构成组件库的「契约」,新增 / 修改组件全部以它们为准:

文档 内容 什么时候看
DESIGN.md 规格总纲:分层结构、设计令牌、视觉规则、代码约定、验收清单、决策与已知坑 动手前、评审时
docs/COMPONENTS.md 组件目录与契约:现有 42 个组件的 API 与边界、组合模式、候选组件、图标新增流程 想知道「用哪个 / 有没有 / 缺什么」
docs/ADDING-A-COMPONENT.md 新增组件手册:12 步流程、代码模板、Spinner 完整示例、常见错误 要补一个组件时照着做
docs/ALRIGHT-DECOMPOSITION.md 参考站拆解:实测令牌、组件尺寸、交互节奏、暗色映射、保留差异 想核对「这个值对不对」
docs/STYLE-INTAKE.md 外部风格接入:截图 → 组件/令牌映射表、决策矩阵、验收清单 新项目给了外部设计稿、和现有风格不一致时

缺组件时的标准动作:先在 COMPONENTS.md §5 找到它(或登记一行)→ 按 ADDING-A-COMPONENT.md 走流程 → 用 DESIGN.md §6 的清单验收。

给 AI 用

仓库自带一个 skill,AI 不需要每次把五份文档读一遍:

  • skills/stash-ui/SKILL.md —— 入口:任务分流(用 / 改 / 加)、硬约束、验证命令、已知坑
  • AGENTS.md / CLAUDE.md —— 仓库根指路,Codex / Claude Code / Cursor 会自动读到
  • docs/AI-USAGE.md —— 三种接入方式(直接读文件 / DSH 技能目录 / DSH 插件包)与维护规则

原则:skill 只做路由和护栏,规则只写在文档里,避免两处维护。

主题(外部风格)

  • themes/example.css —— 只改 CSS 变量的主题示例(换品牌色 / 圆角 / 阴影 / 控件高度);
  • 用法:style.css 之后引入主题文件,禁止写选择器;
  • 完整流程(截图 → 映射 → 决策 → 吸收)见 DESIGN.md §11 与 docs/STYLE-INTAKE.md。

目录结构

stash-ui/
├── DESIGN.md                   # 规格总纲(分层/令牌/视觉/验收)
├── AGENTS.md / CLAUDE.md       # AI 入口(指路到 skill 与文档)
├── skills/stash-ui/SKILL.md    # AI skill:任务分流 + 约束 + 验证
├── docs/
│   ├── COMPONENTS.md           # 组件目录与契约
│   ├── ADDING-A-COMPONENT.md   # 新增组件手册
│   ├── ALRIGHT-DECOMPOSITION.md# 参考站拆解(数值依据)
│   └── AI-USAGE.md             # AI 接入方式与 skill 维护
├── src/                        # 组件库(发布物只有这一层)
│   ├── index.ts                # 统一出口:组件 + 类型 + 样式
│   ├── theme/
│   │   ├── tokens.css          # 设计令牌(颜色/间距/字号/圆角/阴影/动效)
│   │   ├── icons.ts            # 446 个图标名(自动生成)
│   │   ├── iconfont.css        # 图标字体与字形(自动生成)
│   │   └── assets/iconfont.woff2
│   └── components/             # 37 个组件,每个 = Xxx.tsx + Xxx.module.css(见下方清单)
├── site/                       # 展示站(不参与库构建)
│   ├── index.html / demo.html  # 文档站 / 整页示例
│   ├── App.tsx                 # 左侧目录 + 右侧示例
│   ├── app.css                 # 文档站自己的排版样式
│   ├── components/             # Section / Demo / PropsTable
│   └── sections/               # 每个组件一节
├── scripts/screenshot.mjs      # 截图核对用
├── vite.config.ts              # 展示站构建
└── vite.lib.config.ts          # 库构建

组件清单

组件 用途 关键 props
Button 操作按钮(圆角 / 胶囊,支持描边) variant size shape block loading icon
IconButton 圆形图标按钮(28/36/44) label(必填) icon variant size spinning
Card 白色容器(极浅描边) as padding hoverable
Cover 比例封面卡(2:3 / 4:3 / 16:9) ratio src caption lift onClick
Image 带比例 / 圆角 / 失败降级的图片 ratio fit radius fallback
FormField 表单字段排版(标签 + 提示 / 错误) label hint error required
Typography 标题 / 文字 / 段落 level tone size lines
Skeleton 骨架屏占位 variant lines width height
Spinner 加载指示 size label
Switch 即时开关 checked onChange size label
Checkbox 复选框 checked onChange label
RadioGroup 互斥单选组 options value onChange direction
Select 受控下拉选择 options value onChange placeholder
Segmented 视图形态切换 options value onChange size block
Tabs 页内分区切换 items activeKey onChange variant
Menu 侧栏两级菜单 items activeKey onSelect
Breadcrumb 路径导航 items separator
Table 只读数据表格 columns rows rowKey emptyText
Tag 分类 / 标签 / 状态(浅底 / 实底) tone variant shape icon dot
ItemBadge 状态角标:置顶 / 付费 / HOT tone
Badge 数字角标 count tone max
Avatar 头像 / 封面占位 src fallback size shape verified
Grid 卡片列表网格 columns tabletColumns mobileColumns gap as
Layout 页面骨架:页头 / 侧栏 / 内容 / 右栏 / 页脚 header sider aside footer maxWidth gap
Space 元素间距 direction gap wrap align justify
Divider 分隔线 orientation variant spacing
Input 输入框 icon suffix clearable onClear shortcut inputRef
Textarea 多行文本(Markdown / 简介) rows resize invalid
PasswordInput 密码输入(锁图标 + 显隐切换) icon size toggle
Notice 提示条 tone title action
Empty 空态 text action
Result 整块结果反馈 status title description actions
Toast 命令式轻提示 toast() toast.success() <Toaster />
Progress 进度条 percent size tone showLabel
Steps 纵向步骤条 items current
Modal 居中弹窗(原生 dialog) open onClose title footer size
Drawer 侧滑抽屉(原生 dialog) open onClose title placement width
Tooltip hover / 聚焦文字提示 content placement
Popover 点击展开的小面板 content placement
Pagination 分页 page total pageSize onChange
ScrollTop 回到顶部 visible targetId
Icon 图标(446 个字形) name

整页示例与深色主题

site/demo.html 是用这套组件拼出来的完整页面(三栏布局:资料卡 / 内容流 / 侧栏聚合), 只用了库里的组件 + 页面自己的布局样式:

  • http://127.0.0.1:5199/demo.html —— 浅色
  • http://127.0.0.1:5199/demo.html?scheme=dark —— 深色
  • 右上角按钮可以实时切换

深色主题由 <html data-scheme> 驱动(dark / auto / 不设置),组件零改动: 所有颜色走语义令牌,深色只覆盖 tokens.css 末尾的变量块。 新增组件时不要直接写 --su-gray-*,否则深色下会串色——规则见 DESIGN.md §7。

在应用里使用

库构建产物是标准 ESM 包,本地项目可以直接按路径引用:

# 在应用目录里
npm install ../stash-ui        # 或 "dependencies": { "@stash/ui": "file:../stash-ui" }
// main.tsx:样式只引一次(令牌 + 图标字体 + 组件样式都在这份文件里)
import '@stash/ui/style.css'

import { Button, Card, Grid } from '@stash/ui'

接入约定:

  • 只引一次 style.css,不要在页面里重复引令牌;
  • 应用自己的页面级样式(布局、页面专属类)写在应用里,不要加进组件库;
  • 需要改主题就覆盖 --su-* 变量,不要覆盖组件的内部类名。

浏览器与响应式

目标:桌面 Chrome / Edge / Safari / Firefox 最新两个版本;iOS Safari 16+、Android Chrome 110+。 库只用这些基线能力:CSS 变量、CSS Modules、:focus-visible、color-mix()(吸顶页头)、 <dialog>(Modal / Drawer)、dvh、-webkit-line-clamp(段落截断)。

响应式在组件内部处理,页面不用各写一套:

断点 组件 行为
≤1200px Layout 右栏堆到内容下方
≤1100px Layout / Grid 侧栏堆到内容上方;网格降为 2 列
≤720px Grid 网格降为 1 列

触屏相关的约定:

  • 交互只用 click,不依赖 hover;Tooltip 的 hover 样式包在 @media (hover: hover) 里;
  • 弹层宽度都有夹取(Modal 100vw - 32px、Popover min(320px, 100vw - 32px)、Drawer max-width: 100vw);
  • 表格、标签栏、分段控件在窄屏横向滚动,不做压缩;
  • 移动端建议用 sm(36) / md(44) 的图标按钮,xs(28) 留给桌面密集场景。

已实测:文档站与示例页在 390×844(手机)、820×1180(平板)、1440×900(桌面) 下无横向溢出。 尚未在真机(iOS Safari 软键盘、dvh、backdrop-filter)上复核。

约定(摘要,完整规则见 DESIGN.md)

  • 令牌先行:组件里不允许出现硬编码色值 / 字号 / 间距,一律走 --su-*;
  • CSS Modules:每个组件一个 Component.module.css,类名短(.root / .sm)且不外泄;
  • 组件不带数据:库里的组件只接收 props,不请求接口、不读全局状态;
  • 新增组件:按 ADDING-A-COMPONENT.md 的 12 步走, 并用 DESIGN.md §6 的清单验收(组件文件 → 导出 → 展示页 → 导航 → README 清单 → 契约 → typecheck/lint/build → 截图)。

与 stash 应用的关系

stash-web 目前仍是自己的全局 CSS(App.css),本次接入是分阶段的:

  1. 已具备(本仓库):独立组件库 + 展示站,可单独构建与预览;
  2. 下一步:在 stash-web 里以 file: 依赖接入,先把侧栏 / 顶栏 / 空态 / 按钮换成库组件;
  3. 再下一步:把 App.css 按组件拆分,页面只保留布局样式;同步删掉库中重复的样式定义。

对照表(迁移时查):

stash 现有类 组件库对应
.card-box Card
.section-empty Empty
.notice / .notice-error Notice(tone="info" / "error")
.header-info-tag / .item-tag / .service-pill Tag
.count-badge Badge
.search-bar Input(icon="search" + clearable)
.app-grid / .poster-grid / .article-grid / .ces-grid Grid
.back-to-top ScrollTop
.profile-avatar / .app-card-avatar Avatar

构建注意(踩过的坑)

  • 展示站要显式 import 主题 CSS:site/main.tsx 里直接引 src/theme/tokens.css 与 src/theme/iconfont.css。只靠 src/index.ts 的副作用引入时,生产构建会把 iconfont.css 摇掉(dev 正常、build 后图标全空)。
  • 库产物的 CSS 内联了图标字体:lib 模式下 Vite 会把 woff2 转成 data URI, 所以 dist/stash-ui.css 有 80KB+(gzip 56KB),换来「只引一个 CSS 文件」。 展示站走普通构建,字体仍是独立资源。
  • 比例封面不要放进 flex-direction: column 的父容器:纵向 flex 会把子项按内容高度压缩, 带 aspect-ratio 的封面会被裁掉一截(site/app.css 里纵向示例因此改用 grid)。
  • 组件尺寸对齐:Input 与 Button size="md" 都是 36px,并排成一行才是齐的。

为什么不直接用 Storybook

Storybook 能做的事我们这里只需要一小部分,而它要额外的依赖、端口和一套独立构建; 本项目是「本地小工具 + 单二进制发布」的配套,直接做成一个 Vite 站点更轻:

  • site/ 就是文档站,dev 下改组件即时生效,build 后是纯静态页;
  • 展示用例直接用真实组件,不存在「story 与页面不一致」;
  • 截图脚本(scripts/screenshot.mjs)已经把视觉回归的基础打好了, 将来要接 Playwright 截图对比也不用换工具。

后续可做

  • 暗色主题:令牌已经收敛,补一份 [data-theme='dark'] 覆盖即可;
  • Typography / Menu / Tabs / Modal 这类更上层的组件;
  • 展示站加「复制代码」与 props 表自动生成;
  • 视觉回归:在展示站上加 Playwright 截图对比(scripts/screenshot.mjs 已经是起点)。