- TextButton:文字按钮(tone 主色/危险、可选图标、disabled), 用于表格行内操作,hover 只加下划线 - ToggleRow:左标签 + 右开关的开关行,支持 hint 与 switchLabel - Table:新增 size='md' | 'sm',sm 把行内边距收到 space-2 - 展示页:按钮补「文字按钮」、开关补「开关行」、表格补「紧凑档」; COMPONENTS.md 契约 3.46 / 3.47 与 README 清单同步
50 KiB
组件目录与契约
配套:DESIGN.md(规则)、ADDING-A-COMPONENT.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 |
TextButton |
L2 | ✅ | 文字按钮(行内操作,无外框) | icon tone disabled title label |
Input |
L2 | ✅ | 组合式输入框 | icon clearable onClear shortcut inputRef |
Textarea |
L2 | ✅ | 多行文本(Markdown / 简介) | rows resize invalid |
PasswordInput |
L2 | ✅ | 密码输入(锁图标 + 显隐切换) | icon size toggle |
Switch |
L2 | ✅ | 即时生效的开关 | checked onChange size label |
ToggleRow |
L2 | ✅ | 开关行:左标签 + 右开关 | label checked onChange hint switchLabel |
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 |
Brand |
L2 | ✅ | 品牌标识:logo / 首字标记 + 名称 | name mark logo href size tone |
SearchField |
L2 | ✅ | 搜索框(语义版 Input,对外只给字符串) | value onChange variant size hotkey |
Ticker |
L3 | ✅ | 无限滚动的信息条 | children speed paused label |
Layout |
L3 | ✅ | 页面骨架:页头 / 侧栏 / 内容 / 页脚 | header sider aside maxWidth gap height |
Card |
L3 | ✅ | 白色容器(1px 极浅描边) | as padding hoverable |
EntryRow |
L2 | ✅ | 条目行:头像 + 标题 + 描述,整行可点 | title description href avatar badge onOpen |
EntryTile |
L3 | ✅ | 条目磁贴:上图下文白卡 | title description cover ratio onOpen |
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 size 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 类型 默认 说明 countnumber必填 数字 tone'danger' | 'primary' | 'neutral''danger'配色 maxnumber99超过显示 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 灰底 childrenReactNode必填 角标文字(建议 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'胶囊 / 小方块(元信息前缀用) iconReactNode— 前置图标(如分类图标) dotbooleanfalse前置 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 圆角 / 胶囊 blockbooleanfalse占满一行 loadingbooleanfalse自动禁用 + aria-busyspinningbooleanfalse图标原地旋转(不换转圈),用于刷新类动作 iconReactNode— 文字左侧图标 …restButtonHTMLAttributes— 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 类型 默认 说明 iconIconName— 左侧图标 size'sm' | 'md' | 'lg''md'32 / 36 / 44px suffixReactNode— 右侧插槽:图标按钮、单位等;排在清除按钮之前 clearablebooleanfalse有值且传了 onClear才显示清除onClear() => void— 清除回调 shortcutstring— 右侧提示(如 ⌘K),仅展示inputRefRef<HTMLInputElement>— 拿真实 input 做聚焦 className/style— — 作用在外层容器 …restInputHTMLAttributes— 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 类型 默认 说明 columnsnumber4宽屏列数 tabletColumnsnumbercolumns / 2(至少 2)≤1100px mobileColumnsnumber1≤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 类型 默认 说明 srcstring— 图片;加载失败自动降级 altstring''图片替代文本 fallbackstring'•'无图时的字符(通常取首字) iconReactNode— 无图时的图标(优先于 fallback),用于缺省头像 size'sm' | 'md' | 'lg' | 'xl''md'24 / 36 / 48 / 64px shape'circle' | 'square''circle'人/应用用圆,图标封面用方 tone'neutral' | 'primary''neutral'无图底色:浅灰 / 品牌浅渐变(资料卡) verifiedbooleanfalse右下角认证角标(12 / 16 / 20px) -
规则:
fallback传一个字符;资料卡用xl+tone="primary",列表里用lg,紧凑行用sm;不要给头像加边框。 -
展示页:
#avatars
3.16 Modal ✅
-
用途:需要打断流程的确认 / 表单 / 说明。
-
不该用在哪:轻量反馈(等
Toast)、页面内的展开收起(用条件渲染 + 动画)。 -
解剖:原生
<dialog>(焦点陷阱、Esc、背景 inert 由浏览器提供)+ 遮罩 + 标题栏 + 正文 + 底部操作区。 -
Props:
prop 类型 默认 说明 openboolean必填 受控开关 onClose() => void必填 Esc / 遮罩 / 关闭按钮都会调用 titleReactNode必填 标题,同时作为 aria-labelledbysize'sm' | 'md' | 'lg''md'300 / 460 / 640 closeOnOverlaybooleantrue点遮罩是否关闭 footerReactNode— 底部按钮区 -
规则:打开时锁背景滚动;危险操作必须二次确认;底部按钮顺序是「取消在左、主操作在右」。
-
展示页:
#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 类型 默认 说明 Titlelevel1 | 2 | 3 | 4224/32、18/26、16/24、14/22 Titleweight'normal' | 'medium' | 'semibold' | 'strong''medium'400 / 500 / 600 / 700;参考站的 600 用 semiboldTitleas'h1' | 'h2' | 'h3' | 'h4' | 'div'按 level 换语义标签 Texttone'main' | 'secondary' | 'sub' | 'muted''main'文字层级 Textsize'xs' | 'sm' | 'md' | 'base' | 'lg''md'12 / 13 / 14 / 16 / 18 Textstrong/truncatebooleanfalse500 字重 / 单行省略 Paragraphtone'main' | 'secondary' | 'sub''sub'文字层级 Paragraphsize'sm' | 'md' | 'base''base'13 / 14 / 16,长文正文用 md Paragraphleading'base' | 'relaxed''base'relaxed = 1.9 行高(阅读型) Paragraphlinesnumber— 最多几行,超出省略 -
规则:组件不带外边距,段落间距交给
Space;不要在页面里另写font-size。 -
展示页:
#typography
3.19 Skeleton ✅
-
用途:内容加载前的占位(列表首屏、刷新、分页加载)。
-
不该用在哪:按钮内的 loading(
Button自带 spinner)、整页空态(Empty)。 -
解剖:
text(多行文本条,最后一行收短)/rect(封面)/circle(头像),占位块上有一层扫光。 -
Props:
prop 类型 默认 说明 variant'text' | 'rect' | 'circle''text'形状 linesnumber1仅 text:行数 width/heightnumber | 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 类型 默认 说明 checkedboolean必填 受控状态 onChange(checked: boolean) => void必填 切换回调 size'sm' | 'md''md'32×18 / 40×22 disabledbooleanfalse禁用 labelstring— 无障碍名称:旁边没有文字时必填 -
规则:旁边配文字时用
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)、size: 'md' | 'sm'(行高密度,默认md)、emptyText。 - 规则:排序 / 筛选 / 分页由页面组合,组件不内置交互;空数据渲染
emptyText;单元格是任意ReactNode,行内操作直接用TextButton/IconButton。 - 展示页:
#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 类型 默认 说明 headerReactNode— 页头,默认吸顶(半透明 + 模糊) toolbarReactNode— 页头与页身之间的工具条(搜索 / 筛选 / 操作带),宽度随容器,不参与内容区滚动 siderReactNode— 左侧栏,固定宽度 asideReactNode— 右侧栏,固定宽度(目录 / 聚合 / 广告位) footerReactNode— 页脚 childrenReactNode必填 内容区 maxWidthnumber | 'full'1280内容最大宽度 siderWidthnumber260左侧栏宽度 asideWidthnumber300右侧栏宽度 stickyHeaderbooleantrue页头是否吸顶 containedbooleanfalse页身套一层居中容器(页头始终通栏,页头内容自己限宽) gapnumber0侧栏 / 内容 / 右栏之间的间距(px) contentPadding'none' | 'sm' | 'md' | 'lg''md'内容区留白:0 / 12 / 20 / 24 contentOffsetnumber0内容区顶部偏移(px),给页头下的悬浮搜索/工具条留位 gutternumber0contained 容器的左右留白(px) headerHeightnumber60页头最小高度(px) headerVariant'blur' | 'solid''blur'半透明模糊 / 实底 + 下边框 siderBorderedbooleantrue侧栏右侧分隔线 height'auto' | 'screen''auto'screen:锁视口,适合应用型界面 scrollablebooleanfalse内容区内部滚动(配 height="screen")hideScrollbarbooleanfalse内部滚动时隐藏滚动条(仍可滚轮/键盘滚动) contentIdstring— 内容区 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 类型 默认 说明 srcstring— 图片地址;不传时显示占位字符 altstring必填 图片说明,同时作为按钮 title captionReactNode— 底部标题(最多 2 行) ratio'poster' | 'article' | 'wide' | number'article'2/3、4/3、16/9,或数字宽高比 radius'cover' | 'none''cover'8px / 贴边(配合 Card padding="none")liftbooleantrue悬停整块上浮 2px(参考站行为) hoverZoombooleanfalse悬停放大图片 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 类型 默认 说明 labelReactNode必填 字段名(渲染为 <label>)htmlForstring— 关联控件 id hintReactNode— 说明文字,有错误时隐藏 errorReactNode— 错误文案,出现时标红并 role="alert"requiredbooleanfalse显示红色 *childrenReactNode必填 控件( 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 类型 默认 说明 iconIconName | null'lock_line'左侧图标; null去掉size'sm' | 'md' | 'lg''md'32 / 36 / 44,与 Input一致togglebooleantrue是否显示右侧显隐按钮 className/stylestring/CSSProperties— 作用在最外层 …restInputHTMLAttributes— 透传原生属性( type/size/icon由组件接管) -
规则:
onChange是原生事件(与Input一致),Enter 提交用onKeyDown;切换按钮必须带aria-pressed与「显示/隐藏密码」名称,焦点环不得移除。 -
展示页:
#inputs
3.43 Brand ✅
-
用途:页头左侧的品牌标识——图片 logo 或首字圆形标记 + 品牌文字。
-
不该用在哪:头像(用
Avatar);纯文字标题(用Title)。 -
解剖:
inline-flex容器;logo有值时渲染<img>,否则渲染首字标记;href有值时整体是<a>,否则是<span>。 -
Props:
prop 类型 默认 说明 namestring''品牌文字,可留空只显示标记 markstring''标记里的字,留空取 name首字logostring''logo 图片地址;留空用 markhrefstring— 传了渲染成链接 size'sm' | 'md''md'标记 24 / 32 tone'solid' | 'soft''solid'深底白字 / 主色浅底 labelstring— 链接的无障碍名称 -
规则:品牌名可空(只有标记也要能读出来,用
label);图片的alt留空,名称由文字节点承担。 -
展示页:
#site-chrome
3.44 Ticker ✅
-
用途:横向无限滚动的信息条(页头运行状态这类)。
-
不该用在哪:需要用户逐条阅读的长文本(用
Text);通知(用Notice/Toast)。 -
解剖:容器
overflow: hidden+ 两端mask渐隐;内部三份相同内容,按第一份的实测宽度做translateX,所以循环处不跳帧;宽度变化由ResizeObserver重新测量。 -
Props:
prop 类型 默认 说明 childrenReactNode必填 一组内容,组件负责复制三份 speednumber24一份内容滚完的秒数 pausedbooleanfalse暂停但保留进度 labelstring— 读屏用的整体描述 -
规则:只复制不改造
children;后两份带aria-hidden,读屏只读一遍;prefers-reduced-motion下不滚。 -
展示页:
#site-chrome
3.45 SearchField ✅
-
用途:搜索框——语义版
Input,对外只给字符串。 -
不该用在哪:普通表单输入(用
Input);需要自定义后缀的场景(用Input+suffix)。 -
解剖:外层定宽容器 +
Input size="lg"(搜索图标、清空按钮、快捷键提示);variant="card"只换描边/圆角/阴影,不换结构。 -
Props:
prop 类型 默认 说明 valuestring必填 受控值 onChange(value: string) => void必填 直接给字符串 placeholderstring'搜索…'占位文案,同时作为无障碍名称 size'md' | 'lg''lg'36 / 44 高 variant'plain' | 'card''plain'普通输入框 / 实底 + 柔和阴影 maxWidthnumber | string480最大宽度 shortcutstring— 右侧快捷键提示(仅展示) hotkeystring— Ctrl/Cmd + 该键聚焦并全选(需同时传 inputRef)clearOnEscapebooleantrueEsc 清空 inputRefRef<HTMLInputElement>— 透传内部输入框 -
规则:
onChange给字符串而不是事件——这是它和Input唯一的语义差别,别在组件里做过滤/请求。 -
展示页:
#site-chrome
3.46 TextButton ✅
- 用途:文字按钮——看起来像链接、行为像按钮,用于表格行内操作(编辑 / 删除 / 新建子项)。
- 不该用在哪:需要底色、尺寸或独立成行的地方(用
Button);只有图标的地方(用IconButton)。 - 解剖:
<button type="button">+ 可选前置图标;正文font-size-sm,tone决定主色 / 危险色。 - Props:
children、icon、tone: 'primary' | 'danger'(默认primary)、disabled、onClick、title、label。 - 规则:hover 只加下划线,不加底色;
disabled用--su-text-disabled;focus-visible保留焦点环。 - 展示页:
#buttons
3.47 ToggleRow ✅
- 用途:设置面板里「左标签 + 右开关」的一行。
- 不该用在哪:需要用户点「保存」才生效的场景(用
Checkbox+ 按钮)。 - 解剖:
div两端对齐,左侧Text(可加一行hint),右侧Switch。 - Props:
label、checked、onChange、switchLabel(默认取 label 文本)、hint、disabled。 - 规则:开关的无障碍名称必须有值,label 不是字符串时请显式传
switchLabel。 - 展示页:
#switch
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,做页面时直接复制再换数据。
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. 新增图标字形的流程
- 从参考站 iconfont 复制字形码(
\eXXX); src/theme/iconfont.css加一条.su-icon-<name>::before { content: '\eXXX'; };src/components/Icon.tsx的IconName联合类型加'<name>';- 展示页
site/sections/Icons.tsx的ICON_NAMES加一项; - 字形不在现有 woff2 里时,替换
src/theme/assets/iconfont.woff2并重新构建; - 跑一次
npm run build确认 CSS 里字体仍是内联的。
8. 契约模板(新增组件时复制到 §3)
### 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 行为不变 |
| 2026-11-01 | 新增三个「站点外壳」组件,把应用里各自手写的一套收进库:Brand(logo / 首字标记 + 名称,href 可选)、Ticker(无限滚动信息条,按实测宽度位移,循环不跳帧)、SearchField(语义版搜索框,variant 切普通 / 悬浮条)。同时新增展示级令牌 --su-font-size-2xl / --su-line-height-2xl(32 / 40)。兼容性:纯新增,无破坏 |
| 2026-11-01 | 新增 EntryRow / EntryTile:把应用里两套手写内容卡(.app-card / .ces-card)收进库。两者共用同一套点击语义(无修饰键接管、按住修饰键保留默认)与悬停反馈;EntryTile 由 Card padding="none" hoverable + Cover 拼成。兼容性:纯新增 |
| 2026-11-01 | Select 的下拉箭头从 arrow_down_line 换成和 Menu 一致的 CSS 折角(6px、收起朝下 / 展开朝上)。影响面:所有 Select 的外观,交互不变 |
| 2026-11-01 | 新增 TextButton(文字按钮:tone 主色 / 危险色、可选图标)与 ToggleRow(左标签 + 右开关的开关行,支持 hint);Table 新增 size: 'md' | 'sm' 紧凑档。目的是把后台配置页里手写的 .group-action / .sidebar-toggle-row / <table> 收进库。兼容性:纯新增,Table 默认行为不变 |