Files
stash-ui/docs/COMPONENTS.md
T
CNWei 93bb5cbb28 feat(ui): Layout 新增 toolbar 插槽
页头与页身之间的一条真实占位带(搜索 / 筛选 / 操作),宽度跟随容器,
contained 时与侧栏 + 内容同宽,height=screen 下不参与内容区滚动。
用它替代「搜索框 fixed + contentOffset 让位」的写法。

- Layout.tsx / Layout.module.css:新增 toolbar prop 与样式
- 展示页 #layout 增加「页头下的工具条」示例
- COMPONENTS.md 补契约、与 contentOffset 的分工说明、changelog
2026-11-01 15:15:14 +08:00

706 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 组件目录与契约
> 配套:[DESIGN.md](../DESIGN.md)(规则)、[ADDING-A-COMPONENT.md](ADDING-A-COMPONENT.md)(新增流程)、
> [ALRIGHT-DECOMPOSITION.md](ALRIGHT-DECOMPOSITION.md)(参考站实测依据)
状态图例:✅ 已实现 · 🚧 开发中 · 🔜 规划 · ⛔ 不进库(用组合替代)
---
## 1. 组件地图
| 组件 | 层 | 状态 | 一句话 | 关键 props |
| --- | --- | --- | --- | --- |
| `Icon` | L1 | ✅ | 图标字体封装(446 个字形) | `name` |
| `Badge` | L1 | ✅ | 数字角标(18 高,12px) | `count` `tone` `max` |
| `ItemBadge` | L1 | ✅ | 状态角标:置顶 / 付费 / HOT | `tone` |
| `Tag` | L1 | ✅ | 分类与元信息标签(24 高,13px) | `tone` `variant` `shape` `icon` `dot` |
| `Divider` | L1 | ✅ | 分隔线 | `orientation` `variant` `spacing` |
| `Typography` | L1 | ✅ | 标题 / 文字 / 段落(Title / Text / Paragraph) | `level` `tone` `size` `lines` |
| `Button` | L2 | ✅ | 操作按钮(圆角 / 胶囊) | `variant` `size` `shape` `block` `loading` `icon` |
| `IconButton` | L2 | ✅ | 圆形图标按钮(28 / 36 / 44) | `label`(必填) `icon` `variant` `size` `spinning` |
| `Input` | L2 | ✅ | 组合式输入框 | `icon` `clearable` `onClear` `shortcut` `inputRef` |
| `Textarea` | L2 | ✅ | 多行文本(Markdown / 简介) | `rows` `resize` `invalid` |
| `PasswordInput` | L2 | ✅ | 密码输入(锁图标 + 显隐切换) | `icon` `size` `toggle` |
| `Switch` | L2 | ✅ | 即时生效的开关 | `checked` `onChange` `size` `label` |
| `Checkbox` | L2 | ✅ | 复选框(多选 / 确认) | `checked` `onChange` `label` |
| `RadioGroup` | L2 | ✅ | 互斥单选组 | `options` `value` `onChange` `direction` |
| `Select` | L2 | ✅ | 受控下拉选择 | `options` `value` `onChange` `placeholder` |
| `Segmented` | L2 | ✅ | 视图形态切换 | `options` `value` `onChange` `size` `block` |
| `Tabs` | L2 | ✅ | 页内分区切换 | `items` `activeKey` `onChange` `variant` |
| `Menu` | L2 | ✅ | 侧栏两级菜单(可折叠动画) | `items` `activeKey` `onSelect` `itemHeight` |
| `Breadcrumb` | L3 | ✅ | 路径导航 | `items` `separator` |
| `Layout` | L3 | ✅ | 页面骨架:页头 / 侧栏 / 内容 / 页脚 | `header` `sider` `aside` `maxWidth` `gap` `height` |
| `Card` | L3 | ✅ | 白色容器(1px 极浅描边) | `as` `padding` `hoverable` |
| `Cover` | L3 | ✅ | 比例封面卡(2:3 / 4:3 / 16:9 + 标题遮罩) | `ratio` `src` `caption` `lift` `onClick` |
| `Image` | L3 | ✅ | 带比例 / 圆角 / 失败降级的图片 | `ratio` `fit` `radius` `fallback` |
| `FormField` | L3 | ✅ | 表单字段排版(标签 + 控件 + 提示/错误) | `label` `hint` `error` `required` |
| `Grid` | L3 | ✅ | 卡片列表网格 | `columns` `tabletColumns` `mobileColumns` `gap` `as` |
| `Space` | L3 | ✅ | 一维间距容器 | `direction` `gap` `wrap` `align` `justify` |
| `Table` | L3 | ✅ | 只读数据表格 | `columns` `rows` `rowKey` `emptyText` |
| `Notice` | L4 | ✅ | 说明 / 错误提示条 | `tone` `title` `action` |
| `Empty` | L4 | ✅ | 空态 | `text` `action` |
| `Result` | L4 | ✅ | 整块结果反馈 | `status` `title` `description` `actions` |
| `Modal` | L4 | ✅ | 居中弹窗(原生 dialog) | `open` `onClose` `title` `footer` `size` |
| `Drawer` | L4 | ✅ | 侧滑抽屉(原生 dialog) | `open` `onClose` `title` `placement` `width` |
| `Tooltip` | L4 | ✅ | hover / 聚焦文字提示 | `content` `placement` |
| `Popover` | L4 | ✅ | 点击展开的小面板 | `content` `placement` |
| `Toast` | L4 | ✅ | 命令式轻提示 | `toast()` `toast.success()` `<Toaster />` |
| `Progress` | L4 | ✅ | 进度条 | `percent` `size` `tone` `showLabel` |
| `Steps` | L4 | ✅ | 纵向步骤条 | `items` `current` |
| `ScrollTop` | L4 | ✅ | 回到顶部 | `visible` `targetId` `label` |
| `Pagination` | L3 | ✅ | 分页 | `page` `total` `pageSize` `onChange` |
| `Skeleton` | L4 | ✅ | 骨架屏占位 | `variant` `lines` `width` `height` |
| `Spinner` | L4 | ✅ | 加载指示(行内 / 区块) | `size` `label` |
| `Avatar` | L1 | ✅ | 头像 / 封面占位(24 / 36 / 48 / 64) | `src` `fallback` `size` `shape` `tone` `verified` |
规划中的组件见 §5;明确不做的见 §6。
---
## 2. 用哪个?(决策表)
| 场景 | 用它 | 不要 |
| --- | --- | --- |
| 显示数量 | `Badge count={n}` | 用 `Tag` 包数字 |
| 显示「置顶 / 付费 / HOT」 | `ItemBadge tone="sticky \| paid \| hot"` | 用 `Tag` 拼渐变 |
| 分类、标签、元信息 | `Tag`(默认 neutral 浅底) | 自己写带底色的 span |
| 需要强调的分类标签 | `Tag variant="solid"`(实底白字) | 给浅底标签调色值 |
| 连接状态 | `Tag tone="success" dot` | 自绘圆点 |
| 列表为空、搜索无结果 | `Empty`(可带 `action`) | 复用 `Notice` |
| 接口失败、服务不可用 | `Notice tone="error"` | `Empty` |
| 一般说明、引导 | `Notice`(`tone="info"`) | 直接写 p |
| 一块内容 / 一个面板 | `Card`(`padding` 决定内边距) | 自己写白色 div |
| 列表行(应用行、作者行) | 直接 `Space` + 内容 | **套 `Card`**(行不需要底色/边框/阴影) |
| 多列排布 | `Grid` | 手写 `display:grid; repeat(...)` |
| 一维间距 | `Space` | 每个元素写 margin |
| 图标 | `<Icon name="…" />` | 写 `iconfont icon-xxx` 类名 |
| 主操作 | `Button`(一页一个 primary) | 多个 primary 并列 |
| 登录 / 筛选这类主操作 | `Button shape="pill"` | 圆角按钮凑合 |
| 只有图标的操作 | `IconButton label="…"` | `<button><Icon /></button>` |
| 密码输入(登录 / 改密) | `PasswordInput`(锁图标 + 显隐切换) | 自己拼 `Input` + 眼睛按钮(容易漏掉切换) |
| 分隔内容块 | `Divider` | `border-top` 手写 |
| 长内容回到顶部 | `ScrollTop` | 自己写 fixed 按钮 |
---
## 3. 现有组件契约
每个组件按固定结构写:用途 / 不该用在哪 / 解剖 / Props / 规则 / 展示页。新增组件照抄这个结构(模板见 §8)。
### 3.1 Icon ✅
- **用途**:把图标字体的字形封装成组件,颜色与字号继承当前文字。
- **不该用在哪**:需要多色图标、需要 SVG 描边可控的场景。
- **解剖**:单个 `<i class="su-iconfont su-icon-{name}">`,`aria-hidden="true"`。
- **Props**:`name: IconName`(13 个字形)+ 原生 `<i>` 属性。
- **规则**:图标一律视为装饰;字号在父级用 `font-size` 控制;新增字形走 §7 流程。
- **展示页**:`#icons`
### 3.2 Badge ✅
- **用途**:数量角标(1、18、99+)。
- **不该用在哪**:状态文字(用 `ItemBadge` 或 `Tag`)。
- **解剖**:`inline-flex` 胶囊,18px 高、12px 字,等宽数字。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `count` | `number` | 必填 | 数字 |
| `tone` | `'danger' \| 'primary' \| 'neutral'` | `'danger'` | 配色 |
| `max` | `number` | `99` | 超过显示 `N+`;`0` 表示不截断 |
- **规则**:不负责定位;压在标题右上角由使用方用 `className` 绝对定位。
- **展示页**:`#marks`
### 3.3 ItemBadge ✅
- **用途**:标题前的状态角标——「置顶 / 付费 / HOT / 草稿」。
- **不该用在哪**:数量(`Badge`)、分类(`Tag`)。
- **解剖**:18px 高、radius 4、12px 加粗白字、红/橙渐变底(参考站 `.item-badge`)。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `tone` | `'hot' \| 'sticky' \| 'paid' \| 'neutral'` | `'hot'` | hot/sticky 红渐变,paid 橙渐变,neutral 灰底 |
| `children` | `ReactNode` | 必填 | 角标文字(建议 2–4 字) |
- **规则**:只做状态提示,不可点击;和标题并排时用 `Space gap="sm"`,不要加 margin。
- **展示页**:`#marks`
### 3.4 Tag ✅
- **用途**:分类、标签、元信息。
- **不该用在哪**:数量(`Badge`)、状态角标(`ItemBadge`)、可点击筛选(暂无交互态)。
- **解剖**:24px 高胶囊、13px 字,左侧可带图标或状态点;实底变体用于强调。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `tone` | `'neutral' \| 'primary' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | 语义配色 |
| `variant` | `'soft' \| 'solid'` | `'soft'` | 浅底 / 实底白字 |
| `shape` | `'pill' \| 'rounded'` | `'pill'` | 胶囊 / 小方块(元信息前缀用) |
| `icon` | `ReactNode` | — | 前置图标(如分类图标) |
| `dot` | `boolean` | `false` | 前置 6px 状态点 |
- **规则**:标签是内容宽度;放进纵向 flex 时用 `align-items: flex-start`,否则会被拉满。
- **展示页**:`#marks`
### 3.5 Divider ✅
- **用途**:横向分隔内容块、纵向分隔并排元素。
- **解剖**:`role="separator"` 的普通 div,靠 border 画线。
- **Props**:`orientation: 'horizontal' | 'vertical'`、`variant: 'solid' | 'dashed'`、`spacing: 'sm' | 'md'`。
- **规则**:横向自带上下外边距(唯一带 margin 的基础组件),纵向用于 `Space` 内部。
- **展示页**:`#layouts`
### 3.6 Button ✅
- **用途**:页面操作。
- **不该用在哪**:只有图标(`IconButton`)、纯跳转链接(`<a>`)。
- **解剖**:`[icon] [spinner] [label]`,高度 32 / 36,左右内边距 20。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `variant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost' \| 'danger'` | `'primary'` | 语义 |
| `size` | `'sm' \| 'md'` | `'md'` | 32 / 36px |
| `shape` | `'rounded' \| 'pill'` | `'rounded'` | 6px 圆角 / 胶囊 |
| `block` | `boolean` | `false` | 占满一行 |
| `loading` | `boolean` | `false` | 自动禁用 + `aria-busy` |
| `spinning` | `boolean` | `false` | 图标原地旋转(不换转圈),用于刷新类动作 |
| `icon` | `ReactNode` | — | 文字左侧图标 |
| `…rest` | `ButtonHTMLAttributes` | — | `type` 默认 `"button"` |
- **规则**:一屏一个 `primary`;hover 是 `opacity .8`(参考站同款),不要改背景色;禁用是浅底弱字。
- **展示页**:`#buttons`
### 3.7 IconButton ✅
- **用途**:圆形图标按钮(顶栏入口、更多、刷新、关闭)。
- **解剖**:正圆 + 居中图标;`spinning` 时图标旋转。
- **Props**:`label`(**必填**,进 `aria-label` 与 `title`)、`icon`(必填)、
`variant: 'ghost' | 'soft'`、`size: 'xs' | 'sm' | 'md'`(28 / 36 / 44,图标 16 / 20 / 24)、`spinning`、`…rest`。
- **规则**:没有 `label` 不允许合并;`spinning` 只用于「操作进行中」,不做装饰动画。
- **展示页**:`#buttons`
### 3.8 Input ✅
- **用途**:搜索框、筛选输入、表单单行输入。
- **不该用在哪**:多行文本(未来 `Textarea`)、下拉选择(未来 `Select`)。
- **解剖**:`[icon] [input] [suffix] [shortcut] [clear]`,外层容器负责边框与高度(36px、`1px solid gray-3`)。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `icon` | `IconName` | — | 左侧图标 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 32 / 36 / 44px |
| `suffix` | `ReactNode` | — | 右侧插槽:图标按钮、单位等;排在清除按钮之前 |
| `clearable` | `boolean` | `false` | 有值且传了 `onClear` 才显示清除 |
| `onClear` | `() => void` | — | 清除回调 |
| `shortcut` | `string` | — | 右侧提示(如 `⌘K`),仅展示 |
| `inputRef` | `Ref<HTMLInputElement>` | — | 拿真实 input 做聚焦 |
| `className` / `style` | — | — | **作用在外层容器** |
| `…rest` | `InputHTMLAttributes` | — | `onChange` 是原生事件 |
- **规则**:与 `Button size="md"` 同高 36;focus 只把边框换成主色(**不再叠 outline**:文本框点击时也会命中 `:focus-visible`,叠环会出现「双框」,2026-10-28 修);`type="password"` 时隐藏 Edge 原生显隐按钮(`::-ms-reveal`),否则会和 `PasswordInput` 的眼睛叠成两个。
- **展示页**:`#inputs`
### 3.9 Card ✅
- **用途**:白色容器(面板、卡片、详情块)。
- **不该用在哪**:列表行、纯排布容器。
- **解剖**:`1px solid gray-1`(几乎不可见)+ radius 12 + `shadow-card`(1%),内边距由 `padding` 决定。
- **Props**:`as: 'div' | 'section' | 'article' | 'aside'`、`padding: 'none' | 'sm' | 'md' | 'lg'`(0 / 12 / 20 / 24)、`hoverable`、`className`、`style`。
- **规则**:卡片不提供标题栏;「色条 + 标题 + 角标 + 更多」由页面组合;`hoverable` 只加深阴影,不做位移。
- **展示页**:`#cards`
### 3.10 Grid ✅
- **用途**:卡片列表的多列排布。
- **不该用在哪**:一维间距(`Space`)、页面整体骨架(CSS layout)。
- **解剖**:`display:grid`,列数走 CSS 变量,断点内建:`>1100`、`≤1100`、`≤720`。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `columns` | `number` | `4` | 宽屏列数 |
| `tabletColumns` | `number` | `columns / 2`(至少 2) | ≤1100px |
| `mobileColumns` | `number` | `1` | ≤720px |
| `gap` | `'sm' \| 'md' \| 'lg'` | `'md'` | 8 / 12 / 16 |
| `as` | `'div' \| 'ul'` | `'div'` | 列表语义 |
- **规则**:不要在父级用 `flex-direction: column` 包住含比例图片的 Grid(会被压扁,见 DESIGN §9)。
- **展示页**:`#layouts`
### 3.11 Space ✅
- **用途**:一组元素之间的一维间距。
- **解剖**:`display:flex` + `gap`,方向/对齐/间距都用语义档位。
- **Props**:`direction: 'row' | 'column'`、`gap: 'xs' | 'sm' | 'md' | 'lg'`(4 / 8 / 12 / 16)、`wrap`、`align: 'start' | 'center' | 'end' | 'stretch'`、`justify: 'start' | 'center' | 'end' | 'between'`。
- **规则**:间距只用档位,不传像素;纵向排列 + 左对齐要给 `align="start"`。
- **展示页**:`#layouts`
### 3.12 Notice ✅
- **用途**:页面级说明与错误提示。
- **不该用在哪**:表单字段校验、浮层提示(未来 `Toast`)。
- **解剖**:标题 + 正文 + 可选操作按钮;错误态 `role="alert"`。
- **Props**:`tone: 'info' | 'error'`、`title`(必填)、`children`、`action: { label, onClick }`。
- **规则**:错误态只用于「确实失败」;同一区域不要堆两条以上提示。
- **展示页**:`#feedback`
### 3.13 Empty ✅
- **用途**:列表为空、搜索无结果、内容未就绪。
- **解剖**:一句说明 + 可选操作区。
- **Props**:`text`(默认「暂无内容」)、`action`。
- **规则**:空态不要用红色/错误色;能给出下一步操作时一定给。
- **展示页**:`#feedback`
### 3.14 ScrollTop ✅
- **用途**:长内容回到顶部。
- **解剖**:固定在视口右侧中部(`top:50%`、`right:24`)的 42px 圆钮,`visible` 控制淡入淡出。
- **Props**:`visible`(必填)、`targetId`(滚动容器 id;不传滚窗口)、`label`(默认「回到顶部」)、`offset`(距右侧 px,默认 24)。
- **规则**:滚动容器在页面里滚动时必须传 `targetId`;隐藏时 `tabIndex=-1`。
- **展示页**:`#scrolltop`
### 3.15 Avatar ✅
- **用途**:头像、应用图标、封面占位。
- **解剖**:裁切层(图片 / 首字) + 外置认证角标,两者分层,角标不会被圆角切掉。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `src` | `string` | — | 图片;加载失败自动降级 |
| `alt` | `string` | `''` | 图片替代文本 |
| `fallback` | `string` | `'•'` | 无图时的字符(通常取首字) |
| `icon` | `ReactNode` | — | 无图时的图标(优先于 fallback),用于缺省头像 |
| `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | 24 / 36 / 48 / 64px |
| `shape` | `'circle' \| 'square'` | `'circle'` | 人/应用用圆,图标封面用方 |
| `tone` | `'neutral' \| 'primary'` | `'neutral'` | 无图底色:浅灰 / 品牌浅渐变(资料卡) |
| `verified` | `boolean` | `false` | 右下角认证角标(12 / 16 / 20px) |
- **规则**:`fallback` 传一个字符;资料卡用 `xl` + `tone="primary"`,列表里用 `lg`,紧凑行用 `sm`;不要给头像加边框。
- **展示页**:`#avatars`
### 3.16 Modal ✅
- **用途**:需要打断流程的确认 / 表单 / 说明。
- **不该用在哪**:轻量反馈(等 `Toast`)、页面内的展开收起(用条件渲染 + 动画)。
- **解剖**:原生 `<dialog>`(焦点陷阱、Esc、背景 inert 由浏览器提供)+ 遮罩 + 标题栏 + 正文 + 底部操作区。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `open` | `boolean` | 必填 | 受控开关 |
| `onClose` | `() => void` | 必填 | Esc / 遮罩 / 关闭按钮都会调用 |
| `title` | `ReactNode` | 必填 | 标题,同时作为 `aria-labelledby` |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 300 / 460 / 640 |
| `closeOnOverlay` | `boolean` | `true` | 点遮罩是否关闭 |
| `footer` | `ReactNode` | — | 底部按钮区 |
- **规则**:打开时锁背景滚动;危险操作必须二次确认;底部按钮顺序是「取消在左、主操作在右」。
- **展示页**:`#modal`
### 3.17 Pagination ✅
- **用途**:列表/搜索结果的翻页。
- **解剖**:上一页 + 页码(超长时省略号折叠)+ 下一页;32×32、radius 4、当前页主色实底。
- **Props**:`page`(从 1 开始)、`total`、`pageSize`(默认 10)、`onChange(page)`、`siblings`(当前页左右各几个,默认 1)。
- **规则**:首尾页固定;边界上「上一页/下一页」禁用;总数文案由页面自己写(用 `Space justify="between"`)。
- **展示页**:`#pagination`
### 3.18 Typography(Title / Text / Paragraph)✅
- **用途**:页面与内容排版——标题层级、文字层级、正文段落。
- **不该用在哪**:控件内部的文字(按钮/输入自己管),不要为了「加个字号」而套一层 `Text`。
- **解剖**:三个组件共用一个样式模块;`Title` 按 `level` 输出真实 `h1–h4`,`Text` 是 `span`/`p`/`div`,`Paragraph` 用 `-webkit-line-clamp` 做行数截断。
- **Props**:
| 组件 | prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `Title` | `level` | `1 \| 2 \| 3 \| 4` | `2` | 24/32、18/26、16/24、14/22 |
| `Title` | `weight` | `'normal' \| 'medium' \| 'semibold' \| 'strong'` | `'medium'` | 400 / 500 / 600 / 700;参考站的 600 用 `semibold` |
| `Title` | `as` | `'h1' \| 'h2' \| 'h3' \| 'h4' \| 'div'` | 按 level | 换语义标签 |
| `Text` | `tone` | `'main' \| 'secondary' \| 'sub' \| 'muted'` | `'main'` | 文字层级 |
| `Text` | `size` | `'xs' \| 'sm' \| 'md' \| 'base' \| 'lg'` | `'md'` | 12 / 13 / 14 / 16 / 18 |
| `Text` | `strong` / `truncate` | `boolean` | `false` | 500 字重 / 单行省略 |
| `Paragraph` | `tone` | `'main' \| 'secondary' \| 'sub'` | `'sub'` | 文字层级 |
| `Paragraph` | `size` | `'sm' \| 'md' \| 'base'` | `'base'` | 13 / 14 / 16,长文正文用 md |
| `Paragraph` | `leading` | `'base' \| 'relaxed'` | `'base'` | relaxed = 1.9 行高(阅读型) |
| `Paragraph` | `lines` | `number` | — | 最多几行,超出省略 |
- **规则**:组件不带外边距,段落间距交给 `Space`;不要在页面里另写 `font-size`。
- **展示页**:`#typography`
### 3.19 Skeleton ✅
- **用途**:内容加载前的占位(列表首屏、刷新、分页加载)。
- **不该用在哪**:按钮内的 loading(`Button` 自带 spinner)、整页空态(`Empty`)。
- **解剖**:`text`(多行文本条,最后一行收短)/ `rect`(封面)/ `circle`(头像),占位块上有一层扫光。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `variant` | `'text' \| 'rect' \| 'circle'` | `'text'` | 形状 |
| `lines` | `number` | `1` | 仅 text:行数 |
| `width` / `height` | `number \| string` | — | rect 默认 100% / 120px,circle 默认 40 |
| `className` / `style` | — | — | 外边距等布局微调 |
- **规则**:形状必须和真实内容一致(否则加载完成后跳动);整块区域标 `aria-busy="true"`;骨架自身 `aria-hidden`;`prefers-reduced-motion` 下停止扫光。
- **展示页**:`#skeleton`
### 3.20 Spinner ✅
- **用途**:行内或区块的加载指示(同步状态、面板刷新)。
- **不该用在哪**:按钮内(`Button loading` 自带)、文字占位(`Skeleton`)。
- **解剖**:2px 圆环,颜色取主色,`role="status"` + `aria-label`。
- **Props**:`size: 'sm' | 'md'`(14 / 20)、`label`(默认「加载中」)、`className`。
- **规则**:颜色继承 `currentColor`(默认主色,可被外层覆盖);`prefers-reduced-motion` 下降到 2.4s。
- **展示页**:`#spinner`
### 3.21 Switch ✅
- **用途**:设置项的**即时**开关。
- **不该用在哪**:需要点「保存」才生效的表单(用 `Checkbox` + 提交按钮)。
- **解剖**:原生 `button` + `role="switch"`,轨道 + 白色圆点;空格/回车可切换。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `checked` | `boolean` | 必填 | 受控状态 |
| `onChange` | `(checked: boolean) => void` | 必填 | 切换回调 |
| `size` | `'sm' \| 'md'` | `'md'` | 32×18 / 40×22 |
| `disabled` | `boolean` | `false` | 禁用 |
| `label` | `string` | — | 无障碍名称:旁边没有文字时必填 |
- **规则**:旁边配文字时用 `Text` + `Space justify="between"`,不要自己做一套行布局。
- **展示页**:`#switch`
### 3.22 Checkbox ✅
- **用途**:多选、需要确认的选项。
- **解剖**:原生 `input[type=checkbox]`(视觉隐藏)+ 自绘 16px 方块 + label 文本,点击文字也能勾选。
- **Props**:`checked`、`onChange(checked)`、`label`(必填,同时作为无障碍名称)、`disabled`。
- **规则**:多选组用 `Space direction="column"` 排列;禁用态只降对比,不隐藏。
- **展示页**:`#checkbox`
### 3.23 RadioGroup ✅
- **用途**:互斥选项组。
- **解剖**:原生 `input[type=radio]`(隐藏)+ 自绘圆点;同组共享 `name`,方向键在组内移动。
- **Props**:`options: { value, label, disabled? }[]`、`value`、`onChange(value)`、`direction: 'row' | 'column'`、`disabled`。
- **规则**:2–3 个短选项用 row;设置类用 column。单个开/关用 `Switch`,多选用 `Checkbox`。
- **展示页**:`#radio`
### 3.24 Segmented ✅
- **用途**:视图形态切换(网格/列表、全部/收藏)。
- **Props**:`options: { value, label, icon? }[]`、`value`、`onChange(value)`、`size: 'sm' | 'md'`、`block`。
- **规则**:只切展示、不切内容分区(后者用 `Tabs`);选中项白底 + 轻阴影。
- **展示页**:`#segmented`
### 3.25 Select ✅
- **用途**:受控的单值下拉选择。
- **解剖**:触发器是原生 button(`aria-haspopup="listbox"`),展开为 `role="listbox"`;点外部 / Esc 关闭,↑↓ 移动、Enter 选择。
- **Props**:`options`、`value`、`onChange(value)`、`placeholder`、`disabled`。
- **规则**:多选与搜索不在范围内(用 `Input` + 列表组合);高度与 `Input` 一致(36)。
- **展示页**:`#select`
### 3.26 Breadcrumb ✅
- **用途**:详情页路径导航。
- **Props**:`items: { label, href? }[]`、`separator`(默认 `/`)、`onNavigate(href)`(接管点击,用于 SPA 路由;传了就阻止默认跳转)。
- **规则**:最后一项忽略 `href`,并标 `aria-current="page"`;不传 `onNavigate` 时走普通链接(整页跳转)。
- **展示页**:`#breadcrumb`
### 3.27 Tabs ✅
- **用途**:页内内容分区切换。
- **Props**:`items: { key, label, disabled? }[]`、`activeKey`、`onChange(key)`、`variant: 'line' | 'pill'`。
- **规则**:只渲染标签栏,面板由页面用 `role="tabpanel"` 渲染;← → 移动、Home/End 跳首尾。
- **展示页**:`#tabs`
### 3.28 Menu ✅
- **用途**:侧栏菜单(一层折叠 + 两级)。
- **Props**:`items: { key, label, icon?, children? }[]`、`activeKey`、`onSelect(key)`、`defaultOpenKeys`、`itemHeight`(一级行高,默认 36)。
- **规则**:数据无关,业务树由页面转换;含 `activeKey` 的父级默认展开,父子均用 `aria-expanded` / `aria-current` 标注;折叠箭头是 CSS chevron(视觉回归:从字形改为边框绘制)。
- **展示页**:`#menu`
### 3.29 Toast(toast / Toaster)✅
- **用途**:操作反馈,几秒后自动消失。
- **解剖**:模块级 store + `useSyncExternalStore`;`Toaster` 挂在应用根节点,只挂一次。
- **API**:`toast(message, { tone?, duration? })`、`toast.success / .error`、`dismissToast(id)`。
- **规则**:命令式调用;页面内常驻提示用 `Notice`,整块状态用 `Result`;文案一句话,不放按钮。
- **展示页**:`#toast`
### 3.30 Table ✅
- **用途**:数据表格(只读)。
- **Props**:`columns: { key, title, width?, align? }[]`、`rows: Record<string, ReactNode>[]`、`rowKey`(默认 `id`)、`emptyText`。
- **规则**:排序 / 筛选 / 分页由页面组合,组件不内置交互;空数据渲染 `emptyText`。
- **展示页**:`#table`
### 3.31 Result ✅
- **用途**:整块的完成 / 失败 / 警示 / 说明反馈。
- **Props**:`status: 'success' | 'error' | 'warning' | 'info'`、`title`、`description`、`actions`。
- **规则**:只用于整页或整块状态;行内提示用 `Notice`,轻量反馈用 `toast`。
- **展示页**:`#result`
### 3.32 Progress ✅
- **用途**:任务 / 存储进度。
- **Props**:`percent`(夹在 0–100)、`size: 'sm' | 'md'`(4 / 8px)、`tone`、`showLabel`。
- **规则**:纯展示;带 `role="progressbar"` 与 `aria-valuenow`。
- **展示页**:`#progress`
### 3.33 Steps ✅
- **用途**:有顺序的流程(发布、导入向导)。
- **Props**:`items: { title, description? }[]`、`current`(从 0 开始)。
- **规则**:`index < current` 视为已完成;当前项加粗 + 主色描边。
- **展示页**:`#steps`
### 3.34 Drawer ✅
- **用途**:侧滑面板(筛选、设置、详情),不打断主流程。
- **解剖**:与 `Modal` 同源(原生 `<dialog>`),区别是贴边、全高、滑入动画。
- **Props**:`open`、`onClose`、`title`、`placement: 'right' | 'left'`、`width`(默认 360)、`footer`。
- **规则**:需要打断流程的确认用 `Modal`;打开时锁背景滚动。
- **展示页**:`#drawer`
### 3.35 Tooltip ✅
- **用途**:hover / 聚焦时的一行文字说明。
- **Props**:`content`、`placement: 'top' | 'bottom'`、`children`(建议可聚焦元素)。
- **规则**:纯 CSS 定位,不做自动翻转;交互内容用 `Popover`。
- **展示页**:`#tooltip`
### 3.36 Popover ✅
- **用途**:点击展开的小面板(说明、快捷操作)。
- **Props**:`content`、`children`、`placement: 'bottom-start' | 'bottom-end'`。
- **规则**:点外部 / Esc 关闭;内容复杂或需要滚动时改用 `Drawer` / `Modal`。
- **展示页**:`#tooltip`
### 3.37 Layout ✅
- **用途**:页面外壳——页头 / 侧栏 / 内容 / 右栏 / 页脚的区域划分与最大宽度。
- **不该用在哪**:应用内的小区块(用 `Card` + `Grid`);业务导航内容(由页面作为 slot 传入)。
- **解剖**:`flex` 纵向三行(header / body / footer),body 内是 `sider + main + aside`;main 里再套一层限宽容器(`maxWidth`)。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `header` | `ReactNode` | — | 页头,默认吸顶(半透明 + 模糊) |
| `toolbar` | `ReactNode` | — | 页头与页身之间的工具条(搜索 / 筛选 / 操作带),宽度随容器,不参与内容区滚动 |
| `sider` | `ReactNode` | — | 左侧栏,固定宽度 |
| `aside` | `ReactNode` | — | 右侧栏,固定宽度(目录 / 聚合 / 广告位) |
| `footer` | `ReactNode` | — | 页脚 |
| `children` | `ReactNode` | 必填 | 内容区 |
| `maxWidth` | `number \| 'full'` | `1280` | 内容最大宽度 |
| `siderWidth` | `number` | `260` | 左侧栏宽度 |
| `asideWidth` | `number` | `300` | 右侧栏宽度 |
| `stickyHeader` | `boolean` | `true` | 页头是否吸顶 |
| `contained` | `boolean` | `false` | 页身套一层居中容器(**页头始终通栏**,页头内容自己限宽) |
| `gap` | `number` | `0` | 侧栏 / 内容 / 右栏之间的间距(px) |
| `contentPadding` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | 内容区留白:0 / 12 / 20 / 24 |
| `contentOffset` | `number` | `0` | 内容区顶部偏移(px),给页头下的悬浮搜索/工具条留位 |
| `gutter` | `number` | `0` | contained 容器的左右留白(px) |
| `headerHeight` | `number` | `60` | 页头最小高度(px) |
| `headerVariant` | `'blur' \| 'solid'` | `'blur'` | 半透明模糊 / 实底 + 下边框 |
| `siderBordered` | `boolean` | `true` | 侧栏右侧分隔线 |
| `height` | `'auto' \| 'screen'` | `'auto'` | screen:锁视口,适合应用型界面 |
| `scrollable` | `boolean` | `false` | 内容区内部滚动(配 `height="screen"`) |
| `hideScrollbar` | `boolean` | `false` | 内部滚动时隐藏滚动条(仍可滚轮/键盘滚动) |
| `contentId` | `string` | — | 内容区 id(供 ScrollTop 等按 id 滚动) |
- **规则**:Layout 不写业务内容,也不带背景装饰(需要渐变/光晕时由页面自己放绝对定位层);响应式在组件内处理——≤1200px 右栏堆到内容下方、≤1100px 侧栏堆到内容上方,**内容都不隐藏**。
- **`toolbar` 与 `contentOffset` 的分工**:`toolbar` 是**真实占位**的一层,搜索框照常参与文档流,侧栏与内容的顶边天然对齐(推荐);`contentOffset` 只用于「浮在页面上、不能占位」的场景(悬浮条、绝对定位的返回按钮),不要把两者叠加使用。
- **常用配方**:顶栏+内容(首页)、左栏+内容(管理页)、内容+右栏(文章)、三栏(工作台)、窄内容(设置/阅读),展示页用 `Segmented` 切换。
- **展示页**:`#layout`
### 3.38 Cover ✅
- **用途**:比例封面卡——海报(2:3)、图文(4:3)、宽图(16:9)统一用「图片 + 底部压暗层 + 标题」。
- **不该用在哪**:需要叠加角标 / 操作按钮的封面(用 `Card padding="none"` 里放 `Cover` + 角标);纯头像用 `Avatar`。
- **解剖**:`aspect-ratio` 容器 + `object-fit: cover` 图片 + `--su-cover-scrim` 渐变遮罩;传 `onClick` 时整块变成 `<button>`,否则是 `<figure>`。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `src` | `string` | — | 图片地址;不传时显示占位字符 |
| `alt` | `string` | 必填 | 图片说明,同时作为按钮 title |
| `caption` | `ReactNode` | — | 底部标题(最多 2 行) |
| `ratio` | `'poster' \| 'article' \| 'wide' \| number` | `'article'` | 2/3、4/3、16/9,或数字宽高比 |
| `radius` | `'cover' \| 'none'` | `'cover'` | 8px / 贴边(配合 `Card padding="none"`) |
| `lift` | `boolean` | `true` | 悬停整块上浮 2px(参考站行为) |
| `hoverZoom` | `boolean` | `false` | 悬停放大图片 1.04(与 lift 二选一) |
| `onClick` | `() => void` | — | 传了就把整块变成按钮 |
- **规则**:封面比例由 `ratio` 定,不要在页面里再写 `aspect-ratio`;标题必须压暗层,否则白字会丢。
- **展示页**:`#cover`
### 3.39 Image ✅
- **用途**:正文配图、相册、缩略图——带比例、圆角、加载失败降级。
- **不该用在哪**:带标题遮罩的封面卡(用 `Cover`);头像(用 `Avatar`)。
- **Props**:`src`、`alt`、`ratio: 'square' | 'video' | 'wide' | number`(默认 `video`)、
`fit: 'cover' | 'contain'`(默认 `cover`)、`radius: 'none' | 'sm' | 'md' | 'cover'`(默认 `sm`)、`fallback`。
- **规则**:必须给 `alt`;失败自动退化为占位块,不出现破图;懒加载默认开启。
- **展示页**:`#image`
### 3.40 FormField ✅
- **用途**:表单字段的排版——标签 + 控件 + 提示 / 错误。
- **不该用在哪**:**不做校验**(校验留在页面);不是表单容器(多个字段用 `Space` 排列)。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `label` | `ReactNode` | 必填 | 字段名(渲染为 `<label>`) |
| `htmlFor` | `string` | — | 关联控件 id |
| `hint` | `ReactNode` | — | 说明文字,有错误时隐藏 |
| `error` | `ReactNode` | — | 错误文案,出现时标红并 `role="alert"` |
| `required` | `boolean` | `false` | 显示红色 `*` |
| `children` | `ReactNode` | 必填 | 控件(`Input` / `Select` / `Switch`…) |
- **规则**:错误用文案而不是只换颜色(颜色不足以表达);`defaultValue` / `value` 等仍由控件自己管。
- **展示页**:`#inputs`
### 3.41 Textarea ✅
- **用途**:多行文本——Markdown 正文、简介、备注。
- **不该用在哪**:单行短文本(用 `Input`);需要工具栏的富文本(页面自己拼)。
- **Props**:透传 `<textarea>`,另加 `rows`(默认 4)、`resize: 'none' | 'vertical'`(默认 `vertical`)、`invalid`(红边框 + `aria-invalid`)。
- **规则**:焦点只用边框变色,不叠 outline(与 `Input` 一致);`invalid` 要和错误文案一起用。
- **展示页**:`#inputs`
### 3.42 PasswordInput ✅
- **用途**:登录、改密这类密码输入;固定「锁图标 + 眼睛按钮」,显隐切换与无障碍名称由组件负责。
- **不该用在哪**:普通文本输入(用 `Input`);需要自己控制 `type` 的场景(直接用 `Input`)。
- **解剖**:`Input` + `suffix` 插槽里的 `IconButton`——眼睛在输入框**内部**右端,不是并排在外;`type` 在 `password` / `text` 间切换,Edge 原生显隐按钮已隐藏。
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `icon` | `IconName \| null` | `'lock_line'` | 左侧图标;`null` 去掉 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 32 / 36 / 44,与 `Input` 一致 |
| `toggle` | `boolean` | `true` | 是否显示右侧显隐按钮 |
| `className` / `style` | `string` / `CSSProperties` | — | 作用在最外层 |
| `…rest` | `InputHTMLAttributes` | — | 透传原生属性(`type` / `size` / `icon` 由组件接管) |
- **规则**:`onChange` 是原生事件(与 `Input` 一致),Enter 提交用 `onKeyDown`;切换按钮必须带 `aria-pressed` 与「显示/隐藏密码」名称,焦点环不得移除。
- **展示页**:`#inputs`
---
## 4. 组合模式(不进库,但必须照这个拼)
| 模式 | 由什么拼 | 展示页 |
| --- | --- | --- |
| 分区卡片(首页一块) | `Card` + 色条 span + `ItemBadge` + 标题 + `Badge` + `IconButton` + `Grid` | `#composition` |
| 应用行 | `Avatar` + 两行文字(名称 / 说明),无容器 | `#avatars`、`#composition` |
| 筛选工具条 | `Card padding="sm"` + `Space justify="between"` + `Input` + `Tag` + `Button shape="pill"` | `#composition` |
| 海报卡 | `Card padding="none"` + 比例封面 + 标题 + `Tag` | `#composition` |
| 顶栏信息条 | `Space` + `Tag shape="rounded"` + 文字 | `#marks` |
| 标题行 | `ItemBadge` + 标题 + `Tag` | `#marks` |
| 图片悬浮(参考站 `.custom-hover`) | 图片容器 `overflow:hidden` + hover 时 `scale(1.1)` + 65% 黑遮罩淡入 | 未实现(等 `Image` 组件) |
这些结构的代码在 [`site/sections/Composition.tsx`](../site/sections/Composition.tsx),做页面时直接复制再换数据。
---
## 5. 规划中的组件
原计划的 14 个(Radio → Drawer)已全部落地,当前**没有待实现的组件**。
下一批(还没进计划,等应用侧提出真实需求再定):
| 候选 | 触发条件 | 注意点 |
| --- | --- | --- |
| `Image`(比例封面 + 悬浮遮罩) | 应用里有 3 处以上重复的封面实现 | 自带 `aspect-ratio`、懒加载、失败占位 |
| `Form` / `FormItem` | 出现第一个带校验的表单 | 只做布局与错误文案,校验逻辑交给页面 |
| `DatePicker` | 需要按日期筛选 | 依赖 Popover 定位,建议直接用原生 `input[type=date]` |
| `Upload` | 需要拖拽上传 | 依赖接口,先做成受控壳 |
| `Layout` 多侧栏 / 断点可配 | 同一页面需要两个左栏,或要自定义折叠断点 | 现在固定「一个 sider + 一个 aside」;扩展时用 `columns` 数组而不是再加 slot |
---
## 6. 明确不进库
| 东西 | 原因 | 替代 |
| --- | --- | --- |
| 分区卡片、应用条目、海报卡、图文卡 | 业务组件,字段随需求变 | 用 `Card` + `Grid` + `Avatar` + `Tag` 组合(§4) |
| Header / Sidebar / 页面骨架 | 页面级布局 | 放在应用里 |
| 24 栅格 Layout(`Row`/`Col`) | 只用得到 `Grid` | `Grid` + `Space` + CSS Grid |
| 全量图标库 | 字体 500+ 字形,体积无意义 | 按需加字形(§7) |
| 富文本编辑器、图表、日期面板 | 不是设计系统职责 | 引专用库 |
---
## 7. 新增图标字形的流程
1. 从参考站 iconfont 复制字形码(`\eXXX`);
2. `src/theme/iconfont.css` 加一条 `.su-icon-<name>::before { content: '\eXXX'; }`;
3. `src/components/Icon.tsx` 的 `IconName` 联合类型加 `'<name>'`;
4. 展示页 `site/sections/Icons.tsx` 的 `ICON_NAMES` 加一项;
5. 字形不在现有 woff2 里时,替换 `src/theme/assets/iconfont.woff2` 并重新构建;
6. 跑一次 `npm run build` 确认 CSS 里字体仍是内联的。
---
## 8. 契约模板(新增组件时复制到 §3)
```markdown
### Xxx 🚧
- **用途**:
- **不该用在哪**:
- **解剖**:(结构描述:几层、每个部分的职责)
- **Props**:
| prop | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| — | — | — | — |
- **变体与状态**:(variant / size / 状态矩阵中本组件需要覆盖哪些)
- **规则**:(组合禁忌、尺寸对齐、a11y 要求)
- **展示页**:`#anchor`
```
---
## 9. 变更记录
| 日期 | 变更 |
| --- | --- |
| 2026-10-26 | 初版:14 个基础组件(Icon / Badge / Tag / Divider / Button / IconButton / Input / Card / Grid / Space / Notice / Empty / ScrollTop / Avatar) |
| 2026-10-26 | 补齐令牌:`danger-hover`、`warning-text`、`bg-notice`、`bg-danger-soft`、`gradient-verified`、`font-weight-strong`、`control-height-*`、`icon-button-*` |
| 2026-10-26 | **对齐参考站**:阴影 1%/3%、字阶 12/13/14/18、`Tag` 重写(soft/solid/icon/dot)、新增 `ItemBadge`、`Button` 加 `shape`、`IconButton` 改圆形 28/36/44、`Card` 描边改 gray-1、`Input` 描边改 gray-3、补 focus ring 与 reduced-motion |
| 2026-10-26 | 深色主题(`data-scheme`)+ `Button variant="outline"` + `bg-light` 语义令牌;新增整页拼装示例 `site/demo.html` |
| 2026-10-27 | 图标改用参考站全量 iconfont(446 个字形,`IconName` 自动生成);新增 `Modal`、`Pagination`;示例页加入删除确认弹窗与分页 |
| 2026-10-27 | 新增 `Typography`(Title / Text / Paragraph)与 `Skeleton`;补 `--su-font-size-base`(16/24)与 `--su-skeleton-shine`;示例页改用排版组件并在刷新时显示骨架屏 |
| 2026-10-27 | 新增 `Spinner`、`Switch`、`Checkbox`(表单基础收口);示例页左栏加入「偏好」卡片(开关控制封面显示、复选框、同步中的 Spinner) |
| 2026-10-27 | 一次补齐剩余 14 个组件:`RadioGroup`、`Segmented`、`Select`、`Breadcrumb`、`Tabs`、`Menu`、`Toast`、`Table`、`Result`、`Progress`、`Steps`、`Drawer`、`Tooltip`、`Popover`;示例页接入面包屑、下拉、抽屉与 toast |
| 2026-10-27 | 新增 `Layout`(页面骨架:页头 / 侧栏 / 内容 / 页脚),组件总数 37 |
| 2026-10-28 | `Layout` 增加 `aside` / `asideWidth` 右栏,形成三栏骨架;展示页改为 5 个配方切换;补移动端适配(`dvh`、触屏 hover 隔离、分段/弹层夹宽、演示页导航抽屉) |
| 2026-10-28 | 为对齐 stash 原设计补充通用能力:`Layout` 增加 `gap` / `contentPadding` / `gutter` / `headerHeight` / `headerVariant` / `siderBordered` / `height="screen"` + `scrollable` + `contentId`;新增 `Cover`;`Avatar` 增加 `xl` 与 `tone`;`Paragraph` 增加 `size` / `leading`;新增布局类令牌(`--su-layout-width`、`--su-header-height`、`--su-reading-line-height`、`--su-radius-cover`、`--su-shadow-card-strong`、`--su-cover-scrim`) |
| 2026-10-28 | **视觉回归**:`Layout` 的 `contained` 改为只包页身,页头通栏;`screen + scrollable` 修复内容区高度未约束导致无法滚动;新增 `contentOffset`。`Menu` 增加 `itemHeight`、字重 500、圆角 8、chevron 改为 CSS 绘制,列表间距 2→4。`Avatar` 增加 `icon`。影响面:所有用到 `Menu` / `Layout contained` 的页面都需要重截图 |
| 2026-10-28 | **1:1 补全(视觉 + 交互)**:`Title` 增加 `weight`(600);`Input` 增加 `size`(44);`Button` 增加 `spinning`(图标原地旋转);`Cover` 改为 hover 上浮 2px(`lift`,默认开;`hoverZoom` 默认关);`ScrollTop` 增加 `offset`;`Menu` 的 hover 改主色、子菜单改 `grid-template-rows` 高度动画(0.28s);`Avatar xl` 徽标 20px、无图底色用新令牌 `--su-avatar-gradient`。**影响面**:`Cover`/`Menu`/`Avatar`/`Title` 的所有展示页 demo 与应用页面需重截图 |
| 2026-10-28 | **修复**:`Input` 聚焦不再叠 outline(文本框点击会命中 `:focus-visible`,导致边框 + 外环「双框」;现在只用边框变主色)。`Layout` 增加 `hideScrollbar`(内部滚动条可隐藏)。影响面:所有 `Input` 页面视觉(去掉了多余外环)、`Layout screen` 页面可选用 |
| 2026-10-28 | **补齐剩余能力**:新增 `Image`(比例 / 填充 / 失败降级)与 `FormField`(标签 + 提示 + 错误,不做校验);`Button` 增加 `size="lg"`(44);展示页补 `Title weight`、`Avatar xl/tone/icon`、`Cover lift`、`Menu itemHeight`、`Layout screen/hideScrollbar`、`Input size`、`FormField` 示例;新增 `themes/example.css` 主题示例(只改变量) |
| 2026-10-29 | `Breadcrumb` 增加 `onNavigate`(可选):传了就用 `preventDefault` 接管点击,交给 SPA 路由;不传保持普通链接。兼容性:新增可选 prop,无破坏性 |
| 2026-10-29 | 新增 `Textarea`(`rows` / `resize` / `invalid`),与 `Input` 共用边框与焦点样式。兼容性:纯新增 |
| 2026-10-29 | 新增 `PasswordInput`(`icon` / `size` / `toggle`):锁图标 + 显隐切换 + `aria-pressed`,事件语义与 `Input` 一致。兼容性:纯新增 |
| 2026-10-30 | **修复**:`PasswordInput` 的眼睛按钮移入输入框内部(`Input` 新增 `suffix` 插槽);`Input` 隐藏 Edge 原生 `::-ms-reveal` / `::-ms-clear`,避免与自带显隐按钮叠成两个眼睛。兼容性:`Input` 新增可选 prop,无破坏性;`PasswordInput` API 不变、仅外观变化 |
| 2026-10-31 | `Layout` 新增 `toolbar` 插槽:页头与页身之间的一条真实占位带(搜索 / 筛选 / 操作),宽度跟随容器(`contained` 时与侧栏 + 内容同宽),在 `height="screen"` 下不参与内部滚动。用它替代「搜索框 fixed + `contentOffset` 让位」的写法,页面不再需要用 JS 改 DOM 尺寸。兼容性:纯新增可选 prop,`contentOffset` 行为不变 |