Files
UI/llms-full.txt
T

1777 lines
85 KiB
Plaintext
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.
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T04:24:59.738Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
### @ui-arch/frame
1. 引入:import { signal, computed, effect, h, mount } from '@ui-arch/frame'
2. 声明状态:const count = signal(0);无参调用读值 count(),传参写值 count(1)
3. 渲染视图:mount('#app', () => h('div', null, () => `count = ${count()}`))——函数型子节点即响应式文本
4. 绑定事件:props 里 on 开头且值为函数的键(onClick 等)自动 addEventListener
5. 派生数据:const double = computed(() => count() * 2),视图里直接调用 double(),依赖自动追踪
6. 列表渲染:renderList(tbody, () => rows.map(r => h('tr', null, r.name))),信号变化整体替换子节点
### @ui-arch/table-core
1. 创建实例:const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()] })
2. 取派生数据:table.getVisibleRows() 返回 { rows, total };total 语义固定 = 筛选后、分页前的行数
3. 变更状态:只能 dispatch action,如 table.dispatch({ type: 'toggleSort', key: 'name' });状态不可变
4. 订阅刷新:table.subscribe(fn),fn 里重新 getVisibleRows() 并交给渲染层
5. 分页信息:api.getPageInfo() 返回 { page, pageSize, totalPages };越界页码自动钳制
6. 扩展能力:实现 TablePlugin(derive / patchState / api 三钩子)传入 plugins 即插入管线
### @ui-arch/tokens
1. 引入:在应用入口 import '@ui-arch/tokens/tokens.css'
2. 消费:组件样式一律 var(--ua-color-accent) 这类变量,不写死色值
3. 暗色:根元素设 data-theme=dark,变量值自动切换
4. 扩展:新令牌先同时加进 tokens.css 与 tokens.json,再重新生成文档站
### @ui-arch/ui
1. 安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)
2. 引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'
3. 创建状态:import { signal } from '@ui-arch/frame',如 const name = signal('')
4. 调用组件:const el = textField({ value: name }),把 el 挂到你的容器或配合 h() 组合
5. 读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM
### @ui-arch/cli
1. 装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)
2. 查组件:ua list 看全量,ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)
3. 查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码
4. 改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)
5. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens
6. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components
## @ui-arch/ui(skin-dom)v0.1.0
28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
### image — 图片 Image
参数:
- src(必填):string —— 图片地址(缩略图与大图共用)
- alt(默认 ''):string —— 替代文本;缺省空串
- width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认
示例:
```
image({ src: '/a.png', alt: '架构图', width: 160 })
```
手把手:
1. 基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜
2. 点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图
3. 宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width
4. alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它
现场演示:apps/docs/demo.html#sec-image
### button — 按钮 Button
参数:
- label(必填):string —— 按钮文案
- variant(默认 primary):'primary' | 'ghost' | 'danger' —— 视觉强度;缺省 primary
- type:'button' | 'submit' | 'reset' —— 原生类型;缺省 button,表单内不会意外提交
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onClick(回调):() => void —— 点击回调
示例:
```
button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') })
```
手把手:
1. 确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger
2. 绑定 onClick 回调处理点击逻辑
3. 需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染
4. 放进 <form> 里需要提交时显式传 type: "submit";缺省 button 不会触发原生提交
现场演示:apps/docs/demo.html#sec-counter
### textField — 文本输入 TextField
参数:
- value:Signal<string> —— 双向绑定信号:输入写回信号,信号变化写回输入框
- placeholder:string —— 占位文案
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onInput(回调):(value: string) => void —— 输入回调,每次按键触发
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
示例:
```
const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })
```
手把手:
1. 先用 signal('') 创建字符串信号
2. 传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显
3. 在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
现场演示:apps/docs/demo.html#sec-bind
### inputNumber — 数字输入 InputNumber
参数:
- value(必填):Signal<number> —— 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)
- min:number —— 最小值;缺省无下限
- max:number —— 最大值;缺省无上限
- step(默认 1):number —— 步长;缺省 1
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间)
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
示例:
```
const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })
```
手把手:
1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定
2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析
3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
现场演示:apps/docs/demo.html#sec-inputnumber
### textarea — 多行输入 Textarea
参数:
- value:Signal<string> —— 双向绑定信号
- rows(默认 3):number —— 初始行数;缺省 3
- placeholder:string —— 占位文案
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onInput(回调):(value: string) => void —— 输入回调
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
示例:
```
const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })
```
手把手:
1. 创建信号并传入 value
2. 用 rows 控制初始高度,用户可纵向拉伸
3. 字数统计等派生逻辑直接在响应式文本里读 bio().length
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
现场演示:apps/docs/demo.html#sec-form
### slider — 滑块 Slider
参数:
- value(必填):Signal<number> —— 当前值信号,双向绑定:拖动写回,外部赋值同步位置
- min(默认 0):number —— 最小值;缺省 0
- max(默认 100):number —— 最大值;缺省 100
- step(默认 1):number —— 步长;缺省 1
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- showValue(默认 true):boolean —— 是否右侧显示当前值;缺省 true
- onChange(回调):(value: number) => void —— 拖动回调
示例:
```
const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })
```
手把手:
1. 创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定
2. 用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数
3. 拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理
4. 只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal
5. 禁用场景传 disabled(布尔或信号),置灰且不可拖
现场演示:apps/docs/demo.html#sec-slider
### checkbox — 复选框 Checkbox
参数:
- checked:Signal<boolean> —— 勾选状态信号,双向绑定
- label:string —— 右侧文案;缺省只渲染勾选框
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onChange(回调):(checked: boolean) => void —— 变更回调
示例:
```
const agreed = signal(false); checkbox({ label: '同意条款', checked: agreed, onChange: (v) => console.log(v) })
```
手把手:
1. 创建布尔信号 agreed = signal(false)
2. 传入 checked 与 label,勾选即写回信号
3. 表单提交前用 agreed() 判断是否勾选
现场演示:apps/docs/demo.html#sec-form
### switchButton — 开关 Switch
参数:
- checked:Signal<boolean> —— 开关状态信号,双向绑定
- label:string —— 左侧文案
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onChange(回调):(checked: boolean) => void —— 变更回调
示例:
```
const night = signal(true); switchButton({ label: '夜间模式', checked: night })
```
手把手:
1. 创建布尔信号并传入 checked
2. 轨道与滑块的动画由 CSS 完成,无需干预
3. 状态变化在 onChange 里做副作用(如切换主题)
现场演示:apps/docs/demo.html#sec-form
### select — 下拉选择 Select(自绘弹层)
参数:
- options(必填):OptionItem[] —— 选项列表,每项 { value, label }
- value:Signal<string> —— 选中值信号,双向绑定
- placeholder:string —— 未选中时触发器显示的占位文案
- searchable:boolean —— 弹层顶部带搜索框,按标签包含匹配过滤;缺省 false
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层
- onChange(回调):(value: string) => void —— 选中回调
示例:
```
const city = signal(''); select({ options: [{ value: 'bj', label: '北京' }], value: city, placeholder: '选择城市…' })
```
手把手:
1. 准备 options 数组,value 是提交用值、label 是展示文案
2. 创建信号并传入 value;未传时组件内部自持状态
3. 点击触发器展开弹层,点选项写回信号并收起;点外部或按 Esc 也会收起
4. 选中项在弹层中用淡色弱化标记,不抢视觉焦点
5. 弹层展开时按 Esc 只收起下拉并回焦触发器;若下拉在弹窗/抽屉内,再按一次 Esc 才关外层(逐层关闭)
6. searchable: true 时打开即聚焦搜索框,输入实时过滤;↑↓/Home/End 只在可见项间移动,无命中显示「无匹配选项」
现场演示:apps/docs/demo.html#sec-form
### menu — 下拉菜单 Menu
参数:
- label(必填):string —— 触发器文案
- items(必填):MenuItem[] —— 菜单条目:{ value, label, disabled?, danger?, divider?, shortcut? }
- variant(默认 ghost):'primary' | 'ghost' —— 触发器视觉强度;缺省 ghost
- align(默认 start):'start' | 'end' —— 弹层对齐方向;缺省 start(与触发器左对齐)
- onSelect(回调):(value: string) => void —— 选中回调,禁用项不触发
示例:
```
menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })
```
手把手:
1. 准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组
2. 传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'
3. 点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)
4. 条目要给快捷键提示时传 shortcut(如 '⌘K'):右侧灰字展示,组件不绑定按键,实际快捷键由调用方在全局监听
5. 键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过
6. 靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true
7. 展开时按 Esc 只收起菜单并回焦触发器,不会连带关闭所在弹窗(逐层关闭)
现场演示:apps/docs/demo.html#sec-menu
### radioGroup — 单选组 RadioGroup
参数:
- options(必填):OptionItem[] —— 选项列表
- value:Signal<string> —— 选中值信号,双向绑定
- disabled:boolean | Signal<boolean> —— 整组禁用:全部选项与文案置灰且不可选
- onChange(回调):(value: string) => void —— 变更回调
示例:
```
const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }], value: size })
```
手把手:
1. 创建字符串信号,初值设为默认选项的 value
2. 传入 options 与 value,组内自动互斥
3. 读取 size() 获取当前选中值
4. 需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定
现场演示:apps/docs/demo.html#sec-form
### accordion — 折叠面板 Accordion
参数:
- items(必填):AccordionItem[] —— 面板列表:{ value, title, content, disabled? }
- active:Signal<string> —— 受控展开面板信号(单开模式);缺省由组件内部持有状态
- multiple(默认 false):boolean —— 允许多个面板同时展开;缺省 false(手风琴,单开)
- onChange(回调):(active: string) => void —— 展开变化回调:收起时传空串
示例:
```
accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] })
```
手把手:
1. 准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数
2. 默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true
3. 需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态
4. 标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联
5. 内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换
现场演示:apps/docs/demo.html#sec-accordion
### breadcrumb — 面包屑 Breadcrumb
参数:
- items(必填):BreadcrumbItem[] —— 路径条目:{ label, href? },末项自动视为当前页
- separator(默认 /):string —— 分隔符;缺省 '/'
- onNavigate(回调):(item, index) => void —— 无 href 条目的点击回调
示例:
```
breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })
```
手把手:
1. 按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'
2. 需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调
3. 改分隔符传 separator,如 '›';分隔符对读屏隐藏
4. 语义结构为 nav + ol + li,可直接放进页头或卡片标题栏
现场演示:apps/docs/demo.html#sec-breadcrumb
### card — 卡片 Card
参数:
- title:string —— 标题;缺省不渲染标题行
- ...children:unknown[] —— 任意子节点:字符串 / DOM / 响应式函数
示例:
```
card({ title: '用户表' }, dataTable({ data, columns }))
```
手把手:
1. card 是容器组件:第一个参数是 props,其余参数是任意子节点
2. 传 title 显示标题行,否则只有内容区
3. 子节点可以混合字符串、DOM 节点与响应式函数
现场演示:apps/docs/demo.html#sec-card
### badge — 徽标 Badge
参数:
- label(必填):string —— 徽标文案
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' —— 语义色;缺省 default
示例:
```
badge({ label: '成功', variant: 'success' })
```
手把手:
1. 选择语义色:default 中性、success 成功、warning 警告、danger 危险
2. 传入 label 即可,纯展示组件无状态
3. 状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button
现场演示:apps/docs/demo.html#sec-feedback
### tag — 标签 Tag
参数:
- label(必填):string —— 标签文案
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' | 'info' —— 语义色;缺省 default
- closable(默认 false):boolean —— 显示 × 关闭按钮;缺省 false(纯展示)
- onClose(回调):() => void —— 关闭回调:调用方从数据源移除该项后重新渲染
示例:
```
tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })
```
手把手:
1. 纯展示用 badge,需要可移除的标签(筛选项 / 已选条件)用 tag
2. 按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效
3. closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)
4. 组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号
5. 标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定
现场演示:apps/docs/demo.html#sec-tag
### avatar — 头像 Avatar
参数:
- name(必填):string —— 名称:生成占位首字,并作为图片 alt 与悬停标题
- src:string —— 图片地址;缺省用首字占位
- size(默认 32):number —— 直径像素;缺省 32
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' —— 占位底色语义档;缺省 default
示例:
```
avatar({ name: '张三' }); avatar({ name: 'Alice', size: 40, variant: 'success' })
```
手把手:
1. 只传 name:用名称首字做占位(中文取首字、英文大写),底色走 accent 淡化,暗色自动适配
2. 有图片就传 src:渲染 img 并用 name 作 alt,读屏语义完整;图片缺失时由浏览器 alt 兜底
3. 尺寸用 size(像素)控制,头像不参与布局拉伸(flex:none),可安全排进 .row 与表格单元格
4. 需要区分类别(在线/告警)时用 variant 换底色档,颜色一律来自令牌
现场演示:apps/docs/demo.html#sec-avatar
### alert — 提示条 Alert
参数:
- message(必填):string —— 提示内容
- title:string —— 加粗标题行;缺省只渲染正文
- variant(默认 info):'info' | 'success' | 'warning' | 'danger' —— 语义色;缺省 info
- closable(默认 false):boolean —— 显示 × 关闭按钮,退场后从 DOM 移除;缺省 false
- onClose(回调):() => void —— 关闭回调,供调用方同步业务状态
示例:
```
alert({ message: '操作可能存在风险', variant: 'warning' })
```
手把手:
1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误
2. 传入 message 即可,入场自带下滑动画
3. 需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色
4. 需要用户可自行消除的提示传 closable: true:点 × 淡出并从 DOM 移除,配合 onClose 同步业务状态
5. 有标题与正文两层信息时传 title(加粗首行),正文仍走 message
现场演示:apps/docs/demo.html#sec-feedback
### skeleton — 骨架屏 Skeleton
参数:
- variant(默认 rect):'text' | 'rect' | 'circle' —— 占位形状;缺省 rect(整块矩形)
- rows(默认 3):number —— text 形状的行数;缺省 3,末行自动缩短模拟段落
示例:
```
skeleton({ variant: 'text', rows: 3 }); skeleton({ variant: 'circle' }); skeleton({ variant: 'rect' })
```
手把手:
1. 数据加载期间用 skeleton 顶替真实内容,加载完成后整体替换
2. 文本段落用 variant: 'text' + rows 控制行数,头像用 'circle',卡片/图片区域用 'rect'
3. 占位块自带扫光动画,reduced-motion 用户自动静态化为纯灰块
4. 组合布局:skeleton 是普通 DOM,可放进 card / row 等任意容器
现场演示:apps/docs/demo.html#sec-skeleton
### toast — 消息提示 Toast
参数:
- message(必填):string —— 提示文案
- duration(默认 3000):number —— 自动消失毫秒;缺省 3000
- closable(默认 false):boolean —— 显示关闭按钮,点 × 立即收起;缺省 false(仅可关闭的 toast 接收指针事件,其余保持穿透)
- variant(默认 info):'info' | 'success' | 'warning' | 'danger' —— 语义色;缺省 info(toast.error 对应 danger)
示例:
```
toast.success('保存成功'); toast.error('网络请求失败', 5000)
```
手把手:
1. 直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器
2. 不传 duration 时默认停留 3 秒后自动淡出并移除节点
3. 多条消息在右上角自动纵向堆叠,互不遮挡
4. 需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入
5. 同屏最多 4 条,超出自动挤掉最早一条;需要用户主动确认的消息用 showToast({ closable: true }) 给个 ×
现场演示:apps/docs/demo.html#sec-toast
### tooltip — 悬浮提示 Tooltip
参数:
- content(必填):string —— 提示文案(纯文本)
- placement(默认 top):'top' | 'bottom' —— 气泡方位;缺省 top(触发器上方)
- delay(默认 150):number —— 出场延迟毫秒;缺省 150,传 0 立即显示
- trigger(必填):HTMLElement —— 第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)
示例:
```
tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))
```
手把手:
1. 先创建触发器元素,如 const btn = button({ label: '保存' })
2. 用 tooltip(props, trigger) 包一层:返回带气泡的包裹元素,原触发器仍是独立节点
3. 鼠标移入或键盘聚焦约 150ms 后出场,移出 / 失焦立即收起;按 Esc 可立刻关闭
4. 气泡默认在上方,空间不足或需要避让时传 placement: 'bottom'
5. 气泡 pointer-events: none 不抢指针;判读依赖 aria-describedby + role='tooltip',读屏可读
6. 只做展示:不要在气泡里放可点击元素,需要交互请用 modal 或自绘弹层
现场演示:apps/docs/demo.html#sec-tooltip
### popover — 气泡卡片 Popover
参数:
- content(必填):unknown —— 弹层内容:字符串 / DOM 节点 / 响应式函数,可放按钮等交互元素
- title:string —— 弹层标题;缺省不渲染标题行
- placement(默认 bottom):'top' | 'bottom' —— 弹层方位;缺省 bottom(触发器下方)
- trigger(默认 click):'click' | 'hover' —— 触发方式;缺省 click
- delay(默认 150):number —— hover 模式出场延迟毫秒;缺省 150
- onOpenChange(回调):(open: boolean) => void —— 开合回调
- triggerEl(第二个参数)(必填):HTMLElement —— 第二个参数:被包裹的触发器元素
示例:
```
popover({ title: '筛选条件', content: h('div', null, '任意可交互内容') }, button({ label: '筛选' }))
```
手把手:
1. 创建触发器元素,再用 popover(props, trigger) 包一层:返回带弹层的包裹元素
2. 点击触发器开合;点弹层外部或按 Esc 收起(Esc 后焦点回触发器)
3. content 里可以放 button / checkbox 等交互元素(tooltip 只能纯文本),点击弹层内部不会误关
4. hover 场景传 trigger: 'hover' + delay 控制多快出场;placement: 'top' 让弹层往上翻
5. 需要感知开合做副作用(如懒加载数据)时用 onOpenChange
6. 展开时按 Esc 只收起气泡并回焦触发器,不会连带关闭所在弹窗(逐层关闭)
现场演示:apps/docs/demo.html#sec-popover
### spinner — 加载指示器 Spinner
参数:
- size(默认 18):number —— 直径像素;缺省 18
示例:
```
spinner(); spinner({ size: 26 })
```
手把手:
1. 数据加载时插入 spinner(),加载完成移除
2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)
3. 与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner
现场演示:apps/docs/demo.html#sec-feedback
### progress — 进度条 Progress
参数:
- value:Signal<number> —— 当前进度 0-100 信号,变化自动平滑过渡(越界钳制)
- showText(默认 true):boolean —— 是否显示百分比文字;缺省 true
示例:
```
const pct = signal(0); progress({ value: pct }); setInterval(() => pct((pct() + 5) % 105), 400)
```
手把手:
1. 时长未知的加载直接 progress() 不传 value:填充块匀速往复,不显示百分比,读屏读作进行中
2. 创建数值信号 pct = signal(0),范围 0-100(越界自动钳制)
3. 传入 value,填充宽度与百分比文字随信号平滑过渡
4. 进行中填充带流动条纹,到 100% 自动变 success 色并停止条纹
5. 不需要百分比文字时传 showText: false
6. 定量进度用 value 信号:满格自动切 success 色并停止条纹,role=progressbar + aria-valuenow 同步
现场演示:apps/docs/demo.html#sec-feedback
### tabs — 标签页 Tabs
参数:
- tabs(必填):OptionItem[] —— 页签列表,每项 { value, label }
- active:Signal<string> —— 当前激活页签 value 信号,双向绑定
- onChange(回调):(value: string) => void —— 切换回调
示例:
```
const tab = signal('a'); tabs({ tabs: [{ value: 'a', label: '概览' }, { value: 'b', label: '明细', disabled: true }], active: tab })
```
手把手:
1. 创建信号 tab = signal('a'),初值为默认页签
2. tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB
3. 点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点
4. 键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签
现场演示:apps/docs/demo.html#sec-tabs-modal
### stepper — 步骤条 Stepper
参数:
- steps(必填):StepperItem[] —— 步骤列表,每项 { label, description? }
- active(必填):Signal<number> —— 当前步索引(0 基)信号,双向绑定:回跳写回、外部赋值同步
- clickable:boolean —— 已完成步骤是否可点击回跳;缺省 false
- onChange(回调):(index: number) => void —— 步骤切换回调(点击回跳触发)
示例:
```
const step = signal(1); stepper({ steps: [{ label: '下单' }, { label: '支付' }, { label: '完成' }], active: step, clickable: true })
```
手把手:
1. 用 signal(0) 创建当前步索引,传入 active;索引之前的步骤显示 ✓ 已完成态
2. 流程推进时外部赋值即可:step(step() + 1),组件三态自动跟随
3. clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态
现场演示:apps/docs/demo.html#sec-stepper
### rate — 评分 Rate
参数:
- value(必填):Signal<number> —— 当前评分(1 基整数值)信号,双向绑定:点选/键盘写回、外部赋值同步
- max(默认 5):number —— 星标总数;缺省 5
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
- onChange(回调):(value: number) => void —— 评分变化回调
示例:
```
const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) })
```
手把手:
1. 用 signal(0) 创建评分信号并传入 value;0 表示未评分
2. 点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原
3. max 可调整星标总数;disabled 支持布尔或信号禁用
现场演示:apps/docs/demo.html#sec-rate
### empty — 空状态 Empty
参数:
- title:string —— 主标题;缺省不渲染
- description:string —— 补充说明;缺省不渲染
- action:unknown —— 操作区内容(常见为按钮);缺省不渲染
示例:
```
empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })
```
手把手:
1. 在列表 / 表格 / 筛选结果为空时渲染 empty 占位
2. title 一句话说明状态,description 补充原因或建议
3. action 传入 DOM(如 ghost 按钮)引导用户下一步操作
现场演示:apps/docs/demo.html#sec-empty
### divider — 分隔线 Divider
参数:
- label:string —— 居中文案标签;缺省纯线条
- vertical:boolean —— 是否垂直分隔线(用于行内工具栏);缺省水平
- dashed:boolean —— 是否虚线;缺省实线
示例:
```
divider({ label: '更多信息' }) / divider({ vertical: true })
```
手把手:
1. 在区块之间插入 divider 划分内容层次
2. 传 label 在水平线上显示居中文案,如「更多信息」
3. 工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感
现场演示:apps/docs/demo.html#sec-divider
### pagination — 分页器 Pagination
参数:
- page(必填):Signal<number> —— 当前页码信号,双向绑定:外部赋值自动翻页,点击页码写回信号
- pageSize(必填):number —— 每页条数
- total(必填):number —— 总条数
- onChange(回调):(page: number) => void —— 翻页回调
- pageSizes:number[] —— 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器
- onPageSizeChange(回调):(size: number) => void —— 切换每页条数回调,同时自动回到第 1 页
示例:
```
const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) => console.log(p) })
```
手把手:
1. 创建页码信号 page = signal(1),与 pageSize / total 一起传入
2. 页码 ≤7 全部展开;更多时只保留首尾与当前页 ±1,缺口折叠为省略号
3. 首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current
4. 与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步
5. 键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发
6. pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据
现场演示:apps/docs/demo.html#sec-pagination
### modal — 弹窗 Modal
参数:
- open(必填):Signal<boolean> —— 开关信号:true 显示 / false 隐藏
- title(必填):string —— 标题
- size(默认 md):'sm' | 'md' | 'lg' —— 宽度档位:sm 340px / md 420px / lg 640px;缺省 md
- footer:unknown —— 底部操作区内容(常见为按钮组,右对齐);缺省不渲染
- onClose(回调):() => void —— 关闭回调(点遮罩、× 或 Esc 触发)
- ...children:unknown[] —— 弹窗内容子节点
示例:
```
const open = signal(false); modal({ title: '确认操作', open, onClose: () => open(false) }, h('p', null, '内容'))
```
手把手:
1. 创建布尔信号 open = signal(false),弹窗初始隐藏
2. 把 open 传给 modal,其余参数为弹窗内容子节点
3. 打开:open(true);关闭:open(false),点遮罩、× 或 Esc 会自动写回 false;多弹层叠加时 Esc 只关最上层(后开先关)
4. 入场自带遮罩淡入 + 弹窗上浮动画
5. 底部操作按钮放进 footer(如取消/确定),需要更宽内容用 size: 'lg',窄确认框用 'sm'
6. 打开后 Tab 焦点锁在弹窗内,关闭自动把焦点还给打开前的元素
现场演示:apps/docs/demo.html#sec-tabs-modal
### drawer — 抽屉 Drawer
参数:
- title(必填):string —— 标题
- open(必填):Signal<boolean> —— 开关信号:true 滑出 / false 收起
- placement(默认 right):'left' | 'right' —— 滑出方向;缺省 right
- width(默认 320):number —— 面板宽度像素(窄屏最多 92vw);缺省 320
- footer:unknown —— 底部操作区内容;缺省不渲染
- onClose(回调):() => void —— 关闭回调(点遮罩 / × / Esc 触发)
- ...children:unknown[] —— 面板内容子节点
示例:
```
const open = signal(false); drawer({ title: '详情', open, width: 360, onClose: () => open(false) }, h('p', null, '内容'))
```
手把手:
1. 创建布尔信号 open = signal(false),传给 drawer;其余参数为面板内容子节点
2. 打开后从侧边滑出:placement: 'right'(默认)或 'left',width 控制宽度(窄屏自动收到 92vw)
3. 点遮罩、× 或按 Esc 关闭并写回 false;打开时焦点落在面板首个可交互元素,Esc 只关最上层弹层
4. 底部操作按钮放进 footer;与 modal 的分工:modal 是强打断的确认,drawer 保留主视图上下文做详情/编辑
5. 面板内容与 modal 一样接受任意 DOM 与响应式函数
现场演示:apps/docs/demo.html#sec-drawer
### form — 表单 Form / 表单字段 FormItem
参数:
- layout(默认 vertical):'vertical' | 'horizontal' —— 布局方向;缺省 vertical(horizontal 为标签左置)
- submitText:string —— 提交按钮文案;缺省「提交」
- resetText:string —— 重置按钮文案;缺省不渲染
- onSubmit(回调):() => void —— 全部字段校验通过后的提交回调
- (formItem)label(必填):string —— 字段标签
- (formItem)value(必填):Signal<string> —— 字段值信号,校验数据源
- (formItem)required:boolean —— 必填:显示红星并自动追加非空规则
- (formItem)rules:FormRule[] —— 校验规则:required / min / max / pattern / validator,按序执行取首个失败
- (formItem)validateOn(默认 blur):'input' | 'blur' | 'submit' —— 校验时机;缺省 blur(输入时自动清已报错误)
示例:
```
const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])
```
手把手:
1. 为每个字段创建 signal 与控件,如 const name = signal(''); textField({ value: name })
2. 用 formItem 包住控件:formItem({ label, required, value, rules }, control),返回 { el, validate } 句柄
3. 把句柄数组交给 form:form({ onSubmit }, items),form 渲染全部字段 + 提交/重置按钮
4. 提交时逐项校验:全过触发 onSubmit;有错则标红控件、显示错误文案并聚焦首个出错项
5. 规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)
6. 非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)
7. 错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致
8. 触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验
9. 单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type="submit",不会与点击回调重复触发
10. rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖
11. formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交
现场演示:apps/docs/demo.html#sec-formlab
### dataTable — 数据表格 DataTable
参数:
- data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey
- columns(必填):ColumnDef<T>[] —— 列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应
- filterKey:string —— 工具栏筛选的列;缺省取第一列
- pageSize(默认 5):number —— 每页行数;缺省 5
- selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false
- page:Signal<number> —— 受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动
- loading:boolean | Signal<boolean> —— 加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy
- selected:Signal<RowKey[]> —— 受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写
示例:
```
dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })
```
手把手:
1. 准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)
2. 定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕
3. 列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)
4. 列宽用内核字段 width:数字(像素)固定该列,'flex' 或不设则按内容自适应
5. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数
6. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失
7. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行
8. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供
9. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复
10. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
11. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)
现场演示:apps/docs/demo.html#sec-table
### timeline — 时间线 Timeline
参数:
- items(必填):TimelineItem[] —— 条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant
- mode(默认 left):'left' | 'right' —— 轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右
- pending(默认 false):boolean —— 末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false
示例:
```
timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })
```
手把手:
1. 事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后
2. variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)
3. time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析
4. 流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失
5. 需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像
6. content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合
现场演示:apps/docs/demo.html#sec-timeline
### segmented — 分段控制器 Segmented
参数:
- options(必填):OptionItem[] —— 分段选项列表(互斥),每项含 value / label / disabled
- value(必填):Signal<string> —— 当前选中值信号,双向绑定(点击写回,外部赋值即同步滑块)
- size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md
- disabled:boolean | Signal<boolean> —— 整组禁用:布尔直接置位,传信号则动态同步;单项禁用走 options[].disabled
- onChange(回调):(value: string) => void —— 切换回调(禁用项与重复点击当前项都不触发)
示例:
```
segmented({ options: [{ value: 'day', label: '日' }, { value: 'week', label: '周' }], value: range })
```
手把手:
1. 和 tabs 的分工:tabs 是页签(下划线指示条 + 面板语义),segmented 是紧凑互斥切换(视图 / 单位 / 时间粒度)
2. value 必须传信号:点击写回信号,外部改信号滑块自动平移,不要在回调里另存一份状态
3. options[].disabled 置灰单项;disabled 传布尔或信号则整组失效(连滑块一起置灰)
4. 键盘:←→ 在可用项间环形移动并选中,Tab 只落在当前项上(漫游 tabindex)
5. 滑块几何由组件测量按钮位置得到,不要在外部改按钮宽度 / 内边距,否则需自行重渲染
6. 需要带图标或更复杂布局的分段,改用 radioGroup + 自定义样式
现场演示:apps/docs/demo.html#sec-segmented
### descriptions — 描述列表 Descriptions
参数:
- items(必填):DescriptionItem[] —— 描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数)
- column(默认 3):1 | 2 | 3 | 4 —— 桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列
- bordered(默认 false):boolean —— 表格形态(带分隔线与字段名底纹);缺省 false 纯网格
- size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md
- title:string —— 分组标题;缺省不渲染标题行
示例:
```
descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] })
```
手把手:
1. 详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验)
2. column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行
3. value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容
4. bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌
5. 窄屏自动降为单列,无需在调用方写媒体查询
6. 纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value
现场演示:apps/docs/demo.html#sec-descriptions
### statistic — 统计数值 Statistic
参数:
- title:string —— 指标名(小字灰标题);缺省不渲染
- value(必填):Signal<number> —— 数值信号:变化时从当前显示值滚动到新值,首跑即入场动画
- precision(默认 0):number —— 小数位,千分位分隔自动带;缺省 0
- prefix:string —— 数字前缀(如 ¥);缺省不渲染
- suffix:string —— 数字后缀(如 元 / %);缺省不渲染
- trend:'up' | 'down' —— 环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染
示例:
```
const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })
```
手把手:
1. value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本
2. 首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改
3. precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化
4. trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织
5. 多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格
6. 用户开启「减弱动效」时组件直接落终值,无需调用方判断
现场演示:apps/docs/demo.html#sec-statistic
### list — 列表 List
参数:
- items(必填):ListItem[] —— 条目列表,每项 { title, description?, avatar?, extra? }
- header:string —— 列表头标题;缺省不渲染
- footer:string —— 列表尾说明;缺省不渲染
- bordered:boolean —— 是否带外边框与分隔线;缺省 false
- emptyText:string —— 无数据占位文案;缺省「暂无数据」
- onItemClick(回调):(item: ListItem, index: number) => void —— 条目点击回调;提供后条目可点击并带 hover 反馈
示例:
```
list({ header: '待办', bordered: true, items: [{ title: '报销单 RB-2048', description: '等待复核', avatar: avatar({ name: '王' }) }], onItemClick: (item) => console.log(item.title) })
```
手把手:
1. 准备 items 数组,每项至少给 title,description 作次要说明
2. avatar 传头像组件(如 avatar({ name }))、extra 传右侧附加(如 tag(...)),不传该侧不留位
3. 需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线
4. items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖
现场演示:apps/docs/demo.html#sec-list
### carousel — 轮播 Carousel
参数:
- items(必填):unknown[] —— 每张幻灯片内容,顺序即播放顺序
- index:Signal<number> —— 当前下标信号;不传则内部维护
- autoplay:number —— 自动前进间隔(毫秒);缺省不自动播放
- height:number —— 固定高度像素;缺省由内容撑开
- label:string —— 区域无障碍名称;缺省「轮播」
- onChange(回调):(index: number) => void —— 切换回调:点击指示点/箭头/键盘/自动前进都会触发
示例:
```
const i = signal(0); carousel({ items: [h('div', null, 'A'), h('div', null, 'B')], index: i, height: 150, autoplay: 4000, onChange: (k) => console.log(k) })
```
手把手:
1. items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序
2. index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护
3. height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停
4. 键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu
5. 指示点与箭头都可点击,末张下一张环形回到首张
现场演示:apps/docs/demo.html#sec-carousel
### tree — 树形 Tree
参数:
- nodes(必填):TreeNode[] —— 根节点列表,每项 { key, label, children?, disabled? }
- expanded:Signal<string[]> —— 展开的 key 集合;不传则内部维护
- selected:Signal<string> —— 选中 key 信号;不传则内部维护
- defaultExpandAll:boolean —— 首屏展开全部分支;缺省 false
- onSelect(回调):(node: TreeNode) => void —— 选中回调:点击标签或 Enter/空格触发
示例:
```
const sel = signal(''); tree({ nodes: [{ key: 'src', label: 'src', children: [{ key: 'ui', label: 'ui' }] }], selected: sel, onSelect: (n) => console.log(n.key) })
```
手把手:
1. 节点树用 { key, label, children } 描述,key 唯一即可(选中与展开都按 key 记)
2. 传 selected 信号即可外部受控;不传则组件内部维护选中态
3. 点箭头只展开/收起,点标签才选中;叶子节点箭头位置留空以对齐
4. 键盘:↑↓ 在可见节点间移动、Home/End 到首末行(只移动焦点),Enter/空格 选中、→ 展开、← 收起;disabled 节点点击与键盘都不响应
5. defaultExpandAll 首屏展开全部分支(外部已给 expanded 初值时以外部为准)
现场演示:apps/docs/demo.html#sec-tree
### upload — 文件选择 Upload
参数:
- accept:string —— 接受的类型(同原生 accept),如 'image/*,.pdf'
- multiple:boolean —— 允许多选;缺省 false(单选时新选择覆盖旧的)
- hint:string —— 提示文案;缺省「点击或拖拽文件到此处」
- disabled:boolean —— 禁用:不可点选、不接受拖拽
- onChange(回调):(files: File[]) => void —— 文件变化回调:选择/拖拽/移除后都触发,回传副本
示例:
```
upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onChange: (files) => console.log(files.map((f) => f.name)) })
```
手把手:
1. 组件只做「选文件 + 回传」,onChange 里拿 File[] 自行决定上传接口(fetch/FormData 都行)
2. multiple: true 允许多选,重复的文件(同名同大小)自动跳过;单选模式下新选择覆盖旧文件
3. accept 透传原生 accept 限制可选类型,但仍需在 onChange 里二次校验扩展名
4. 键盘:区域可 Tab 聚焦,Enter/空格 打开选择器;拖拽时区域高亮,拖出恢复
5. disabled 时不可点选、不接受拖拽;已选清单的 × 会触发 onChange 回传剩余文件
现场演示:apps/docs/demo.html#sec-upload
### notification — 通知卡片 Notification
参数:
- title(必填):string —— 通知标题
- description:string —— 补充说明;缺省不渲染
- variant:'info' | 'success' | 'warning' | 'danger' —— 语义色(左侧条与圆点);缺省 info
- duration:number —— 自动关闭毫秒;缺省 0 = 常驻不自动关
- closable:boolean —— 是否显示关闭按钮;缺省 true
- action:unknown —— 操作区内容(如按钮);缺省不渲染
示例:
```
const { close } = notification({ title: '导出完成', description: 'report.csv 已开始下载', variant: 'success', duration: 4000 }); // close() 手动关闭
```
手把手:
1. 调用即入列:右下角堆叠,同屏最多 3 条,超出自动挤掉最早一条
2. 缺省常驻(duration 0),需要自动消失就传毫秒数,如 duration: 4000
3. 返回 { el, close } 句柄:程序化关闭用 close(),需要自己挂交互就用 el
4. action 放操作区(常见为按钮);variant 决定左侧条与圆点语义色
5. 与 toast 的分工:toast 右上角轻量瞬时,notification 右下角可长读、可操作
现场演示:apps/docs/demo.html#sec-notification
### result — 结果页 Result
参数:
- status:'success' | 'warning' | 'danger' | 'info' —— 终态语义色(图标底色);缺省 info
- title(必填):string —— 主标题
- description:string —— 补充说明;缺省不渲染
- extra:unknown —— 操作区内容(常见为主次按钮);缺省不渲染
示例:
```
result({ status: 'success', title: '提交成功', description: '工单 #2049 已受理', extra: button({ label: '查看工单', variant: 'primary', onClick: () => go() }) })
```
手把手:
1. status 决定图标底色(success 绿 / warning 黄 / danger 红 / info 蓝),整体居中排布
2. title 必填;description 用于说明原因或下一步,超过一行自动居中换行
3. extra 放操作区,常见为主按钮 + 幽灵按钮;不传则不渲染
4. 组件自带 role="status",插入页面后读屏会播报终态文案
现场演示:apps/docs/demo.html#sec-result
### transfer — 穿梭框 Transfer
参数:
- dataSource(必填):TransferItem[] —— 全量条目,左右两栏都按是否在 targetKeys 里派生
- targetKeys:Signal<string[]> —— 右侧集合信号;不传则组件内部维护,传了即外部受控
- titles:[string, string] —— 左右栏标题;缺省「待选列表 / 已选列表」
- searchable:boolean —— 是否显示搜索框;缺省 true,逐栏独立过滤
- height:number —— 列表区固定高度像素;缺省走样式表 220,超出滚动
- onChange:(keys: string[]) => void —— 右侧集合变化回调(搬运后触发,传副本)
示例:
```
transfer({ targetKeys: enabled, titles: ['待选', '已启用'], onChange: (keys) => toast.info(`已启用 ${keys.length} 项`), dataSource: [{ key: 'a11y', label: '无障碍语义' }, { key: 'ssr', label: 'SSR(暂不可用)', disabled: true }] })
```
手把手:
1. dataSource 是唯一数据源,条目归属由 targetKeys 决定;搬运只改 targetKeys,不删条目
2. 两栏独立勾选,勾选后点中间 → / ← 整体搬运,搬完两栏勾选自动清空;无勾选时按钮自动禁用
3. 每栏均有搜索框与全选:全选只覆盖当前搜索结果里未禁用的条目,部分勾选时全选框显示半选
4. disabled 条目不可勾选、不计入全选基数、也不会被搬运
5. 传入 targetKeys 信号即为受控用法,外部写入即可程序化搬运(如「一键全选」)
现场演示:apps/docs/demo.html#sec-transfer
### cascader — 级联选择 Cascader
参数:
- options(必填):CascaderOption[] —— 根级选项,children 递归出列,value 逐级拼成路径
- value:Signal<string[]> —— 选中路径信号;不传则内部维护,传了即外部受控
- placeholder:string —— 未选时占位文案;缺省「请选择」
- separator:string —— 路径分隔符;缺省「 / 」
- changeOnSelect:boolean —— 允许停在中间层;缺省 false 必须选到叶子才提交
- disabled:boolean | Signal<boolean> —— 禁用触发器;支持信号响应式禁用
- onChange:(path: string[]) => void —— 选中回调(叶子或中间层,传副本)
示例:
```
cascader({ value: areaPath, changeOnSelect: true, options: [{ value: 'east', label: '华东', children: [{ value: 'sh', label: '上海' }] }] })
```
手把手:
1. 传入嵌套 options,第一列是根级选项,悬停带 children 的项即展开下一列
2. 点击叶子提交;changeOnSelect 为 true 时点中间层也提交(适合「省/市」这类粗粒度就够用的场景)
3. value 是路径数组(逐级 value),传信号即受控;触发器文案按 separator 拼接已提交路径
4. 键盘:↑↓ 同列移动、→ 下钻、← 回退、Enter/空格 提交、Esc 收起(与弹窗共用一个 Esc 栈)
5. 面板空间不足时自动向上翻转;disabled 选项不可点也不会展开下一列
现场演示:apps/docs/demo.html#sec-cascader
### splitter — 可拖拽分栏 Splitter
参数:
- panes(必填):unknown[] —— 各栏内容(节点或纯文本),顺序即排列顺序
- direction:'horizontal' | 'vertical' —— 分割方向;缺省 horizontal(左右)
- sizes:Signal<number[]> —— 各栏百分比数组(和为 100);不传则内部均分
- min:number —— 每栏最小百分比;缺省 10,拖到边界即停
- onChange:(sizes: number[]) => void —— 拖拽 / 键盘调整后回调(传副本)
示例:
```
splitter({ sizes: ratio, min: 15, panes: [leftPanel, rightPanel], onChange: (s) => ratio([...s]) })
```
手把手:
1. panes 长度决定栏数与分隔条数(n 栏对应 n-1 条),栏宽由 sizes 百分比驱动
2. 指针按住分隔条拖动即可改比例,拖动过程两栏之和恒定、各自不低于 min
3. 分隔条可 Tab 聚焦,← → / ↑ ↓ 每次调 2%,按住 Shift 调 10%;双击复位为均分
4. direction 传 vertical 变为上下分栏,键盘方向键与光标同步切换
5. 传入 sizes 信号即受控,外部写入可做「预设布局」切换(如 1:1 / 2:1)
现场演示:apps/docs/demo.html#sec-splitter
### autoComplete — 自动补全 AutoComplete
参数:
- options(必填):AutoCompleteOption[] | ((q: string) => AutoCompleteOption[]) —— 候选项:静态列表或按关键字计算的函数(可远程联想)
- value:Signal<string> —— 输入值信号;不传则内部维护,传了即外部受控
- placeholder:string —— 占位文案,同时作为无障碍名称
- emptyText:string —— 无匹配时的提示文案;缺省「无匹配项」
- maxItems:number —— 最多展示条数;缺省 8
- disabled:boolean | Signal<boolean> —— 禁用输入与弹层
- onInput:(query: string) => void —— 每次输入回调,适合触发远程请求
- onSelect:(option: AutoCompleteOption) => void —— 选中回调(点击或 Enter)
示例:
```
autoComplete({ value: acValue, placeholder: '输入组件名', options: [{ value: 'dataTable', hint: '表格' }], onSelect: (o) => toast.success(o.value) })
```
手把手:
1. 输入任何字符即按包含匹配(忽略大小写)过滤候选,空串时弹层不展开
2. ↑↓ 在候选间环形移动并高亮,Enter 选中,Esc 收起,Tab 失焦也会收起
3. 候选行支持右侧 hint(分类 / 拼音),选中后值写回输入框并触发 onSelect
4. options 传函数时每次输入都会用当前关键字调用,可在此发起远程请求并返回新数组
5. value 传信号即受控:外部写入会同步回填输入框内容
现场演示:apps/docs/demo.html#sec-autocomplete
### datePicker — 日期选择 DatePicker
参数:
- value:Signal<string> —— 选中日期(YYYY-MM-DD)信号;不传则内部维护
- placeholder:string —— 未选时占位文案;缺省「请选择日期」
- min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点
- max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点
- disabled:boolean | Signal<boolean> —— 禁用触发器
- onChange:(date: string) => void —— 选中回调(点击日期或 Enter)
示例:
```
datePicker({ value: dateVal, min: '2026-10-20', max: '2026-10-31', onChange: (d) => toast.success(d) })
```
手把手:
1. 点触发器展开当月网格,打开时视图自动对准已选日期(无选中则今天)
2. ‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色
3. 键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页
4. min / max 为闭区间,越界日期置灰不可点也不可键盘选中
5. value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)
现场演示:apps/docs/demo.html#sec-datepicker
### backTop — 返回顶部 BackTop
参数:
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
- threshold:number —— 滚动超过多少像素才浮现;缺省 300
- progress:boolean —— 是否显示滚动进度环;缺省 true
- onClick:() => void —— 回顶之后的回调(适合埋点)
示例:
```
backTop({ threshold: 300, onClick: () => toast.info('已回到顶部') })
```
手把手:
1. 把返回的元素挂到页面任意位置即可,组件自身 position: fixed 定位在右下角
2. 滚动越过 threshold 才浮现(淡入 + 上移微动画),回到阈值内自动隐藏
3. progress 为 true 时外环弧长等于已滚比例;内滚容器用 target 传入该元素
4. 点击平滑回顶;系统开启「减少动效」时改为瞬间跳转
现场演示:apps/docs/demo.html#sec-backtop
### colorPicker — 颜色选择 ColorPicker
参数:
- value:Signal<string> —— 颜色值信号(CSS 颜色串,支持 var(--ua-*));不传则内部维护
- presets:string[] —— 预设色板;缺省取语义令牌色(accent/success/warning/danger/text/border)
- placeholder:string —— 未选时占位文案;缺省「请选择颜色」
- disabled:boolean | Signal<boolean> —— 禁用触发器
- onChange:(color: string) => void —— 选定回调(点色板、取色器或手输回车)
示例:
```
colorPicker({ value: colorVal, onChange: (c) => toast.success(c) })
```
手把手:
1. 点触发器展开弹层:上方是预设色板,下方是原生取色器 + 手输框
2. 预设缺省直接用 --ua-* 令牌色,所以暗色主题切换后色块自动跟着变,无需维护两套色值
3. 点色块即提交;手输框支持任意 CSS 颜色串(含 var(--ua-color-accent)),回车提交
4. 触发器左侧色块始终预览当前值,点击外部或 Esc 收起
现场演示:apps/docs/demo.html#sec-colorpicker
### tour — 引导漫游 Tour
参数:
- steps(必填):TourStep[] —— 步骤序列:每步含 target 选择器、title、可选 description
- padding:number —— 聚光区相对目标的外扩像素;缺省 6
- onFinish:() => void —— 走完最后一步的回调
- onClose:() => void —— 中途跳过(× 或 Esc)的回调
示例:
```
const t = tour({ steps: [{ target: '#sec-tour', title: '引导漫游', description: '按选择器定位' }], onFinish: () => toast.success('完成') }); document.body.append(t.el); t.start();
```
手把手:
1. tour() 返回 { el, start, close } 句柄:el 必须挂到 body(fixed 定位,不占布局)
2. start() 从第一步播放,可重复调用重播;close() 立即退出并按跳过处理
3. 聚光框按 target 选择器定位并外扩 padding,其余区域由超大 box-shadow 压暗(省掉独立遮罩层)
4. 卡片优先落在目标下方,空间不足自动翻到上方,左右夹在视口内;滚动 / 缩放会实时重算
5. 末步按钮文案变「完成」并触发 onFinish;× 或 Esc 触发 onClose,焦点归还开启前的元素
现场演示:apps/docs/demo.html#sec-tour
### watermark — 水印 Watermark
参数:
- text(必填):string —— 水印文字,支持 \n 多行
- opacity:number —— 透明度;缺省 0.12
- rotate:number —— 旋转角度(度);缺省 -20
- gap:number —— 平铺间距像素;缺省 140
- fontSize:number —— 字号像素;缺省 14
- color:string —— 颜色(CSS 串或 var(--ua-*) 令牌);缺省取 --ua-color-text-muted 的实色
- children:...unknown[] —— 被水印覆盖的内容,作为第二个及之后的实参传入
示例:
```
watermark({ text: '内部资料\n仅限内网', gap: 160 }, card({ title: '受保护内容' }, h('p', null, '正文')))
```
手把手:
1. 第一个参数是配置,其后所有实参都被当作内容渲染在水印层下方
2. 组件用一块 gap 见方的 canvas 画单个旋转单元,再 background-repeat 铺满,因此不需要监听尺寸变化
3. color 传 var(--ua-*) 时会先解析成计算值(canvas 不认 CSS 变量),所以暗色主题下颜色会自动跟随
4. 覆盖层为 pointer-events:none,下方按钮/输入框照常可点;watermark 根节点自带圆角与裁剪
现场演示:apps/docs/demo.html#sec-watermark
### checkboxGroup — 多选组 CheckboxGroup
参数:
- options(必填):CheckOption[] —— 选项集合:value / label / 可选 disabled
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
- min:number —— 最少选几项:到下限后已选项禁用,防止取消
- max:number —— 最多选几项:到上限后未选项禁用
- checkAll:boolean —— 是否显示全选行(含半选);缺省 false
- direction:'horizontal' | 'vertical' —— 排列方向;缺省 horizontal
- disabled:boolean | Signal<boolean> —— 整组禁用
- onChange:(values: string[]) => void —— 变更回调(传副本)
示例:
```
checkboxGroup({ value: permPick, min: 1, max: 3, checkAll: true, options: [{ value: 'read', label: '读取' }, { value: 'write', label: '写入' }] })
```
手把手:
1. options 顺序即渲染顺序;勾选后把整份数组写回 value 信号并触发 onChange
2. max 生效时未选项自动禁用,min 生效时已选项自动禁用——用禁用表达边界,不抛错也不静默回滚
3. checkAll 行的选中/半选只统计未禁用项,禁用项不计入基数
4. direction 传 vertical 改为纵向排列;disabled 支持信号,整组响应式禁用
现场演示:apps/docs/demo.html#sec-checkgroup
### affix — 固钉 Affix
参数:
- offset:number —— 距视口顶部多少像素后转为固定;缺省 0
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
- children:...unknown[] —— 被固定的内容,作为第二个及之后的实参传入
示例:
```
affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正文')))
```
手把手:
1. 把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed
2. 滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳
3. 固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合
4. 内滚容器场景把该元素传给 target,判定改为基于它的滚动位置
现场演示:apps/docs/demo.html#sec-affix
### multiSelect — 多选下拉 MultiSelect
参数:
- options(必填):CheckOption[] —— 选项集合:value / label / 可选 disabled
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
- placeholder:string —— 未选时占位文案;已选时让位给标签
- max:number —— 最多选几项:到上限后未选项自动禁用
- searchable:boolean —— 是否可输入过滤;缺省 true
- disabled:boolean | Signal<boolean> —— 禁用整个下拉
- onChange:(values: string[]) => void —— 变更回调(传副本)
示例:
```
multiSelect({ value: tagPick, max: 3, placeholder: '选择技术栈', options: [{ value: 'ts', label: 'TypeScript' }, { value: 'css', label: 'CSS 令牌' }] })
```
手把手:
1. 选中项以标签形式回显,点标签上的 × 或空输入时按退格即可删除末个标签
2. 点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)
3. 点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点
4. ↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)
5. value 传数组信号即受控,外部写入会同步标签区
现场演示:apps/docs/demo.html#sec-multiselect
API 速览:
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮
- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用,空间不足自动翻转,键入字母快跳选项;searchable 开启弹层搜索框
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭,空间不足自动翻转,键入字母快跳菜单项
- flip(trigger, panel, preferred):浮层上下翻转判定,按触发器与面板的视口几何决定 preferred 方位放不下时是否翻转,返回实际生效方位 'top' | 'bottom'
- radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用
- accordion(props):折叠面板,标题按钮 + grid 行高过渡,单开(手风琴)或多开,支持禁用面板与 active 信号受控
- breadcrumb(props):面包屑,nav + ol 语义结构,末项为当前页(aria-current),有 href 走原生链接、无 href 交给 onNavigate
- card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger
- tag(props):可关闭标签,variant 五档语义色,closable + onClose 支持从数据源移除(纯展示请用 badge)
- avatar(props):头像,有 src 渲染图片(alt 用名称)、无 src 用首字占位,size 可调 + 四档语义底色
- alert(props):提示条,variant 四档语义色 + 可选 title 与 closable 关闭按钮,role=alert 读屏可播报
- toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失(悬停暂停、移出续时),出入场微动画;showToast(props) 为完整入参版本(closable 显示关闭按钮,同屏最多 4 条)
- tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起;视口一侧空间不足时自动翻转
- popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转
- skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画
- pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数
- spinner(props):加载指示器,size 控制直径
- progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配
- empty(props):空状态占位,title/description 可选,action 放操作区
- divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案)
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
- list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态
- carousel(props):轮播,items 横排位移,指示点/箭头/←→ 键切换,autoplay 毫秒自动前进且悬停暂停,index 信号可外部受控
- tree(props):树形,递归节点 + 箭头展开/收起 + 点选节点,键盘 Enter 选中、→ 展开、← 收起,禁用节点不响应
- upload(props):文件选择,点击或拖拽入列,清单可逐个移除,onChange 回传文件数组(不内置上传请求)
- notification(props):右下角通知卡片(标题 + 描述 + 操作区),返回 { el, close } 句柄,缺省常驻需手动关闭
- result(props):结果页,status 四档语义色 + 标题 + 说明 + 操作区,用于提交完成/校验失败等终态
- transfer(props):穿梭框,左右两栏勾选后整体搬运,targetKeys 信号双向绑定,两栏各自搜索与全选(含半选),禁用条目不参与搬运也不可勾选
- cascader(props):级联选择,多列联动面板(悬停下钻 / 点击提交),value 为路径数组信号,changeOnSelect 允许停在中间层,全键盘可操作
- splitter(props):可拖拽分栏容器,分隔条支持指针拖拽 / 方向键微调 / 双击复位,sizes 为百分比数组信号
- autoComplete(props):输入联想,输入即过滤候选、↑↓ 环形高亮 / Enter 选中 / Esc 收起,options 可传函数做远程联想
- datePicker(props):日期选择,单月网格 + 翻月 / 今天快捷键,value 为 YYYY-MM-DD 信号,min/max 闭区间限制,方向键逐日逐周可导航
- backTop(props):返回顶部悬浮按钮,越过阈值才浮现,外环 conic-gradient 显示已滚比例,点击平滑回顶
- colorPicker(props):颜色选择,预设色板(缺省语义令牌色)+ 原生取色器 + 手输 CSS 颜色串,value 为颜色串信号
- tour(props):引导漫游,返回 { el, start, close } 句柄;聚光框按 target 选择器定位,卡片自动选方位,Esc / 跳过走 onClose
- watermark(props, ...children):内容容器 + 平铺水印覆盖层,canvas 生成单元、颜色取自令牌,覆盖层不挡交互
- checkboxGroup(props):多选组,value 为数组信号,min/max 用禁用边界项实现(不抛错),checkAll 支持半选且只统计未禁用项
- affix(props, ...children):固钉,滚过 offset 后内容转 fixed 贴顶、外层保留占位高度,固定后宽度随容器自适应
- multiSelect(props):多选下拉,标签式回显 + 可搜索弹层,点选不收起便于连续多选,max 到限后未选项禁用
一行用法:
```
const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')
```
约定:
- 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
- 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
- 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
- 无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*
---
## @ui-arch/frame(headless-core)v0.1.0
零依赖微框架:组件 = 返回 DOM 的普通函数。signal/effect/computed 管状态,h() 管 DOM,无虚拟 DOM 无生命周期抽象。
API 速览:
- signal(initial) -> Signal<T>:无参读值并收集依赖,传参写值并触发订阅
- effect(fn):立即执行并追踪依赖,信号变化自动重跑
- computed(fn):由信号派生的只读计算值
- h(tag, props, ...children):hyperscript;on* 绑事件,函数值属性/子节点自动响应式
- renderList(el, fn):信号变化时整体替换容器子节点
- mount(target, view):挂载视图
一行用法:
```
const count = signal(0); mount('#app', () => h('button', { onClick: () => count(count() + 1) }, () => `count = ${count()}`))
```
约定:
- 组件 = 返回 DOM 节点的普通函数,不做类组件与生命周期抽象
- 跨系统状态(如 table-core)用桥接信号:subscribe 里递增 version 信号
---
## @ui-arch/table-core(headless-core)v0.1.0
Headless 表格内核:不渲染任何 DOM,只管理状态与派生。任何渲染层通过 getVisibleRows() 拿行模型,配 subscribe() 刷新。
API 速览:
- createTable(options) -> TableInstance: create headless table instance
- sortPlugin(options?) -> multi-column sorting plugin (asc -> desc -> off cycle)
- filterPlugin(options?) -> column filter plugin (default substring match, custom predicate supported)
- paginationPlugin() -> client-side pagination with page clamping, exposes setPage/nextPage/prevPage/getPageInfo
- selectionPlugin() -> rowKey-based selection, exposes selectAllVisible
一行用法:
```
const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()], initialState: { pagination: { page: 1, pageSize: 20 } } }); table.dispatch({ type: 'toggleSort', key: 'name' }); table.getVisibleRows()
```
约定:
- selection/expanded must store rowKey, never row index
- derive pipeline order is fixed: filter -> sort -> paginate
- actions are the only way to mutate state; state is immutable
---
## @ui-arch/tokens(design-tokens)v0.1.0
设计令牌唯一来源:CSS 变量 --ua-* + 同名 JSON。皮肤只允许消费变量,禁止硬编码颜色;暗色只换值不换名。
API 速览:
- tokens.css: import '@ui-arch/tokens/tokens.css' once at app root
- tokens.json: programmatic access for tools and agents
一行用法:
```
import '@ui-arch/tokens/tokens.css'; then use var(--ua-color-accent) etc. in any skin package
```
---
## @ui-arch/cli(tooling)v0.1.0
工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。
API 速览:
- ua index:扫描 packages/ 两层目录的 ua.manifest.json,生成 .ua/index.json 与 llms.txt
- ua list:列出注册表内所有包与组件数
- ua info <包名|组件id>:包名返回摘要,组件 id 返回 props 表 + 步骤 + 示例;--json 输出原始清单
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
- ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
一行用法:
```
node packages/tools/cli/bin/ua.js <index|list|info <包名|组件id>|docs|tokens|style|verify|commit-lint|mcp>;仓库内等价于 npm run index / docs / style / verify
```
约定:
- 零依赖:只用 node 标准库,Node >= 18
- 清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生
- 门禁只增不改:新增校验必须能被人手复现,报错要指出文件与字段
---
## 设计令牌(--ua-*)
- --ua-color-bg: #ffffff(暗色 #101216)
- --ua-color-surface: #f6f7f9(暗色 #1a1c22)
- --ua-color-text: #1c1e21(暗色 #e7e9ec)
- --ua-color-text-muted: #6b7280(暗色 #888780)
- --ua-color-border: rgba(0, 0, 0, 0.15)(暗色 rgba(255, 255, 255, 0.15))
- --ua-color-accent: #185fa5(暗色 #378add)
- --ua-color-accent-contrast: #ffffff(暗色 #042c53)
- --ua-color-danger: #a32d2d(暗色 #d96a6a)
- --ua-color-success: #0f6e56(暗色 #4fc39e)
- --ua-color-warning: #9a6b1f(暗色 #d9a94f)
- --ua-radius-sm: 4px
- --ua-radius-md: 8px
- --ua-radius-lg: 12px
- --ua-space-1: 4px
- --ua-space-2: 8px
- --ua-space-3: 12px
- --ua-space-4: 16px
- --ua-space-table-cell: var(--ua-space-2) var(--ua-space-3)
- --ua-font-size-sm: 12px
- --ua-font-size-md: 13px
- --ua-font-size-lg: 14px
皮肤包只允许消费这些变量:写死 #hex 会被 verify 拦截,拼错变量名会被令牌引用校验拦截。
# 组合配方(Recipes)
> 单个组件的参数表说明「怎么用」,这些配方说明「怎么拼」。所有配方都只消费组件对外 API,不依赖内部文件。
## 配方 1:表单校验 + 提交反馈
```ts
const name = signal('');
const email = signal('');
const formRef = form({ onSubmit: (ok) => (ok ? result({ status: 'success', title: '已保存' }) : toast.error('请检查表单')) }, [
formItem({ label: '名称', value: name, required: true, rules: [{ min: 2 }] }, textField({ value: name })),
formItem({ label: '邮箱', value: email, required: true, rules: [{ pattern: /^\S+@\S+$/ }] }, textField({ value: email })),
]);
```
要点:
- 校验规则挂在 `formItem`,值信号只有一个来源(`value` 同时给 `formItem` 与控件),避免两处各存一份。
- 提交结果用 `toast`(轻量、瞬时)或 `result`(整页终态)表达,不要两者同时弹。
## 配方 2:危险操作二次确认
```ts
const open = signal(false);
const dangerOpen = (key: string) => {
open(true);
return modal({
title: `确认删除 ${key}?`,
open,
size: 'sm',
onClose: () => open(false),
footer: h('div', { class: 'row' },
button({ label: '取消', variant: 'ghost', onClick: () => open(false) }),
button({ label: '删除', variant: 'danger', onClick: () => { open(false); toast.success('已删除'); } })),
}, h('p', null, '删除后不可恢复。'));
};
```
要点:
- `modal` 自带焦点陷阱与 Esc 逐层关闭,不要在业务层再挂 Esc 监听。
- 确认按钮用 `variant: 'danger'`,取消用 `ghost`,主次分明。
## 配方 3:表格筛选 + 分页 + 批量操作
```ts
const picked = signal<string[]>([]);
const page = signal(1);
dataTable({
data: rows,
columns: [{ key: 'name', header: '名称', sortable: true }, { key: 'city', header: '城市' }],
pageSize: 10,
page, // 同一 page 信号也给 pagination,二者自动联动
selectable: true,
selected: picked,
loading: saving,
});
pagination({ page, pageSize: 10, total: rows.length, jumpable: true, onChange: (p) => page(p) });
```
要点:
- `page` 信号是表格与分页器的唯一共享状态;外部改页码前自己钳制范围(core 的 `setPage` 不做钳制)。
- 选中一律存 `rowKey`,翻页/筛选后仍然稳定;全选只作用于当前可见行。
- 批量操作按钮在 `picked().length === 0` 时禁用,避免空提交。
## 配方 4:长列表数据加载与空态
```ts
const loading = signal(true);
const items = signal<ListItem[]>([]);
list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' });
```
要点:
- 空态文案随加载态切换;列表本身会在 `items` 为空时渲染 `empty`,不需要业务层再判断一次。
- 需要骨架屏就换成 `skeleton`,加载完成再 `renderList` 成真实行。
## 通用约定
- 状态一律放信号(`signal` / `computed`),组件只读写信号,不持有业务数据副本。
- 反馈分层:`toast` 右上角瞬时、`notification` 右下角可长读、`result` 整页终态、`alert` 页内常驻。
- 样式不要写颜色与间距字面值,先跑 `ua tokens` 查 `--ua-*`(verify 会拦截未定义令牌与 `#hex`)。