commit 52296b336899a89f97f58de727ce130f69c6219a Author: stash Date: Wed Oct 28 20:44:18 2026 +0800 feat: stash-ui 0.1.0(40 个组件 + 文档站 + 主题机制) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9abc34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +dist-site/ +shots/ +*.local +.DS_Store + +.ref/ diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..ec79e6a --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }], + "react/jsx-key": "error" + }, + "ignorePatterns": ["dist", "dist-site", "shots"] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..298b996 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,17 @@ +# AGENTS.md + +本仓库是 **Stash UI**:一套独立的设计系统 + 组件库(React + CSS Modules + 设计令牌,零运行时依赖)。 + +改任何页面或组件之前: + +1. 读 [`skills/stash-ui/SKILL.md`](skills/stash-ui/SKILL.md) —— AI 使用入口(任务分流 + 硬约束 + 验证命令)。 +2. 需要细节时再读 [`DESIGN.md`](DESIGN.md)、[`docs/COMPONENTS.md`](docs/COMPONENTS.md)、 + [`docs/ADDING-A-COMPONENT.md`](docs/ADDING-A-COMPONENT.md)。 + +提交前必须通过: + +```bash +npm run typecheck && npm run lint && npm run build +``` + +视觉改动还要截图核对(`scripts/screenshot.mjs`,深浅色各看一次)。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2c3844d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ +# Claude Code 入口 + +规则与流程统一写在 [`AGENTS.md`](AGENTS.md) 和 [`skills/stash-ui/SKILL.md`](skills/stash-ui/SKILL.md), +这里只做指路,避免同一份规则维护两次。 diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..cb8492b --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,415 @@ +# Stash UI 设计系统 · 规格总纲 + +> 版本 0.1 · 适用 `@stash/ui` +> 本文件定义**所有组件共同遵守的规则**。新增组件、改动组件、评审组件都以下面这些条目为准。 + +四份文档的分工: + +| 文档 | 回答什么问题 | 什么时候看 | +| --- | --- | --- | +| **DESIGN.md**(本文) | 整体结构与硬性规则:分层、令牌、视觉、代码约定、验收标准 | 动手前看,评审时逐条对照 | +| [docs/COMPONENTS.md](docs/COMPONENTS.md) | 有哪些组件、各自的契约、缺什么组件、缺的时候该做什么 | 想知道「用哪个 / 有没有」时看 | +| [docs/ADDING-A-COMPONENT.md](docs/ADDING-A-COMPONENT.md) | 从零加一个组件的完整流程、模板、示例 | 确认要新增时照着做 | +| [docs/ALRIGHT-DECOMPOSITION.md](docs/ALRIGHT-DECOMPOSITION.md) | 参考站拆解:实测令牌、组件尺寸、交互节奏、暗色映射、保留差异 | 怀疑「这个值对不对」时查依据 | + +展示站(`site/`)是这三份文档的运行实例:**文档里写的规则,展示页上必须能直接看到**。 + +--- + +## 1. 三条硬约束 + +违反任意一条,组件不合并。 + +1. **只认令牌** + 组件 CSS 里的颜色、字号、行高、圆角、阴影、动效、间距,一律 `var(--su-*)`。 + 允许的字面量只有四类:`0`、`1px` 边框、`100%`、以及组件内声明的局部尺寸变量 `--xxx-size`(见 §5.3)。 +2. **组件不认识业务** + 不请求接口、不读路由、不 import 业务类型(`Category` / `entry` 之类)。 + 组件只接收 props,数据由页面喂给它。 +3. **每个组件都有展示页** + 展示页只能用真实组件渲染,不能写「演示专用版本」;新增组件必须同时加一节。 + +--- + +## 2. 分层结构 + +| 层 | 职责 | 现有组件 | 硬性规则 | +| --- | --- | --- | --- | +| **L1 原子** | 不可再分的小元素 | `Icon` `Badge` `Tag` `Divider` | 不负责布局,**不带外边距**,尺寸由内容或父级决定 | +| **L2 控件** | 接收输入 / 触发操作 | `Button` `IconButton` `Input` | 高度只用 `--su-control-height-*` 或 `--su-icon-button-*` | +| **L3 容器与布局** | 承载内容、排布 | `Card` `Grid` `Space` | 不假设内容形态;不写死宽度;不感知业务 | +| **L4 反馈** | 表达状态 | `Notice` `Empty` `ScrollTop` | 不阻塞、不弹层 | +| **页面级(不进库)** | 顶栏、侧栏、详情页、业务卡片 | — | 放在应用里,用 L1–L4 拼装 | + +依赖方向: + +```text +L4 ──→ L3 ──→ L1 + │ │ + └──────┴──→ L1 L2 ──→ L1 +``` + +- 只允许「上层依赖下层」,**禁止下层 import 上层**(例如 `Card` 里不 import `Empty`)。 +- 组件之间不共享 CSS。除 `Card` 这种明确的容器外,组件不假设自己处在什么容器里。 +- 业务组件(分区卡片、应用条目、海报卡……)属于页面层:**用库里组件拼,不进库**。 + +--- + +## 3. 令牌(唯一的视觉来源) + +完整定义见 [`src/theme/tokens.css`](src/theme/tokens.css)。前缀 `--su-` 是为了避免和宿主应用变量冲突。 +所有取值都来自参考站实测(见 [docs/ALRIGHT-DECOMPOSITION.md](docs/ALRIGHT-DECOMPOSITION.md)),不是拍脑袋定的。 + +### 3.1 颜色 + +| 令牌组 | 用途 | 不要用于 | +| --- | --- | --- | +| `--su-gray-1..8` | 灰阶底座(与参考站一致) | 直接当品牌色用 | +| `--su-bg-page` / `--su-bg-card` / `--su-bg-hover` / `--su-bg-subtle` / `--su-bg-light` | 页面底、卡片底、悬浮底、浅色块(标签)、浅色块(按钮) | 做强调色 | +| `--su-bg-notice` / `--su-bg-danger-soft` | 提示条底色 | 卡片底色 | +| `--su-text-main` / `-secondary` / `-sub` / `-muted` / `-placeholder` | 文字层级(主文 → 次级 → 说明 → 占位) | 互相替代 | +| `--su-border` / `--su-border-strong` / `--su-card-border` | 默认细边 / 需要看清的分隔 / 卡片描边(几乎不可见) | 画装饰线 | +| `--su-color-primary` (+`-hover` `-soft`) | 主操作、链接、选中 | 大块背景 | +| `--su-color-danger` (+`-hover` `-soft` `-border`) | 危险、错误 | 普通强调 | +| `--su-color-warning` (+`-soft` `-text`) / `--su-color-orange-dark` `-light` | 提醒、付费角标 | 错误态 | +| `--su-color-success` (+`-soft`) | 在线、成功 | 主操作 | +| `--su-gradient-verified` / `-hot` / `-paid` / `-red` | 认证角标 / 置顶 / 付费 / 热门 | 大面积背景 | + +### 3.2 间距 + +`--su-space-1..10` = 4 / 8 / 12 / 16 / 20 / 24 / 32 / 40。 +只用这些值;需要 2px、6px 这类视觉微调时才允许字面量(且仅限内边距/圆点尺寸)。 + +### 3.3 字号与字重 + +| 令牌 | 值 | 用途 | +| --- | --- | --- | +| `--su-font-size-xs` / `--su-line-height-xs` | 12 / 16 | 角标(`Badge` / `ItemBadge`) | +| `--su-font-size-sm` / `--su-line-height-sm` | 13 / 20 | 标签、辅助文字 | +| `--su-font-size-md` / `--su-line-height-md` | 14 / 22 | 控件文字(按钮、输入、表格) | +| `--su-font-size-base` / `--su-line-height-base` | 16 / 24 | 内容正文、卡片标题(`Paragraph` / `Title level=3`) | +| `--su-font-size-lg` / `--su-line-height-lg` | 18 / 26 | 区块标题、大图标 | +| `--su-font-size-xl` / `--su-line-height-xl` | 24 / 32 | 页面标题(`Title level=1`) | + +> 参考站的实际排版是 `13/20`、`14/22`、`16/24`、`18/26`、body `16/24`。 +> 组件库把「控件文字」定在 14、把「内容正文」定在 16:控件更紧凑,文章更好读。 + +字重只有三档:`normal 400`、`bold 500`(默认「加粗」)、`strong 700` +(**只给两类**:字形符号如认证角标的 ✓,以及 12px 的 `ItemBadge` 状态角标)。 +组件内**禁止**写 `font-weight: 600`。 + +### 3.4 圆角、阴影、控件尺寸、动效 + +| 令牌 | 值 | 用途 | +| --- | --- | --- | +| `--su-radius-xs` / `sm` / `md` / `lg` / `pill` | 4 / 6 / 12 / 24 / 999 | 标签 / 控件 / 卡片 / 大容器 / 胶囊 | +| `--su-shadow-card` | `0 6px 16px 0 rgb(27 26 49 / 1%)` | 卡片静止(参考站实测,极轻) | +| `--su-shadow-hover` | 同位置参数,3% | 卡片悬停 | +| `--su-shadow-pop` | `0 8px 24px rgb(27 26 49 / 12%)` | 浮层 | +| `--su-control-height-sm` / `md` | 32 / 36 | 按钮、输入框、未来的 Select | +| `--su-icon-button-xs` / `sm` / `md` | 28 / 36 / 44 | 圆形图标按钮(参考站 `.btn-icon`) | +| `--su-duration-fast` / `base` | 0.2s / 0.3s | 颜色变化 / 展开淡入 | +| `--su-ease-in-out` / `-out` / `-in` | ease-in-out / ease-out / ease-in | 默认用 `-in-out`(参考站同款) | +| `--su-skeleton-shine` | 浅色 50% 白 / 深色 8% 白 | 骨架屏扫光 | +| `--su-focus-ring` / `-offset` | `2px solid primary` / 2px | 键盘焦点环 | + +### 3.5 布局与阅读 + +| 令牌 | 值 | 用途 | +| --- | --- | --- | +| `--su-layout-width` | 1380px | 页面容器最大宽度(Layout `contained` 与页面共用) | +| `--su-header-height` | 60px | 应用型页头高度 | +| `--su-radius-cover` | 8px | 封面卡圆角(比卡片小一档) | +| `--su-reading-line-height` | 1.9 | 长文正文行高(`Paragraph leading="relaxed"`) | +| `--su-cover-scrim` | 黑 85% → 40% → 透明 | 封面标题的压暗层 | +| `--su-shadow-card-strong` | 2% 卡片阴影 | 需要更明显层次时(如首页分区卡) | +| `--su-z-header` / `--su-z-float` | 100 / 90 | 顶栏 / 浮动元素 | + +--- + +## 4. 视觉规则(拿不准就按这里来) + +### 4.1 尺寸与间距 + +- 控件高度只有两档;**并排出现时必须同档**(`Input` 与 `Button size="md"` 都是 36px)。 +- 组件**不带 margin**;元素间距交给 `Space` 或父容器。例外:`ScrollTop` 是浮动元素、`Divider` 自身带间距。 +- 卡片内边距:`sm 12` / `md 20` / `lg 24`,不要出现 18、22 这类值。 + +### 4.2 颜色的用法 + +- 文字三档:主文 `text-main`、次级 `text-sub`、说明 `text-muted`;不要自造灰。 +- **行式条目(列表行、作者行)不加底色、不加边框、不加阴影**——这是主题的核心观感,`Card` 不要套在行上。 +- 卡片用「1px 细边 + 极轻阴影」,不要用重阴影或彩色描边制造层级。 + +### 4.3 状态矩阵 + +每个交互组件都要覆盖下表;展示页也要逐个状态摆出来。 + +| 状态 | 实现 | 备注 | +| --- | --- | --- | +| 默认 | 令牌默认值 | — | +| hover | 主色/危险按钮 `opacity: .8`;次要按钮换 `gray-3` 底;图标按钮换 `gray-2` 底 + 文字加深 | 参考站同款,不做位移、不缩放 | +| active | 与 hover 同色,不再额外变形 | 主题本身没有按压态 | +| disabled | 换浅底 + 弱文字 + `cursor: not-allowed`(不是整体降透明) | 不改变布局 | +| loading | 同色 spinner 占用图标位 + 自动 disabled + `aria-busy` | 仅 `Button` / `IconButton` | +| focus-visible | 组件自己写 `outline: var(--su-focus-ring)` + `offset` | 参考站把焦点样式删了,我们**必须**补(WCAG) | +| 空 | `Empty` | 不复用 `Notice` | +| 错误 | `Notice tone="error"` | 带 `role="alert"` | + +### 4.4 动效 + +- 过渡时长统一用 `--su-duration-base`(0.3s)+ `--su-ease-in-out`,与参考站一致; + 只有 hover 的快速反馈可以用 `--su-duration-fast`(0.2s)。 +- 只做颜色 / 背景 / 边框 / 阴影 / 透明度,不做位移、缩放、旋转(加载 spinner 除外)。 +- 需要 `transition` 的:`Button`、`IconButton`、`Card hoverable`、`Input`、`ScrollTop`、`Tag`(如加交互)。 +- **每个带动效的组件都要有 `@media (prefers-reduced-motion: reduce)` 分支**(过渡置 none,spinner 放慢)。 + +### 4.5 响应式 + +断点只有三个档,**组件内部**处理,页面不要各写一套: + +| 断点 | 谁在用 | 行为 | +| --- | --- | --- | +| `1200px` | `Layout` | 右栏(aside)从并排变成堆到内容下方 | +| `1100px` | `Layout`、`Grid` | 侧栏堆到内容上方;`Grid` 降为 2 列 | +| `820px` | 页面(如示例页) | 顶部导航收进抽屉、左侧资料栏隐藏 | +| `720px` | `Grid` | 降为 1 列 | + +- 组件宽度默认 `100%`,由容器决定;不写死像素宽度(演示代码除外)。 +- **不靠隐藏做适配**:`Layout` 在窄屏下把侧栏/右栏堆叠,内容一律保留;页面如果确实要隐藏(如移动端的资料栏),由页面自己决定。 +- 高度用 `100dvh` 优先(`100vh` 兜底),地址栏收起时不会跳动。 +- 弹层宽度必须夹住:Modal `calc(100vw - 32px)`、Popover `min(320px, calc(100vw - 32px))`、Drawer `max-width: 100vw`。 +- 表格/标签栏/分段控件在窄屏横向滚动,不做压缩截断。 + +### 4.6 触屏 + +- 交互只用 `click`,不依赖 hover;`Tooltip` 的 hover 样式包在 `@media (hover: hover)` 里,触屏走 `:focus-within`。 +- 可点区域:`xs` 图标按钮 28×28 是**桌面密集场景**下限;移动端布局请用 `sm(36)` / `md(44)`。 +- 按钮类加 `touch-action: manipulation`,去掉双击缩放带来的点击延迟。 +- 原生控件(checkbox/radio/switch)都包在 `