# @ui-arch 组件库 Agent 文档(无图形版) > 自动生成于 2026-10-26T05:59:36.078Z,数据源:各包 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 —— 缩略图宽度像素,高度自适应;缺省走样式默认 - fallback(默认 '图片加载失败'):string —— 加载失败时缩略图内显示的占位文案;缺省「图片加载失败」 示例: ``` 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 —— 禁用态:布尔直接置位,传信号则动态同步 - onClick(回调):() => void —— 点击回调 示例: ``` button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') }) ``` 手把手: 1. 确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger 2. 绑定 onClick 回调处理点击逻辑 3. 需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染 4. 放进
里需要提交时显式传 type: "submit";缺省 button 不会触发原生提交 现场演示:apps/docs/demo.html#sec-counter ### textField — 文本输入 TextField 参数: - value:Signal —— 双向绑定信号:输入写回信号,信号变化写回输入框 - placeholder:string —— 占位文案 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onInput(回调):(value: string) => void —— 输入回调,每次按键触发 - prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 - suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 - clearable(默认 false):boolean —— 开启后有值即显示一键清空按钮;缺省 false 示例: ``` 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 —— 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步) - min:number —— 最小值;缺省无下限 - max:number —— 最大值;缺省无上限 - step(默认 1):number —— 步长;缺省 1 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - 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 —— 双向绑定信号 - rows(默认 3):number —— 初始行数;缺省 3 - placeholder:string —— 占位文案 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onInput(回调):(value: string) => void —— 输入回调 - prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 - suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 - maxCount:number —— 字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限 示例: ``` 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 —— 当前值信号,双向绑定:拖动写回,外部赋值同步位置 - min(默认 0):number —— 最小值;缺省 0 - max(默认 100):number —— 最大值;缺省 100 - step(默认 1):number —— 步长;缺省 1 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - 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 —— 勾选状态信号,双向绑定 - label:string —— 右侧文案;缺省只渲染勾选框 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - 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 —— 开关状态信号,双向绑定 - ariaLabel:string —— 无可见文案时的可访问名(如纯图标开关);缺省取 label - label:string —— 左侧文案 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - 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 —— 选中值信号,双向绑定 - placeholder:string —— 未选中时触发器显示的占位文案 - searchable:boolean —— 弹层顶部带搜索框,按标签包含匹配过滤;缺省 false - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层 - 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 —— 选中值信号,双向绑定 - disabled:boolean | Signal —— 整组禁用:全部选项与文案置灰且不可选 - 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 —— 受控展开面板信号(单开模式);缺省由组件内部持有状态 - 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(渲染 );单页应用想自行接管时不给 href,改用 onNavigate 回调 3. 改分隔符传 separator,如 '›';分隔符对读屏隐藏 4. 语义结构为 nav + ol + li,可直接放进页头或卡片标题栏 现场演示:apps/docs/demo.html#sec-breadcrumb ### card — 卡片 Card 参数: - title:string —— 标题;缺省不渲染标题行 - ...children:unknown[] —— 任意子节点:字符串 / DOM / 响应式函数 - extra:unknown —— 标题行右侧操作区(按钮 / 统计文案);缺省不渲染 - footer:unknown —— 底部操作区;缺省不渲染 - bordered:boolean —— 是否显示边框;缺省 true - hoverable:boolean —— 悬停浮起(可点击卡片);缺省 false 示例: ``` card({ title: '用户表' }, dataTable({ data, columns })) ``` 手把手: 1. card 是容器组件:第一个参数是 props,其余参数是任意子节点 2. 传 title 显示标题行,否则只有内容区 3. 子节点可以混合字符串、DOM 节点与响应式函数 4. title 与 extra 同处标题行:有 extra 时右侧对齐,没标题也能只放操作区 5. footer 渲染在内容之后的底部区,右对齐并带上分隔线,适合放主次按钮 6. bordered:false 去边框(嵌在已有边界的容器里),hoverable 让整卡悬停浮起(自带 reduced-motion 兜底) 现场演示:apps/docs/demo.html#sec-card ### badge — 徽标 Badge 参数: - label:string —— 徽标文案;与 count / dot 三选一,dot 时作可访问名 - variant:'default' | 'success' | 'warning' | 'danger' —— 语义色;缺省 default - count:number —— 数字角标:超过 max 显示 N+,负数归零 - max:number —— 数字上限;缺省 99 - dot:boolean —— 只显示小红点(无文字),role=img + label 作名称 示例: ``` badge({ label: '成功', variant: 'success' }) ``` 手把手: 1. 纯状态文案直接传 label,配 variant 定语义色 2. 要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+ 3. 只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」) 现场演示:apps/docs/demo.html#sec-feedback ### tag — 标签 Tag 参数: - label(必填):string —— 标签文案 - variant(默认 default):'default' | 'success' | 'warning' | 'danger' | 'info' —— 语义色;缺省 default - closable(默认 false):boolean —— 显示 × 关闭按钮;缺省 false(纯展示) - onClose(回调):() => void —— 关闭回调:调用方从数据源移除该项后重新渲染 - checkable(默认 false):boolean —— 可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准 - checked:Signal —— 选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持 - onChange:(checked: boolean) => 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 等容器,自动换行由容器决定 6. 需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢) 现场演示: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 —— 关闭回调,供调用方同步业务状态 - banner(默认 false):boolean —— 通栏形态:去圆角、只保留下边框、左右撑满,适合页面顶部通知 - showIcon(默认 false):boolean —— 显示语义图标(info ℹ / success ✓ / warning ! / danger ✕),纯装饰不进读屏 - icon:string | HTMLElement —— 自定义图标节点,传了即覆盖语义图标 示例: ``` alert({ message: '操作可能存在风险', variant: 'warning' }) ``` 手把手: 1. 按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误 2. 传入 message 即可,入场自带下滑动画 3. 需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色 4. 需要用户可自行消除的提示传 closable: true:点 × 淡出并从 DOM 移除,配合 onClose 同步业务状态 5. 有标题与正文两层信息时传 title(加粗首行),正文仍走 message 6. banner: true 出通栏形态(圆角与左右边框归零),页面顶部通知直接铺满容器即可,不需要额外包裹层 现场演示: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 —— 第二个参数:被包裹的触发器元素 - open:Signal —— 受控开合信号;不传则内部维护,传了外部可编程开合(popconfirm 即基于它) 示例: ``` 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 - label:string —— 读屏播报文案,如「正在加载订单」;缺省「加载中」 示例: ``` spinner(); spinner({ size: 26 }) ``` 手把手: 1. 数据加载时插入 spinner(),加载完成移除 2. 用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号) 3. 与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner 4. 区块级加载给 spinner 传 label 说明在加载什么,读屏才不会只念一句「加载中」 现场演示:apps/docs/demo.html#sec-feedback ### progress — 进度条 Progress 参数: - value:Signal —— 当前进度 0-100 的信号;不传即不确定态(未知时长的加载) - showText:boolean —— 是否右侧显示百分比;缺省 true(不确定态忽略) - label:string —— 可访问名,如「上传进度」;缺省不确定态念「进行中」 - variant:'default' | 'success' | 'warning' | 'danger' —— 填充语义色;缺省 default(满格自动切 success) 示例: ``` const pct = signal(0); progress({ value: pct }); setInterval(() => pct((pct() + 5) % 105), 400) ``` 手把手: 1. 传 value 信号给定量进度,不传则退化为往复的不确定态 2. label 写清这是什么进度,读屏会把名称与百分比一起念出来 3. 失败或告警用 variant: 'danger' / 'warning',该颜色在满格时也不被 success 覆盖 现场演示:apps/docs/demo.html#sec-feedback ### tabs — 标签页 Tabs 参数: - tabs(必填):TabItem[] —— 页签列表,每项 { value, label, disabled?, closable? } - active:Signal —— 当前激活页签 value 信号,双向绑定 - onChange(回调):(value: string) => void —— 切换回调 - onClose(回调):(value: string) => void —— 关闭页签回调:组件不自行移除,凭 value 从数据源里摘掉后重绘 - closable:boolean —— 页签项上的开关:true 时渲染关闭按钮(×),关闭按钮不进 Tab 序但可用鼠标点;缺省不可关 示例: ``` const tab = signal('a'); tabs({ tabs: [{ value: 'a', label: '概览', closable: true }, { value: 'b', label: '明细', disabled: true }], active: tab, onClose: (v) => toast.info(`关闭 ${v}`) }) ``` 手把手: 1. 创建信号 tab = signal('a'),初值为默认页签 2. tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB 3. 点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点 4. 需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘 5. 键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签 现场演示:apps/docs/demo.html#sec-tabs-modal ### stepper — 步骤条 Stepper 参数: - steps(必填):StepperItem[] —— 步骤列表,每项 { label, description? } - active(必填):Signal —— 当前步索引(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 —— 当前评分(1 基整数值)信号,双向绑定:点选/键盘写回、外部赋值同步 - max(默认 5):number —— 星标总数;缺省 5 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - 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 ### space — 间距容器 Space 参数: - direction:'horizontal' | 'vertical' —— 主轴方向;缺省 horizontal 横排 - size:'sm' | 'md' | 'lg' | number —— 子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md - align:'start' | 'center' | 'end' | 'baseline' —— 交叉轴对齐;缺省 center,按钮与文字混排不错位 - justify:'start' | 'center' | 'end' | 'between' —— 主轴分布;缺省 start,between 两端撑开 - wrap:boolean —— 空间不足是否换行;缺省 false - block:boolean —— 是否占满父容器宽度;缺省按内容收缩 示例: ``` space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...) ``` 手把手: 1. 把一排需要统一间距的元素交给 space 托管,不再各自写 margin 2. 调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字 3. 竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐 4. 工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap 现场演示:apps/docs/demo.html#sec-space ### pagination — 分页器 Pagination 参数: - page(必填):Signal —— 当前页码信号,双向绑定:外部赋值自动翻页,点击页码写回信号 - pageSize(必填):number —— 每页条数 - total(必填):number —— 总条数 - onChange(回调):(page: number) => void —— 翻页回调 - jumpable(默认 false):boolean —— 末尾显示跳页输入框(总页数 > 1 才出现);缺省 false - 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 —— 开关信号:true 显示 / false 隐藏 - title(必填):string —— 标题 - size(默认 md):'sm' | 'md' | 'lg' —— 宽度档位:sm 340px / md 420px / lg 640px;缺省 md - footer:unknown —— 底部操作区内容(常见为按钮组,右对齐);缺省不渲染 - closeOnOverlay:boolean —— 点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用) - 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 —— 开关信号:true 滑出 / false 收起 - placement(默认 right):'left' | 'right' | 'top' | 'bottom' —— 滑出方向;缺省 right - width(默认 320):number —— 面板尺寸(像素):左右滑出为宽度,上下滑出为高度;缺省 320 - footer:unknown —— 底部操作区内容;缺省不渲染 - closeOnOverlay:boolean —— 点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用) - 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 —— 字段值信号,校验数据源 - (formItem)required:boolean —— 必填:显示红星并自动追加非空规则 - (formItem)rules:FormRule[] —— 校验规则:required / min / max / pattern / validator,按序执行取首个失败 - (formItem)validateOn(默认 blur):'input' | 'blur' | 'submit' —— 校验时机;缺省 blur(输入时自动清已报错误) - (formItem)hint:string —— 控件下方辅助说明(格式提示等);报错时让位给错误文案 - onReset:() => void —— 重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮 示例: ``` 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 聚合提交 12. 需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行 现场演示:apps/docs/demo.html#sec-formlab ### dataTable — 数据表格 DataTable 参数: - data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey - columns(必填):ColumnDef[] —— 列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应 - filterKey:string —— 工具栏筛选的列;缺省取第一列 - pageSize(默认 5):number —— 每页行数;缺省 5 - selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false - page:Signal —— 受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动 - loading:boolean | Signal —— 加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy - selected:Signal —— 受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写 - exportable:boolean —— 工具栏显示「导出 CSV」:导出筛选 + 排序后的全量行(不分页);缺省 false - exportName:string —— 导出文件名;缺省 table.csv 示例: ``` 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 存) 12. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充 13. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出 现场演示: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 —— 当前选中值信号,双向绑定(点击写回,外部赋值即同步滑块) - size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md - disabled:boolean | Signal —— 整组禁用:布尔直接置位,传信号则动态同步;单项禁用走 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 —— 数值信号:变化时滚动过渡到新值;倒计时模式可不传 - countdown:number —— 目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value - 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. 用户开启「减弱动效」时组件直接落终值,无需调用方判断 7. 做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表 现场演示: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 —— 当前下标信号;不传则内部维护 - 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 —— 展开的 key 集合;不传则内部维护 - selected:Signal —— 选中 key 信号;不传则内部维护 - defaultExpandAll:boolean —— 首屏展开全部分支;缺省 false - searchable:boolean —— 顶部搜索框:按标签过滤,命中节点保留父链并自动展开;缺省 false - onSelect(回调):(node: TreeNode) => void —— 选中回调:点击标签或 Enter/空格触发 - multi:boolean —— 是否显示勾选框并开启多选;缺省 false - checked:Signal —— 勾选 key 集合(multi 时生效);不传则内部维护 - onCheck:(keys: string[]) => void —— 勾选变更回调(父子已级联,传副本) 示例: ``` 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 初值时以外部为准) 6. multi: true 时每行前置勾选框:勾父级下钻全选子孙(跳过禁用项),子孙全选后父级自动上勾、部分勾选显示半选 7. 勾选框点击不触发选中(stopPropagation),键盘在多选模式下 Enter/空格改为切换勾选 8. 节点多时开 searchable:输入即过滤,命中节点连同父链一起显示,隐藏的枝不参与方向键遍历 现场演示:apps/docs/demo.html#sec-tree ### upload — 文件选择 Upload 参数: - accept:string —— 接受的类型(同原生 accept),如 'image/*,.pdf' - multiple:boolean —— 允许多选;缺省 false(单选时新选择覆盖旧的) - hint:string —— 提示文案;缺省「点击或拖拽文件到此处」 - maxSize:number —— 单文件体积上限(字节);超限丢弃并在提示行说明,缺省不限 - maxCount:number —— multiple 时的文件数上限;超出部分丢弃并提示,缺省不限 - disabled:boolean —— 禁用:不可点选、不接受拖拽 - onChange(回调):(files: File[]) => void —— 文件变化回调:选择/拖拽/移除后都触发,回传副本 示例: ``` upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onChange: (files) => console.log(files.map((f) => f.name)) }) ``` 手把手: 1. 把 upload 放进表单,accept 限定类型、multiple 允许多选 2. 需要拦体积就给 maxSize(字节),限制数量给 maxCount 3. 被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验) 4. onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层) 现场演示:apps/docs/demo.html#sec-upload ### notification — 通知卡片 Notification 参数: - title(必填):string —— 通知标题 - description:string —— 补充说明;缺省不渲染 - variant:'info' | 'success' | 'warning' | 'danger' —— 语义色(左侧条与圆点);缺省 info - duration:number —— 自动关闭毫秒;缺省 0 = 常驻不自动关,非 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 —— 右侧集合信号;不传则组件内部维护,传了即外部受控 - 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 —— 选中路径信号;不传则内部维护,传了即外部受控 - placeholder:string —— 未选时占位文案;缺省「请选择」 - separator:string —— 路径分隔符;缺省「 / 」 - changeOnSelect:boolean —— 允许停在中间层;缺省 false 必须选到叶子才提交 - disabled:boolean | Signal —— 禁用触发器;支持信号响应式禁用 - 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 —— 各栏百分比数组(和为 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 —— 输入值信号;不传则内部维护,传了即外部受控 - placeholder:string —— 占位文案,同时作为无障碍名称 - emptyText:string —— 无匹配时的提示文案;缺省「无匹配项」 - maxItems:number —— 最多展示条数;缺省 8 - disabled:boolean | Signal —— 禁用输入与弹层 - 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 —— 选中日期(YYYY-MM-DD)信号;不传则内部维护 - placeholder:string —— 未选时占位文案;缺省「请选择日期」 - min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点 - max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点 - disabled:boolean | Signal —— 禁用触发器 - 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 —— 颜色值信号(CSS 颜色串,支持 var(--ua-*));不传则内部维护 - presets:string[] —— 预设色板;缺省取语义令牌色(accent/success/warning/danger/text/border) - placeholder:string —— 未选时占位文案;缺省「请选择颜色」 - disabled:boolean | Signal —— 禁用触发器 - 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 —— 选中值数组信号;不传则内部维护 - min:number —— 最少选几项:到下限后已选项禁用,防止取消 - max:number —— 最多选几项:到上限后未选项禁用 - checkAll:boolean —— 是否显示全选行(含半选);缺省 false - direction:'horizontal' | 'vertical' —— 排列方向;缺省 horizontal - disabled:boolean | Signal —— 整组禁用 - 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 —— 选中值数组信号;不传则内部维护 - placeholder:string —— 未选时占位文案;已选时让位给标签 - max:number —— 最多选几项:到上限后未选项自动禁用 - searchable:boolean —— 是否可输入过滤;缺省 true - disabled:boolean | Signal —— 禁用整个下拉 - 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 ### anchor — 锚点导航 Anchor 参数: - items:AnchorItem[] —— 锚点项集合,每项含 href(形如 #sec-x)、title、可选 disabled - offset:number —— 定位与高亮判定预留的顶部距离,适合有吸顶栏的页面;缺省 0 - container:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页滚动) - label:string —— nav 的无障碍名称;缺省「锚点导航」 - onChange:(href: string) => void —— 跳转后的回调,参数为命中的 href(适合埋点或联动) 示例: ``` anchor({ items: [{ href: '#sec-button', title: '按钮' }, { href: '#sec-card', title: '卡片' }], offset: 72 }) ``` 手把手: 1. 在页面任意位置放一个容器,调用 anchor 并把返回的 nav 塞进去(固定侧栏或吸顶栏均可) 2. items 里每条 href 指向页面上真实存在的区块 id,标题即链接文案;目标缺失的项在滚动判定时自动跳过 3. 页面有吸顶导航时把它的高度传给 offset,跳转与高亮判定都会预留这段距离 4. 滚动容器不是整页时用 container 传入该元素;点击走平滑滚动,系统开启「减少动效」时改为瞬移,hash 用 replaceState 回写不堆历史 现场演示:apps/docs/demo.html#sec-anchor ### popconfirm — 二次确认 Popconfirm 参数: - title(必填):string —— 气泡标题,通常写成一句问句(如「删除这条记录?」) - description:string —— 补充说明:影响范围、是否可撤销;缺省给一句通用提示 - confirmText:string —— 确认按钮文案;缺省「确定」 - cancelText:string —— 取消按钮文案;缺省「取消」 - variant:'default' | 'danger' —— 确认按钮语义色;删除类操作传 danger,缺省 default - placement:'top' | 'bottom' —— 气泡方位;缺省 top,压在触发器上方不盖住被操作的行 - onConfirm(回调):() => void —— 点确定后的回调(此时弹层已收起) - onCancel(回调):() => void —— 取消 / 点外部 / Esc 收起时的回调 - open:Signal —— 受控开合信号;不传则内部维护,传了可由外部编程开合 示例: ``` popconfirm({ title: '删除这条记录?', description: '删除后无法恢复', variant: 'danger', confirmText: '删除', onConfirm: () => toast.success('已删除') }, button({ label: '删除', variant: 'danger' })) ``` 手把手: 1. 把要保护的触发器元素(按钮 / 图标)作为第二个参数传进去,返回的包裹节点替换原位置 2. 写清 title(问句)与 description(影响范围),删除类操作把 variant 设成 danger,确认按钮会自动变成危险色 3. 点确定先收起弹层再触发 onConfirm,回调里可以放心同步改数据;取消、点外部、Esc 一律走 onCancel 4. 展开时焦点自动落在「取消」上,误按回车不会执行危险操作;需要外部控制开合时传 open 信号 现场演示:apps/docs/demo.html#sec-popconfirm 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 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签;页签项 closable 时带关闭按钮(聚焦后 Delete 同样可关),关闭只回调 onClose 由调用方摘数据 - stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳 - rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配 - empty(props):空状态占位,title/description 可选,action 放操作区 - divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线 - space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调 - 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 到限后未选项禁用 - anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器 - popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」 一行用法: ``` 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:无参读值并收集依赖,传参写值并触发订阅 - 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 |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([]); 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([]); list({ items: items(), bordered: true, emptyText: loading() ? '加载中…' : '暂无数据' }); ``` 要点: - 空态文案随加载态切换;列表本身会在 `items` 为空时渲染 `empty`,不需要业务层再判断一次。 - 需要骨架屏就换成 `skeleton`,加载完成再 `renderList` 成真实行。 ## 配方 5:数据表 + 独立分页器 + 导出 ```ts const page = signal(1); const table = dataTable({ data: rows, columns: [ { key: 'name', header: '姓名', sortable: true, width: 160 }, { key: 'city', header: '城市', align: 'center', width: 'flex' }, ], page, // 受控页码:与外部 pagination 双向联动 selectable: true, exportable: true, // 导出筛选 + 排序后的全量行(不是当前页) exportName: '用户列表.csv', }); const pager = pagination({ page, pageSize: 5, total: rows.length, jumpable: true, pageSizes: [5, 10, 20] }); ``` 要点: - 页码只保留一个来源:`dataTable` 与 `pagination` 共传同一个 `page` 信号,翻页由内核写回,桥接层会钳制越界值。 - `exportable` 导出的是「筛选 + 排序后、未分页」的全量行,列序与表头一致、单元格走 `format`;想导出当前页请自行读 `getVisibleRows()`。 - 列宽拖拽只对 `width` 给数字的列生效(`'flex'` 列交给浏览器自适应);双击手柄回到声明宽度。 - 行选择一律按 rowKey 记账,筛选换掉可见行后已选仍在,批量操作前用已选计数确认。 ## 通用约定 - 状态一律放信号(`signal` / `computed`),组件只读写信号,不持有业务数据副本。 - 反馈分层:`toast` 右上角瞬时、`notification` 右下角可长读、`result` 整页终态、`alert` 页内常驻。 - 样式不要写颜色与间距字面值,先跑 `ua tokens` 查 `--ua-*`(verify 会拦截未定义令牌与 `#hex`)。