Files
UI/packages/skin/ui/ua.manifest.json
T

4573 lines
204 KiB
JSON
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.
{
"name": "@ui-arch/ui",
"type": "skin-dom",
"description": "DOM 皮肤组件库:表单 / 反馈 / 浮层 / 导航 / 数据展示,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌(组件全量见 api.exports 或刚体清单)",
"tags": [
"components",
"dom",
"skin",
"table",
"form",
"feedback"
],
"api": {
"entry": "src/index.ts",
"exports": [
"button(props):按钮,variant 三档视觉强度,disabled / loading 支持布尔或信号动态同步(loading 转圈并禁点),onClick 点击回调",
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数",
"inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块",
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限",
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
"checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用",
"switchButton(props):开关,checked 信号双向绑定,loading 加载锁(滑块转圈 + aria-busy),disabled 支持布尔或信号禁用",
"select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用,空间不足自动翻转,键入字母快跳选项;searchable 开启弹层搜索框,clearable 有值时给清空按钮",
"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 / 键盘聚焦延迟出场,Esc 收起;触摸屏按下即出、抬手延迟 1.2s 收起,气泡 pointer-events: none 不挡点击",
"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 可配,allowHalf 开启半星(左半区 0.5 分)",
"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,展开即聚焦「取消」",
"calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange",
"typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮",
"grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列",
"floatButton(props):悬浮操作按钮,固定停靠左下 / 右下角,label 缺省为纯图标圆形,badge 走角标,tooltip 兼作 aria-label",
"mentions(props):@提及输入,textarea 内键入 @ 弹候选(名字 / 关键词过滤),↑↓ 环形移动、Enter/Tab 选中、Esc 只收本层,选中按光标替换并写回 value",
"treeSelect(props):树形选择,按钮触发弹层并复用 tree(搜索 / 多选 / 级联),单选选中即收起、多选保持展开,Esc 与点外收起",
"link(props):链接,站内跳转与站外跳转统一入口,external 自动补 ↗ 与 rel=noopener noreferrer 并给读屏播报",
"avatarGroup(props):头像组,横向叠放并在超出 max 时折叠成 +N,整组 title 列出全部姓名",
"virtualList(props):虚拟列表,固定行高长列表只渲染可视窗口 + 上下缓冲条,万级数据滚动不随总量掉帧;返回 { el, scrollTo } 句柄",
"commandPalette(props):命令面板,⌘K / Ctrl+K 唤起,输入即过滤(主文案/说明/关键词三层),↑↓ 环形选择 + 回车执行 + Esc 收起,选中与关闭都归还焦点;返回 { el, open, close } 句柄",
"timePicker(props):时间选择,时 / 分两列弹层,value 信号双向绑定 HH:mm,分钟可配步长,Esc 收起",
"pinInput(props):验证码输入,N 格单字符数字输入,输入自动进格、退格回删、方向键移格、粘贴自动分发,value 信号双向绑定,mask 可密文显示",
"copyButton(props):复制按钮,点击把 text 写入剪贴板并短暂显示已复制,text 可传函数取实时值,clipboard API 不可用时降级 execCommand",
"countdown(props):倒计时,秒级递减到 0 自动停表并触发 onFinish,缺省 mm:ss 展示,返回句柄 start/stop/reset",
"kbd(props):键盘按键标记,键帽样式展示单个按键或组合键(多键以 + 连接),用于快捷键提示",
"highlight(props):关键词高亮,命中片段包 mark(大小写不敏感全部命中),text / keyword 支持信号响应式刷新",
"inputTag(props):标签输入,输入后回车 / 逗号成标签、chip 可单个删除、空框退格删末个,value 信号双向绑定,max 满额自动禁输",
"layout(props, ...children):布局骨架,页头 / 侧栏 / 内容 / 页脚四区一次成型,侧栏可折叠(collapsed 信号双向绑定)",
"jsonViewer(props):JSON 树查看,按层折叠、分支带计数摘要、叶子按类型着色,只读不编辑",
"diffView(props):文本差异对比,行级 LCS 对齐 + 增删着色 + 双行号列,顶部带 +N / −N 统计",
"codeBlock(props):代码块,等宽多行 + 行号列 + 标题行 + 一键复制原文,只展示不高亮",
"signaturePad(props):手写签名板,指针绘制 + DPR 位图,返回 handle(el / clear / toDataURL / isEmpty)",
"loading(props, ...children):区域加载遮罩,内容原地降透明度 + 转圈文案,带延迟出场避免快请求闪烁",
"relativeTime(props):把时间戳渲染成「3 分钟前」并自动刷新,超阈值改显示绝对日期,title 恒给精确值",
"sparkline(props):迷你趋势线,把数值序列压成折线(可填充 + 末点标记),无坐标轴无交互",
"heatmap(props):日历热力图,按周列 × 星期行铺格子、四档浓度表示当天数量,悬停看明细",
"progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘",
"noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报",
"sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄"
],
"keyTypes": [
"ButtonProps",
"TextFieldProps",
"TextareaProps",
"CheckboxProps",
"SwitchProps",
"SelectProps",
"RadioGroupProps",
"MenuItem",
"MenuProps",
"AccordionItem",
"AccordionProps",
"BreadcrumbItem",
"BreadcrumbProps",
"OptionItem",
"CardProps",
"BadgeProps",
"AlertProps",
"ToastProps",
"TooltipProps",
"SkeletonProps",
"PaginationProps",
"SpinnerProps",
"ProgressProps",
"TabsProps",
"ModalProps",
"FormRule",
"FormItemProps",
"FormItemHandle",
"FormProps",
"DataTableProps",
"PopoverProps",
"SliderProps",
"TagProps",
"DrawerProps",
"AvatarProps",
"TimelineItem",
"TimelineProps",
"SegmentedProps",
"DescriptionItem",
"DescriptionsProps",
"StatisticProps"
]
},
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
"conventions": [
"皮肤不持有业务状态,一律委托 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-*"
],
"docs": {
"intro": "70 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。\n\n阴影对照表:下拉弹层=shadow-2 / 浮层与悬浮按钮=shadow-3 / 模态弹窗与抽屉及通知=shadow-4 / 卡片与输入组轻浮起=shadow-1;主按钮与强调件的主题色投影=shadow-accent / shadow-accent-strong,组件内不得再手写 box-shadow 字面值。\n\n浮层入场统一:新弹层用 animation: ua-pop-drop var(--ua-duration-fast) var(--ua-ease-out) both(自上而下 4px 淡入),弹层翻到触发器上方时叠 .is-flip 覆盖成 ua-pop-rise(自下而上),不要再为单个组件自造 keyframes——方向与时长各写一份就会出现「翻转后还朝下入场」的错位。",
"steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
"创建状态:import { signal } from '@ui-arch/frame',如 const name = signal('')",
"调用组件:const el = textField({ value: name }),把 el 挂到你的容器或配合 h() 组合",
"读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM"
],
"components": [
{
"id": "image",
"title": "图片 Image",
"demo": "sec-image",
"props": [
{
"n": "src",
"t": "string",
"r": 1,
"d": "图片地址(缩略图与大图共用)"
},
{
"n": "alt",
"t": "string",
"d": "替代文本;缺省空串",
"df": "''"
},
{
"n": "width",
"t": "number",
"d": "缩略图宽度像素,高度自适应;缺省走样式默认"
},
{
"n": "fallback",
"t": "string",
"df": "'图片加载失败'",
"d": "加载失败时缩略图内显示的占位文案;缺省「图片加载失败」"
}
],
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
"steps": [
"基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜",
"点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图",
"宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width",
"alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它"
]
},
{
"id": "button",
"title": "按钮 Button",
"demo": "sec-counter",
"props": [
{
"n": "label",
"t": "string",
"r": 1,
"d": "按钮文案"
},
{
"n": "variant",
"t": "'primary' | 'ghost' | 'danger'",
"d": "视觉强度;缺省 primary",
"df": "primary"
},
{
"n": "type",
"t": "'button' | 'submit' | 'reset'",
"d": "原生类型;缺省 button,表单内不会意外提交"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onClick",
"t": "() => void",
"k": 1,
"d": "点击回调"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "高度档位:sm 紧凑 / md 默认 / lg 大触控目标,走 --ua-control-h-* 三档;缺省 md"
},
{
"n": "loading",
"t": "boolean | Signal<boolean>",
"df": "false",
"d": "加载态:转圈并禁点(aria-busy 播报忙碌);布尔直接置位,传信号则动态同步"
}
],
"example": "button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') })",
"steps": [
"确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger",
"绑定 onClick 回调处理点击逻辑",
"需要禁用态时传 disabled(布尔或信号);异步提交用 loading:开始时置 true,完成后置 false,按钮转圈并自动禁点",
"放进 <form> 里需要提交时显式传 type: \"submit\";缺省 button 不会触发原生提交"
]
},
{
"id": "textField",
"title": "文本输入 TextField",
"demo": "sec-bind",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "双向绑定信号:输入写回信号,信号变化写回输入框"
},
{
"n": "placeholder",
"t": "string",
"d": "占位文案"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onInput",
"t": "(value: string) => void",
"k": 1,
"d": "输入回调,每次按键触发"
},
{
"n": "prefix",
"t": "string | HTMLElement",
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
},
{
"n": "suffix",
"t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "clearable",
"t": "boolean",
"df": "false",
"d": "开启后有值即显示一键清空按钮;缺省 false"
},
{
"n": "maxCount",
"t": "number",
"d": "字数上限:同时设原生 maxlength,并在输入框内侧显示实时计数;缺省不限"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "高度档位:sm 紧凑 / md 默认 / lg 大触控目标,走 --ua-control-h-* 三档;缺省 md"
},
{
"n": "password",
"t": "boolean",
"d": "密码框:原生打点渲染,内置「显示/隐藏」切换按钮(aria-label 同步);缺省普通文本"
}
],
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
"steps": [
"先用 signal('') 创建字符串信号",
"传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显",
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`",
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载",
"需要限制长度就给 maxCount:超限输入被原生拦截,输入框内侧实时显示 n/上限"
]
},
{
"id": "inputNumber",
"title": "数字输入 InputNumber",
"demo": "sec-inputnumber",
"props": [
{
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)"
},
{
"n": "min",
"t": "number",
"d": "最小值;缺省无下限"
},
{
"n": "max",
"t": "number",
"d": "最大值;缺省无上限"
},
{
"n": "step",
"t": "number",
"d": "步长;缺省 1",
"df": "1"
},
{
"n": "precision",
"t": "number",
"d": "小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差,step 0.1 也干净);缺省不处理"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onChange",
"t": "(value: number) => void",
"k": 1,
"d": "值变化回调(已夹取到合法区间)"
},
{
"n": "prefix",
"t": "string | HTMLElement",
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
},
{
"n": "suffix",
"t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "高度档位:sm 紧凑 / md 默认 / lg 大触控目标,走 --ua-control-h-* 三档;缺省 md"
}
],
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
"steps": [
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用",
"金额、比例这类小数场景传 precision:值与显示都固定小数位,步进不再出现 0.30000000000000004",
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
]
},
{
"id": "textarea",
"title": "多行输入 Textarea",
"demo": "sec-form",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "双向绑定信号"
},
{
"n": "rows",
"t": "number",
"d": "初始行数;缺省 3",
"df": "3"
},
{
"n": "placeholder",
"t": "string",
"d": "占位文案"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onInput",
"t": "(value: string) => void",
"k": 1,
"d": "输入回调"
},
{
"n": "prefix",
"t": "string | HTMLElement",
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
},
{
"n": "suffix",
"t": "string | HTMLElement",
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
},
{
"n": "maxCount",
"t": "number",
"d": "字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限"
},
{
"n": "autosize",
"t": "boolean | { minRows?, maxRows? }",
"d": "自适应内容高度:true 跟随内容涨落,对象态钳最小/最大行数,超出 maxRows 内部滚动;缺省固定行数"
}
],
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
"steps": [
"创建信号并传入 value",
"用 rows 控制初始高度,用户可纵向拉伸",
"字数统计等派生逻辑直接在响应式文本里读 bio().length",
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
]
},
{
"id": "slider",
"title": "滑块 Slider",
"demo": "sec-slider",
"props": [
{
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "当前值信号,双向绑定:拖动写回,外部赋值同步位置"
},
{
"n": "min",
"t": "number",
"d": "最小值;缺省 0",
"df": "0"
},
{
"n": "max",
"t": "number",
"d": "最大值;缺省 100",
"df": "100"
},
{
"n": "step",
"t": "number",
"d": "步长;缺省 1",
"df": "1"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "showValue",
"t": "boolean",
"d": "是否右侧显示当前值;缺省 true",
"df": "true"
},
{
"n": "marks",
"t": "{ value: number; label?: string }[]",
"d": "刻度标注:按值定位在轨道下方,label 缺省显示数值;纯装饰不进读屏(数值由原生 input 播报)"
},
{
"n": "range",
"t": "boolean",
"df": "false",
"d": "双滑块区间模式:value 当下限、end 当上限,两拇指之间用强调色连成已选段"
},
{
"n": "end",
"t": "Signal<number>",
"d": "区间上限信号;仅 range 模式使用,不传则组件内部维护"
},
{
"n": "onRangeChange",
"t": "(low: number, high: number) => void",
"k": 1,
"d": "区间两端变更回调,参数已保证 low ≤ high(越界那一端会被压平)"
},
{
"n": "onChange",
"t": "(value: number) => void",
"k": 1,
"d": "拖动回调"
}
],
"example": "const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })",
"steps": [
"创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定",
"用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数",
"拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理",
"只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal",
"禁用场景传 disabled(布尔或信号),置灰且不可拖",
"需要在轨道下方标刻度(如 0 / 50 / 100 的档位含义)传 marks:[{ value, label }],按 min-max 换算百分比定位,容器宽度变化时自动跟随",
"要选数值区间就传 range: true 并给 end 信号:两条原生 range 叠在同一轨道,只有拇指可拖;拖下限越过上限会自动压平,回调走 onRangeChange(low, high)"
]
},
{
"id": "checkbox",
"title": "复选框 Checkbox",
"demo": "sec-form",
"props": [
{
"n": "checked",
"t": "Signal<boolean>",
"d": "勾选状态信号,双向绑定"
},
{
"n": "label",
"t": "string",
"d": "右侧文案;缺省只渲染勾选框"
},
{
"n": "indeterminate",
"t": "boolean | Signal<boolean>",
"d": "半选态(批量选择时部分子项选中):仅影响视觉与读屏 mixed 播报,不改变 checked 值;点一下即回到全选"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onChange",
"t": "(checked: boolean) => void",
"k": 1,
"d": "变更回调"
}
],
"example": "const agreed = signal(false); checkbox({ label: '同意条款', checked: agreed, onChange: (v) => console.log(v) })",
"steps": [
"创建布尔信号 agreed = signal(false)",
"传入 checked 与 label,勾选即写回信号",
"表单提交前用 agreed() 判断是否勾选",
"做全选/子项联动时把「部分选中」折算成 indeterminate(布尔或信号):视觉显示横杠,读屏播报 mixed,点一下即全选"
]
},
{
"id": "switchButton",
"title": "开关 Switch",
"propsType": "SwitchProps",
"demo": "sec-form",
"props": [
{
"n": "checked",
"t": "Signal<boolean>",
"d": "开关状态信号,双向绑定"
},
{
"n": "ariaLabel",
"t": "string",
"d": "无可见文案时的可访问名(如纯图标开关);缺省取 label"
},
{
"n": "label",
"t": "string",
"d": "左侧文案"
},
{
"n": "loading",
"t": "boolean | Signal<boolean>",
"d": "加载中:锁定交互、滑块换成转圈并挂 aria-busy(异步提交防误点);与 disabled 同时生效"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onChange",
"t": "(checked: boolean) => void",
"k": 1,
"d": "变更回调"
}
],
"example": "const night = signal(true); switchButton({ label: '夜间模式', checked: night })",
"steps": [
"创建布尔信号并传入 checked",
"轨道与滑块的动画由 CSS 完成,无需干预",
"状态变化在 onChange 里做副作用(如切换主题)",
"切换要等异步请求就传 loading(布尔或信号):请求期间滑块转圈、不可点,完成后自动恢复"
]
},
{
"id": "select",
"title": "下拉选择 Select(自绘弹层)",
"demo": "sec-form",
"props": [
{
"n": "options",
"t": "OptionItem[]",
"r": 1,
"d": "选项列表,每项 { value, label };每项可加 group 指定分组小标题"
},
{
"n": "value",
"t": "Signal<string>",
"d": "选中值信号,双向绑定"
},
{
"n": "placeholder",
"t": "string",
"d": "未选中时触发器显示的占位文案"
},
{
"n": "searchable",
"t": "boolean",
"d": "弹层顶部带搜索框,按标签包含匹配过滤;缺省 false"
},
{
"n": "clearable",
"t": "boolean",
"d": "有值时触发器右缘出现清空按钮(收起态按 Delete / Backspace 也能清);缺省 false,用于「可选筛选项」"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层"
},
{
"n": "ariaLabel",
"t": "string",
"d": "触发器的可访问名:没有可见 label 关联时(如表格分页条的「每页条数」)给读屏一个名字;缺省不落 aria-label"
},
{
"n": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "选中回调"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "高度档位:sm 紧凑 / md 默认 / lg 大触控目标,走 --ua-control-h-* 三档;缺省 md"
}
],
"example": "const city = signal(''); select({ options: [{ value: 'bj', label: '北京' }], value: city, placeholder: '选择城市…' })",
"steps": [
"准备 options 数组,value 是提交用值、label 是展示文案",
"创建信号并传入 value;未传时组件内部自持状态",
"点击触发器展开弹层,点选项写回信号并收起;点外部或按 Esc 也会收起",
"选中项在弹层中用淡色弱化标记,不抢视觉焦点",
"弹层展开时按 Esc 只收起下拉并回焦触发器;若下拉在弹窗/抽屉内,再按一次 Esc 才关外层(逐层关闭)",
"searchable: true 时打开即聚焦搜索框,输入实时过滤;↑↓/Home/End 只在可见项间移动,无命中显示「无匹配选项」",
"需要允许「不选」的筛选项就开 clearable:有值时右缘出现 ×,点它把值写成空串并回调 onChange(''),键盘在收起态按 Delete / Backspace 等价",
"候选多时给选项加 group:相邻同组合成一段、组首插灰色小标题(段间有分隔线),过滤后整组无命中时标题一起隐藏——搜索路径与键盘 ↑↓ 仍只走真实选项",
"没有可见 label 的场合(工具栏、分页条)传 ariaLabel:触发器只显示当前值文案,读屏需要「这个下拉在选什么",
"弹层展开期间滚动页面或任意滚动容器,会按视口重算上下翻转(与 tooltip 共用 position.ts 的 trackShift);收起即摘监听"
]
},
{
"id": "menu",
"title": "下拉菜单 Menu",
"demo": "sec-menu",
"props": [
{
"n": "label",
"t": "string",
"r": 1,
"d": "触发器文案"
},
{
"n": "items",
"t": "MenuItem[]",
"r": 1,
"d": "菜单条目:{ value, label, disabled?, danger?, divider?, shortcut? }"
},
{
"n": "variant",
"t": "'primary' | 'ghost'",
"d": "触发器视觉强度;缺省 ghost",
"df": "ghost"
},
{
"n": "align",
"t": "'start' | 'end'",
"d": "弹层对齐方向;缺省 start(与触发器左对齐)",
"df": "start"
},
{
"n": "onSelect",
"t": "(value: string) => void",
"k": 1,
"d": "选中回调,禁用项不触发"
}
],
"example": "menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'del', label: '删除', danger: true }], onSelect: (v) => console.log(v) })",
"steps": [
"准备 items 数组:value 是回调标识、label 是展示文案,插一条 { divider: true } 即可分出条目组",
"传 label 决定触发器文案;默认 ghost 轻量样式,主操作用 variant: 'primary'",
"点触发器展开弹层,点条目收起并触发 onSelect;点外部或按 Esc 也会收起(Esc 后焦点回到触发器)",
"条目要给快捷键提示时传 shortcut(如 '⌘K'):右侧灰字展示,组件不绑定按键,实际快捷键由调用方在全局监听",
"键盘:Tab 聚焦触发器,↑↓ 展开并在可用条目间环形移动,Enter / Space 选中;disabled 项自动跳过",
"靠右对齐避免溢出时传 align: 'end';危险操作给条目加 danger: true",
"展开时按 Esc 只收起菜单并回焦触发器,不会连带关闭所在弹窗(逐层关闭)"
]
},
{
"id": "radioGroup",
"title": "单选组 RadioGroup",
"demo": "sec-form",
"props": [
{
"n": "options",
"t": "OptionItem[]",
"r": 1,
"d": "选项列表"
},
{
"n": "value",
"t": "Signal<string>",
"d": "选中值信号,双向绑定"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "整组禁用:全部选项与文案置灰且不可选"
},
{
"n": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "变更回调"
}
],
"example": "const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }], value: size })",
"steps": [
"创建字符串信号,初值设为默认选项的 value",
"传入 options 与 value,组内自动互斥",
"读取 size() 获取当前选中值",
"需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定"
]
},
{
"id": "accordion",
"title": "折叠面板 Accordion",
"demo": "sec-accordion",
"props": [
{
"n": "items",
"t": "AccordionItem[]",
"r": 1,
"d": "面板列表:{ value, title, content, disabled? }"
},
{
"n": "active",
"t": "Signal<string>",
"d": "受控展开面板信号(单开模式);缺省由组件内部持有状态"
},
{
"n": "multiple",
"t": "boolean",
"d": "允许多个面板同时展开;缺省 false(手风琴,单开)",
"df": "false"
},
{
"n": "onChange",
"t": "(active: string) => void",
"k": 1,
"d": "展开变化回调:收起时传空串"
}
],
"example": "accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' }, { value: 'b', title: '通知', content: h('p', null, '…') }] })",
"steps": [
"准备 items:value 作标识、title 作标题、content 可为字符串 / DOM 节点 / 响应式函数",
"默认手风琴模式:展开一个自动收起其他;需要同时展开多个时传 multiple: true",
"需要外部控制或读取展开项时传 active 信号(受控单开),否则组件内部自持状态",
"标题是原生 button:Tab 聚焦、Enter / Space 切换,展开态带 aria-expanded + region 关联",
"内容区用 grid 行高 0fr → 1fr 过渡,无需测量高度;reduced-motion 用户自动静态切换"
]
},
{
"id": "breadcrumb",
"title": "面包屑 Breadcrumb",
"demo": "sec-breadcrumb",
"props": [
{
"n": "items",
"t": "BreadcrumbItem[]",
"r": 1,
"d": "路径条目:{ label, href? },末项自动视为当前页"
},
{
"n": "separator",
"t": "string",
"d": "分隔符;缺省 '/'",
"df": "/"
},
{
"n": "onNavigate",
"t": "(item, index) => void",
"k": 1,
"d": "无 href 条目的点击回调"
},
{
"n": "maxItems",
"t": "number",
"d": "最多展示几项:超出时首项之后折叠成「…」按钮(aria-label 报出折叠条数),点开即完整展开;缺省不折叠"
}
],
"example": "breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })",
"steps": [
"按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'",
"需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调",
"改分隔符传 separator,如 '›';分隔符对读屏隐藏",
"语义结构为 nav + ol + li,可直接放进页头或卡片标题栏",
"路径过深时传 maxItems(如 4):首项 + 「…」+ 末几项,点「…」原地展开全部,末项始终是当前页"
]
},
{
"id": "card",
"title": "卡片 Card",
"demo": "sec-card",
"props": [
{
"n": "title",
"t": "string",
"d": "标题;缺省不渲染标题行"
},
{
"n": "...children",
"t": "unknown[]",
"d": "任意子节点:字符串 / DOM / 响应式函数"
},
{
"n": "extra",
"t": "unknown",
"d": "标题行右侧操作区(按钮 / 统计文案);缺省不渲染"
},
{
"n": "footer",
"t": "unknown",
"d": "底部操作区;缺省不渲染"
},
{
"n": "bordered",
"t": "boolean",
"d": "是否显示边框;缺省 true"
},
{
"n": "hoverable",
"t": "boolean",
"d": "悬停浮起(可点击卡片);缺省 false"
}
],
"example": "card({ title: '用户表' }, dataTable({ data, columns }))",
"steps": [
"card 是容器组件:第一个参数是 props,其余参数是任意子节点",
"传 title 显示标题行,否则只有内容区",
"子节点可以混合字符串、DOM 节点与响应式函数",
"title 与 extra 同处标题行:有 extra 时右侧对齐,没标题也能只放操作区",
"footer 渲染在内容之后的底部区,右对齐并带上分隔线,适合放主次按钮",
"bordered:false 去边框(嵌在已有边界的容器里),hoverable 让整卡悬停浮起(自带 reduced-motion 兜底)"
]
},
{
"id": "badge",
"title": "徽标 Badge",
"demo": "sec-feedback",
"props": [
{
"n": "label",
"t": "string",
"d": "徽标文案;与 count / dot 三选一,dot 时作可访问名"
},
{
"n": "variant",
"t": "'default' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 default"
},
{
"n": "count",
"t": "number",
"d": "数字角标:超过 max 显示 N+,负数归零"
},
{
"n": "max",
"t": "number",
"d": "数字上限;缺省 99"
},
{
"n": "dot",
"t": "boolean",
"d": "只显示小红点(无文字),role=img + label 作名称"
}
],
"example": "badge({ label: '成功', variant: 'success' })",
"steps": [
"纯状态文案直接传 label,配 variant 定语义色",
"要显示数量传 count(如未读数),超过 max(缺省 99)自动渲染成 99+",
"只提示「有更新」用 dot: true,配合 label 给出读屏名(如「有未读消息」)",
"不要给 badge 挂点击:它不是按钮(无 tabindex 也无键盘响应);「点角标进消息中心」应当把 badge 塞进 button 里,让按钮承担语义"
]
},
{
"id": "tag",
"title": "标签 Tag",
"demo": "sec-tag",
"props": [
{
"n": "label",
"t": "string",
"r": 1,
"d": "标签文案"
},
{
"n": "variant",
"t": "'default' | 'success' | 'warning' | 'danger' | 'info'",
"d": "语义色;缺省 default",
"df": "default"
},
{
"n": "closable",
"t": "boolean",
"d": "显示 × 关闭按钮;缺省 false(纯展示)",
"df": "false"
},
{
"n": "onClose",
"t": "() => void",
"k": 1,
"d": "关闭回调:调用方从数据源移除该项后重新渲染"
},
{
"n": "checkable",
"t": "boolean",
"df": "false",
"d": "可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准"
},
{
"n": "checked",
"t": "Signal<boolean>",
"d": "选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持"
},
{
"n": "onChange",
"t": "(checked: boolean) => void",
"d": "选中态变化回调,参数是点击后的新值"
}
],
"example": "tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })",
"steps": [
"纯展示用 badge,需要可移除的标签(筛选项 / 已选条件)用 tag",
"按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效",
"closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)",
"组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号",
"标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定",
"需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢)"
]
},
{
"id": "avatar",
"title": "头像 Avatar",
"demo": "sec-avatar",
"props": [
{
"n": "name",
"t": "string",
"r": 1,
"d": "名称:生成占位首字,并作为图片 alt 与悬停标题"
},
{
"n": "src",
"t": "string",
"d": "图片地址;缺省用首字占位"
},
{
"n": "size",
"t": "number",
"d": "直径像素;缺省 32",
"df": "32"
},
{
"n": "variant",
"t": "'default' | 'success' | 'warning' | 'danger'",
"d": "占位底色语义档;缺省 default",
"df": "default"
}
],
"example": "avatar({ name: '张三' }); avatar({ name: 'Alice', size: 40, variant: 'success' })",
"steps": [
"只传 name:用名称首字做占位(中文取首字、英文大写),底色走 accent 淡化,暗色自动适配",
"有图片就传 src:渲染 img 并用 name 作 alt,读屏语义完整;图片缺失时由浏览器 alt 兜底",
"尺寸用 size(像素)控制,头像不参与布局拉伸(flex:none),可安全排进 .row 与表格单元格",
"需要区分类别(在线/告警)时用 variant 换底色档,颜色一律来自令牌"
]
},
{
"id": "alert",
"title": "提示条 Alert",
"demo": "sec-alert",
"props": [
{
"n": "message",
"t": "string",
"r": 1,
"d": "提示内容"
},
{
"n": "title",
"t": "string",
"d": "加粗标题行;缺省只渲染正文"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info",
"df": "info"
},
{
"n": "closable",
"t": "boolean",
"d": "显示 × 关闭按钮,退场后从 DOM 移除;缺省 false",
"df": "false"
},
{
"n": "onClose",
"t": "() => void",
"k": 1,
"d": "关闭回调,供调用方同步业务状态"
},
{
"n": "banner",
"t": "boolean",
"df": "false",
"d": "通栏形态:去圆角、只保留下边框、左右撑满,适合页面顶部通知"
},
{
"n": "showIcon",
"t": "boolean",
"df": "false",
"d": "显示语义图标(info ℹ / success ✓ / warning ! / danger ✕),纯装饰不进读屏"
},
{
"n": "icon",
"t": "string | HTMLElement",
"d": "自定义图标节点,传了即覆盖语义图标"
}
],
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
"传入 message 即可,入场自带下滑动画",
"需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色",
"需要用户可自行消除的提示传 closable: true:点 × 淡出并从 DOM 移除,配合 onClose 同步业务状态",
"有标题与正文两层信息时传 title(加粗首行),正文仍走 message",
"banner: true 出通栏形态(圆角与左右边框归零),页面顶部通知直接铺满容器即可,不需要额外包裹层"
]
},
{
"id": "skeleton",
"title": "骨架屏 Skeleton",
"demo": "sec-skeleton",
"props": [
{
"n": "variant",
"t": "'text' | 'rect' | 'circle'",
"d": "占位形状;缺省 rect(整块矩形)",
"df": "rect"
},
{
"n": "rows",
"t": "number",
"d": "text 形状的行数;缺省 3,末行自动缩短模拟段落",
"df": "3"
}
],
"example": "skeleton({ variant: 'text', rows: 3 }); skeleton({ variant: 'circle' }); skeleton({ variant: 'rect' })",
"steps": [
"数据加载期间用 skeleton 顶替真实内容,加载完成后整体替换",
"文本段落用 variant: 'text' + rows 控制行数,头像用 'circle',卡片/图片区域用 'rect'",
"占位块自带扫光动画,reduced-motion 用户自动静态化为纯灰块",
"组合布局:skeleton 是普通 DOM,可放进 card / row 等任意容器"
]
},
{
"id": "toast",
"title": "消息提示 Toast",
"demo": "sec-toast",
"props": [
{
"n": "message",
"t": "string",
"r": 1,
"d": "提示文案"
},
{
"n": "duration",
"t": "number",
"d": "自动消失毫秒;缺省 3000",
"df": "3000"
},
{
"n": "closable",
"t": "boolean",
"d": "显示关闭按钮,点 × 立即收起;缺省 false(任意 toast 都接收指针,悬停或聚焦即暂停倒计时)",
"df": "false"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info(toast.error 对应 danger)",
"df": "info"
},
{
"n": "actionLabel",
"t": "string",
"d": "操作按钮文案(如「撤销」);传了才渲染,点后 toast 立即收起"
},
{
"n": "onAction",
"t": "() => void",
"k": 1,
"d": "操作按钮回调,在 toast 收起之后触发(撤销删除之类)"
}
],
"example": "toast.success('保存成功'); toast.error('网络请求失败', 5000)",
"steps": [
"直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器",
"不传 duration 时默认停留 3 秒后自动淡出并移除节点",
"鼠标悬停或键盘 Tab 进卡片时倒计时暂停,移出 / 焦点离开后按剩余时长续走(剩余不足 400ms 时补到 400ms,避免松手即消失);宿主容器仍穿透,只有卡片本体这一小块可命中",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入",
"同屏最多 4 条,超出自动挤掉最早一条;需要用户主动确认的消息用 showToast({ closable: true }) 给个 ×",
"要「撤销」这类操作时给 showToast 传 actionLabel 与 onAction:按钮出现在文案右侧,点完先收起再回调,duration 建议放到 5000 让用户来得及点",
"同屏最多 4 条:第 5 条进来时最早一条走正常退场(淡出后移除)而不是瞬间消失;要不被挤掉就传 duration: 0 让它常驻,靠 closable 手动关"
]
},
{
"id": "tooltip",
"title": "悬浮提示 Tooltip",
"demo": "sec-tooltip",
"props": [
{
"n": "content",
"t": "string",
"r": 1,
"d": "提示文案(纯文本)"
},
{
"n": "placement",
"t": "'top' | 'bottom' | 'left' | 'right'",
"d": "气泡方位;缺省 top,出场时按视口剩余空间自动翻到对侧(水平方位按宽度判左右)",
"df": "top"
},
{
"n": "delay",
"t": "number",
"d": "出场延迟毫秒;缺省 150,传 0 立即显示",
"df": "150"
},
{
"n": "trigger",
"t": "HTMLElement",
"r": 1,
"d": "第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)"
}
],
"example": "tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))",
"steps": [
"把提示要挂在的元素作为第二个参数传入,返回的包裹节点替换原位置(气泡绝对定位,不占布局)",
"鼠标悬停或键盘聚焦后延迟 delay 毫秒出场,移出 / 失焦 / Esc 收起;空间不足时自动翻到另一侧",
"触摸屏没有 hover:手指按下即出场,抬手后停留 1.2 秒再收起,免得手指正挡着就消失",
"气泡设了 pointer-events: none,不会挡住下面的点击;aria-describedby 已关联,读屏能念出提示文案",
"方位支持上下左右四向:贴边或工具栏竖排时用 left / right,出场时按该方向剩余空间自动翻到对侧",
"出场期间滚动页面或任意滚动容器,气泡会按视口重算上下翻转(rAF 节流,与 select 等浮层共用同一套 trackShift);收起后监听立即摘掉,不在关闭态空转"
]
},
{
"id": "popover",
"title": "气泡卡片 Popover",
"demo": "sec-popover",
"props": [
{
"n": "content",
"t": "unknown",
"r": 1,
"d": "弹层内容:字符串 / DOM 节点 / 响应式函数,可放按钮等交互元素"
},
{
"n": "title",
"t": "string",
"d": "弹层标题;缺省不渲染标题行"
},
{
"n": "placement",
"t": "'top' | 'bottom'",
"d": "弹层方位;缺省 bottom(触发器下方)",
"df": "bottom"
},
{
"n": "trigger",
"t": "'click' | 'hover'",
"d": "触发方式;缺省 click",
"df": "click"
},
{
"n": "delay",
"t": "number",
"d": "hover 模式出场延迟毫秒;缺省 150",
"df": "150"
},
{
"n": "onOpenChange",
"t": "(open: boolean) => void",
"k": 1,
"d": "开合回调"
},
{
"n": "triggerEl(第二个参数)",
"t": "HTMLElement",
"r": 1,
"d": "第二个参数:被包裹的触发器元素"
},
{
"n": "open",
"t": "Signal<boolean>",
"d": "受控开合信号;不传则内部维护,传了外部可编程开合(popconfirm 即基于它)"
}
],
"example": "popover({ title: '筛选条件', content: h('div', null, '任意可交互内容') }, button({ label: '筛选' }))",
"steps": [
"创建触发器元素,再用 popover(props, trigger) 包一层:返回带弹层的包裹元素",
"点击触发器开合;点弹层外部或按 Esc 收起(Esc 后焦点回触发器)",
"content 里可以放 button / checkbox 等交互元素(tooltip 只能纯文本),点击弹层内部不会误关",
"hover 场景传 trigger: 'hover' + delay 控制多快出场;placement: 'top' 让弹层往上翻",
"需要感知开合做副作用(如懒加载数据)时用 onOpenChange",
"展开时按 Esc 只收起气泡并回焦触发器,不会连带关闭所在弹窗(逐层关闭)"
]
},
{
"id": "spinner",
"title": "加载指示器 Spinner",
"demo": "sec-feedback",
"props": [
{
"n": "size",
"t": "number",
"d": "直径像素;缺省 18",
"df": "18"
},
{
"n": "label",
"t": "string",
"d": "读屏播报文案,如「正在加载订单」;缺省「加载中」"
}
],
"example": "spinner(); spinner({ size: 26 })",
"steps": [
"数据加载时插入 spinner(),加载完成移除",
"用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
"与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner",
"区块级加载给 spinner 传 label 说明在加载什么,读屏才不会只念一句「加载中」"
]
},
{
"id": "progress",
"title": "进度条 Progress",
"demo": "sec-feedback",
"props": [
{
"n": "value",
"t": "Signal<number>",
"d": "当前进度 0-100 的信号;不传即不确定态(未知时长的加载)"
},
{
"n": "type",
"t": "'line' | 'circle'",
"df": "line",
"d": "形态:线性条或环形;环形需同时传 value(不确定态仍走线性条)"
},
{
"n": "showText",
"t": "boolean",
"d": "是否右侧显示百分比;缺省 true(不确定态忽略)"
},
{
"n": "label",
"t": "string",
"d": "可访问名,如「上传进度」;缺省不确定态念「进行中」"
},
{
"n": "variant",
"t": "'default' | 'success' | 'warning' | 'danger'",
"d": "填充语义色;缺省 default(满格自动切 success)"
},
{
"n": "format",
"t": "(v: number) => string",
"k": 1,
"d": "文本自定义:传入则线/环形的进度文本用其返回值替代默认「N%」;缺省取整百分比"
}
],
"example": "const pct = signal(0); progress({ value: pct }); setInterval(() => pct((pct() + 5) % 105), 400)",
"steps": [
"传 value 信号给定量进度,不传则退化为往复的不确定态",
"label 写清这是什么进度,读屏会把名称与百分比一起念出来",
"失败或告警用 variant: 'danger' / 'warning',该颜色在满格时也不被 success 覆盖",
"看板 / 上传卡片要环形进度就传 type: 'circle'(同时必须传 value):SVG 双圆按 dashoffset 出弧,中间自动显示百分比,语义色同 variant"
]
},
{
"id": "tabs",
"title": "标签页 Tabs",
"demo": "sec-tabs-modal",
"props": [
{
"n": "tabs",
"t": "TabItem[]",
"r": 1,
"d": "页签列表,每项 { value, label, disabled?, closable? }"
},
{
"n": "active",
"t": "Signal<string>",
"d": "当前激活页签 value 信号,双向绑定"
},
{
"n": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "切换回调"
},
{
"n": "onClose",
"t": "(value: string) => void",
"k": 1,
"d": "关闭页签回调:组件不自行移除,凭 value 从数据源里摘掉后重绘"
},
{
"n": "extra",
"t": "unknown",
"d": "页签栏右侧操作区内容(按钮 / 链接):与页签同一行、共用一条分隔线;缺省不渲染且页签栏结构不变"
},
{
"n": "closable",
"t": "boolean",
"d": "页签项上的开关:true 时渲染关闭按钮(×),关闭按钮不进 Tab 序但可用鼠标点;缺省不可关"
}
],
"example": "const tab = signal('a'); tabs({ tabs: [{ value: 'a', label: '概览', closable: true }, { value: 'b', label: '明细', disabled: true }], active: tab, onClose: (v) => toast.info(`关闭 ${v}`) })",
"steps": [
"创建信号 tab = signal('a'),初值为默认页签",
"tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB",
"点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点",
"页签多到一屏放不下(页签条横向溢出)时,切到被裁掉的页签会自动滚回可视区:只滚页签条本身、不带动页面,系统开启「减弱动效」时直接跳位;已完整可见则不动,不打断用户手动滚动",
"需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘",
"键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签",
"页签栏右侧要放「新建 / 设置」这类操作就传 extra(按钮或链接):它与页签同一行共用一条分隔线,不传时 DOM 结构保持不变"
]
},
{
"id": "stepper",
"title": "步骤条 Stepper",
"demo": "sec-stepper",
"props": [
{
"n": "steps",
"t": "StepperItem[]",
"r": 1,
"d": "步骤列表,每项 { label, description? }"
},
{
"n": "active",
"t": "Signal<number>",
"r": 1,
"d": "当前步索引(0 基)信号,双向绑定:回跳写回、外部赋值同步"
},
{
"n": "clickable",
"t": "boolean",
"d": "已完成步骤是否可点击回跳;缺省 false"
},
{
"n": "direction",
"t": "'horizontal' | 'vertical'",
"df": "'horizontal'",
"d": "排列方向:horizontal 横排连接线在节点之间;vertical 竖排、连接线落在圆点下方,已走完的一段用主色"
},
{
"n": "onChange",
"t": "(index: number) => void",
"k": 1,
"d": "步骤切换回调(点击回跳触发)"
}
],
"example": "const step = signal(1); stepper({ steps: [{ label: '下单' }, { label: '支付' }, { label: '完成' }], active: step, clickable: true })",
"steps": [
"用 signal(0) 创建当前步索引,传入 active;索引之前的步骤显示 ✓ 已完成态",
"流程推进时外部赋值即可:step(step() + 1),组件三态自动跟随",
"clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态",
"侧栏或移动端传 direction: 'vertical':圆点成一列、说明文字紧贴右侧,步与步之间由圆点下方的竖线相连(末步不画线,已完成的段用主色标进度)"
]
},
{
"id": "rate",
"title": "评分 Rate",
"demo": "sec-rate",
"props": [
{
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "当前评分(1 基,allowHalf 时 0.5 步进)信号,双向绑定:点选/键盘写回、外部赋值同步"
},
{
"n": "max",
"t": "number",
"d": "星标总数;缺省 5",
"df": "5"
},
{
"n": "allowHalf",
"t": "boolean",
"df": "false",
"d": "允许半星:点击星标左半区记 0.5 分,键盘步进同为 0.5;缺省整数"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onChange",
"t": "(value: number) => void",
"k": 1,
"d": "评分变化回调"
},
{
"n": "allowClear",
"t": "boolean",
"d": "允许清除:再次点击当前分值(半星须精确相等)清零;缺省不可清"
}
],
"example": "const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) })",
"steps": [
"用 signal(0) 创建评分信号并传入 value;0 表示未评分",
"点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原",
"max 可调整星标总数;disabled 支持布尔或信号禁用",
"要支持半星就传 allowHalf:点星标左半区记 0.5 分,悬停位置实时预览半格填充"
]
},
{
"id": "empty",
"title": "空状态 Empty",
"demo": "sec-empty",
"props": [
{
"n": "title",
"t": "string",
"d": "主标题;缺省不渲染"
},
{
"n": "description",
"t": "string",
"d": "补充说明;缺省不渲染"
},
{
"n": "icon",
"t": "unknown",
"d": "图标区内容(字符串或图标 DOM);缺省渲染 ⌀ 占位符号,装饰性元素不进无障碍树"
},
{
"n": "action",
"t": "unknown",
"d": "操作区内容(常见为按钮);缺省不渲染"
}
],
"example": "empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })",
"steps": [
"在列表 / 表格 / 筛选结果为空时渲染 empty 占位",
"title 一句话说明状态,description 补充原因或建议",
"action 传入 DOM(如 ghost 按钮)引导用户下一步操作",
"要换成自家插图就传 icon(字符串或 SVG 节点):它固定 aria-hidden,文案仍由 title / description 承担"
]
},
{
"id": "divider",
"title": "分隔线 Divider",
"demo": "sec-divider",
"props": [
{
"n": "label",
"t": "string",
"d": "居中文案标签;缺省纯线条"
},
{
"n": "vertical",
"t": "boolean",
"d": "是否垂直分隔线(用于行内工具栏);缺省水平"
},
{
"n": "dashed",
"t": "boolean",
"d": "是否虚线;缺省实线"
}
],
"example": "divider({ label: '更多信息' }) / divider({ vertical: true })",
"steps": [
"在区块之间插入 divider 划分内容层次",
"传 label 在水平线上显示居中文案,如「更多信息」",
"工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感",
"分割线是纯视觉件:没有 role 也没有焦点,读屏不会播报它。真要让读屏理解分组,用 heading 或 section,别指望横线"
]
},
{
"id": "space",
"title": "间距容器 Space",
"demo": "sec-space",
"props": [
{
"n": "direction",
"t": "'horizontal' | 'vertical'",
"d": "主轴方向;缺省 horizontal 横排"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg' | number",
"d": "子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md"
},
{
"n": "align",
"t": "'start' | 'center' | 'end' | 'baseline'",
"d": "交叉轴对齐;缺省 center,按钮与文字混排不错位"
},
{
"n": "justify",
"t": "'start' | 'center' | 'end' | 'between'",
"d": "主轴分布;缺省 start,between 两端撑开"
},
{
"n": "wrap",
"t": "boolean",
"d": "空间不足是否换行;缺省 false"
},
{
"n": "block",
"t": "boolean",
"d": "是否占满父容器宽度;缺省按内容收缩"
},
{
"n": "split",
"t": "string",
"d": "子元素之间的分隔符(如 · / |):纯装饰、aria-hidden,仅 2 个以上子元素时插入"
}
],
"example": "space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...)",
"steps": [
"把一排需要统一间距的元素交给 space 托管,不再各自写 margin",
"调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字",
"竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐",
"工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap",
"一行里要塞分隔符(面包屑式「A · B · C」)传 split: '·':分隔符由容器插入且读屏跳过,不要手写 span 再逐个调 margin"
]
},
{
"id": "pagination",
"title": "分页器 Pagination",
"demo": "sec-pagination",
"props": [
{
"n": "page",
"t": "Signal<number>",
"r": 1,
"d": "当前页码信号,双向绑定:外部赋值自动翻页,点击页码写回信号"
},
{
"n": "pageSize",
"t": "number",
"r": 1,
"d": "每页条数"
},
{
"n": "total",
"t": "number | Signal<number>",
"r": 1,
"d": "总条数;传信号则总量变化时自动重算页数(并把越界页码钳回并回调 onChange)"
},
{
"n": "simple",
"t": "boolean",
"df": "false",
"d": "极简模式:只留上一页 / 下一页与「当前 / 总页」文字,适合表格工具栏等窄位"
},
{
"n": "hideOnSinglePage",
"t": "boolean",
"df": "false",
"d": "只有一页时整个分页器隐藏(不占布局位)"
},
{
"n": "onChange",
"t": "(page: number) => void",
"k": 1,
"d": "翻页回调"
},
{
"n": "jumpable",
"t": "boolean",
"df": "false",
"d": "末尾显示跳页输入框(总页数 > 1 才出现);缺省 false"
},
{
"n": "pageSizes",
"t": "number[]",
"d": "每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器"
},
{
"n": "onPageSizeChange",
"t": "(size: number) => void",
"k": 1,
"d": "切换每页条数回调,同时自动回到第 1 页"
}
],
"example": "const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) => console.log(p) })",
"steps": [
"创建页码信号 page = signal(1),与 pageSize / total 一起传入",
"页码 ≤7 全部展开;更多时只保留首尾与当前页 ±1,缺口折叠为省略号",
"首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current",
"与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步",
"键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发",
"pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据",
"空间紧的地方传 simple:页码区换成「3 / 5」文字 + 两个箭头(文字带 aria-live,翻页会被读屏播报);数据少到只有一页时再传 hideOnSinglePage,整块自动消失而不是留个灰掉的 1",
"总量会变(筛选 / 删除后)就把 total 传信号:页码越界时组件自动钳到最后一页并回调 onChange,父级 state 不会停在空页"
]
},
{
"id": "modal",
"title": "弹窗 Modal",
"demo": "sec-tabs-modal",
"props": [
{
"n": "open",
"t": "Signal<boolean>",
"r": 1,
"d": "开关信号:true 显示 / false 隐藏"
},
{
"n": "title",
"t": "string",
"r": 1,
"d": "标题"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "宽度档位:sm 340px / md 420px / lg 640px;缺省 md",
"df": "md"
},
{
"n": "footer",
"t": "unknown",
"d": "底部操作区内容(常见为按钮组,右对齐);缺省不渲染;给了它就用自定义页脚,内置按钮组不再渲染"
},
{
"n": "confirmText",
"t": "string",
"d": "内置页脚:确认按钮文案;缺省「确定」(不传 footer 且有 onConfirm 时也渲染内置页脚)"
},
{
"n": "cancelText",
"t": "string",
"d": "内置页脚:取消按钮文案;缺省「取消」,点击走 close 流程"
},
{
"n": "onConfirm",
"t": "() => void | Promise<void>",
"k": 1,
"d": "内置页脚:确认回调;返回 Promise 时按钮变「处理中…」并禁用,成功自动收窗、失败留在窗口重试"
},
{
"n": "closeOnOverlay",
"t": "boolean",
"d": "点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用)"
},
{
"n": "onClose",
"t": "() => void",
"k": 1,
"d": "关闭回调(点遮罩、× 或 Esc 触发)"
},
{
"n": "...children",
"t": "unknown[]",
"d": "弹窗内容子节点"
}
],
"example": "const open = signal(false); modal({ title: '确认操作', open, onClose: () => open(false) }, h('p', null, '内容'))",
"steps": [
"创建布尔信号 open = signal(false),弹窗初始隐藏",
"把 open 传给 modal,其余参数为弹窗内容子节点",
"打开:open(true);关闭:open(false),点遮罩、× 或 Esc 会自动写回 false;多弹层叠加时 Esc 只关最上层(后开先关)",
"入场自带遮罩淡入 + 弹窗上浮动画",
"底部操作按钮放进 footer(如取消/确定),需要更宽内容用 size: 'lg',窄确认框用 'sm'",
"打开后 Tab 焦点锁在弹窗内,关闭自动把焦点还给打开前的元素",
"普通确认框不必手拼 footer:传 confirmText / onConfirm 即得「取消 + 确认」按钮组;onConfirm 写成 async,组件会在等待期间把按钮置灰改文案,resolve 后自己收窗、reject 时保持打开供重试"
]
},
{
"id": "drawer",
"title": "抽屉 Drawer",
"demo": "sec-drawer",
"props": [
{
"n": "title",
"t": "string",
"r": 1,
"d": "标题"
},
{
"n": "open",
"t": "Signal<boolean>",
"r": 1,
"d": "开关信号:true 滑出 / false 收起"
},
{
"n": "placement",
"t": "'left' | 'right' | 'top' | 'bottom'",
"d": "滑出方向;缺省 right",
"df": "right"
},
{
"n": "width",
"t": "number",
"d": "面板尺寸(像素):左右滑出为宽度,上下滑出为高度;缺省 320",
"df": "320"
},
{
"n": "footer",
"t": "unknown",
"d": "底部操作区内容;缺省不渲染"
},
{
"n": "closeOnOverlay",
"t": "boolean",
"d": "点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用)"
},
{
"n": "modal",
"t": "boolean",
"d": "是否模态:false 时无遮罩、不锁背景滚动、不困焦点,页面仍可操作(检查器 / 详情面板);缺省 true"
},
{
"n": "onClose",
"t": "() => void",
"k": 1,
"d": "关闭回调(点遮罩 / × / Esc 触发)"
},
{
"n": "...children",
"t": "unknown[]",
"d": "面板内容子节点"
}
],
"example": "const open = signal(false); drawer({ title: '详情', open, width: 360, onClose: () => open(false) }, h('p', null, '内容'))",
"steps": [
"创建布尔信号 open = signal(false),传给 drawer;其余参数为面板内容子节点",
"打开后从侧边滑出:placement: 'right'(默认)或 'left',width 控制宽度(窄屏自动收到 92vw)",
"点遮罩、× 或按 Esc 关闭并写回 false;打开时焦点落在面板首个可交互元素,Esc 只关最上层弹层",
"底部操作按钮放进 footer;与 modal 的分工:modal 是强打断的确认,drawer 保留主视图上下文做详情/编辑",
"面板内容与 modal 一样接受任意 DOM 与响应式函数",
"要「一边看一边改」就把 modal 设成 false:遮罩透明且不吃点击、背景滚动不锁、Tab 不被困在面板里,× 与 Esc 仍能关"
]
},
{
"id": "form",
"title": "表单 Form / 表单字段 FormItem",
"demo": "sec-formlab",
"props": [
{
"n": "layout",
"t": "'vertical' | 'horizontal'",
"d": "布局方向;缺省 vertical(horizontal 为标签左置)",
"df": "vertical"
},
{
"n": "columns",
"t": "1 | 2 | 3",
"d": "字段列数:>1 时字段走栅格铺列(沿用 grid 的窄屏单列),按钮区始终整行;缺省 1",
"df": "1"
},
{
"n": "submitText",
"t": "string",
"d": "提交按钮文案;缺省「提交」"
},
{
"n": "resetText",
"t": "string",
"d": "重置按钮文案;缺省不渲染"
},
{
"n": "onSubmit",
"t": "() => void",
"k": 1,
"d": "全部字段校验通过后的提交回调"
},
{
"n": "(formItem)label",
"t": "string",
"r": 1,
"d": "字段标签"
},
{
"n": "(formItem)value",
"t": "Signal<string>",
"r": 1,
"d": "字段值信号,校验数据源"
},
{
"n": "(formItem)required",
"t": "boolean",
"d": "必填:显示红星并自动追加非空规则"
},
{
"n": "(formItem)rules",
"t": "FormRule[]",
"d": "校验规则:required / min / max / pattern / validator,按序执行取首个失败"
},
{
"n": "(formItem)validateOn",
"t": "'input' | 'blur' | 'submit'",
"d": "校验时机;缺省 blur(输入时自动清已报错误)",
"df": "blur"
},
{
"n": "(formItem)hint",
"t": "string",
"d": "控件下方辅助说明(格式提示等);报错时让位给错误文案"
},
{
"n": "(formItem)span",
"t": "number",
"d": "多列表单里本字段占几列(如整宽的多行备注);单列布局或窄屏下自动退回整行"
},
{
"n": "(formItem)visible",
"t": "Signal<boolean>",
"d": "条件显示信号:为假时整项隐藏且不参与校验(未显示的字段不该拦住提交)"
},
{
"n": "onReset",
"t": "() => void",
"d": "重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮"
},
{
"n": "onError",
"t": "(count: number) => void",
"k": 1,
"d": "校验不通过时的回调,参数为不通字段数(适合埋点或顶部提示)"
},
{
"n": "errorSummary",
"t": "boolean",
"d": "是否在表单顶部显示「有 N 项需要修改」汇总条;缺省 true,自带错误 UI 时传 false"
}
],
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
"steps": [
"为每个字段创建 signal 与控件,如 const name = signal(''); textField({ value: name })",
"用 formItem 包住控件:formItem({ label, required, value, rules }, control),返回 { el, validate } 句柄",
"把句柄数组交给 form:form({ onSubmit }, items),form 渲染全部字段 + 提交/重置按钮",
"提交时逐项校验:全过触发 onSubmit;有错则标红控件、显示错误文案并聚焦首个出错项",
"规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)",
"非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)",
"错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致",
"触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验",
"单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type=\"submit\",不会与点击回调重复触发",
"rules.validator 返回 Promise 即为异步规则(如「邀请码是否已被使用」远程校验):提交时逐项 await,校验期间提交按钮禁用,结论按轮次取舍避免乱序覆盖",
"formItem 返回 { el, validate, validateAsync } 句柄:validate 只跑同步规则(供 blur/input 即时反馈),validateAsync 逐条 await 供 form 聚合提交",
"需要给字段加格式说明就传 hint:平时显示在控件下方,校验失败时自动让位给错误文案,同一位置不堆两行",
"字段多的编辑页传 columns: 2 或 3 走多列栅格:列间距与窄屏堆叠沿用 grid,按钮区始终整行;单列表单不要传(默认 1)",
"多列表单里想整宽就传 span:formItem({ span: 2 }, textarea(...)) 会占满两列,窄屏单列时自动忽略跨列(否则会撑出横向滚动)",
"字段要按另一个字段的值决定是否出现(选了「其他」才出现输入框),给 formItem 传 visible 信号:隐藏项自动清掉错误态并退出校验,不用自己在 items 数组里增删",
"校验失败必定中止提交:判据是错误条数而不是「有没有找到焦点目标」,所以只有自绘控件(非搜索式下拉 / 评分 / 开关,容器里没有原生 input)的字段也能拦住;焦点定位顺序是控件 → 触发器 → 字段容器(后者临时 tabindex=-1,只接受程序聚焦,不污染 Tab 序)"
]
},
{
"id": "dataTable",
"title": "数据表格 DataTable",
"propsType": "DataTableProps",
"demo": "sec-table",
"props": [
{
"n": "data",
"t": "T[] | Signal<T[]>",
"r": 1,
"d": "全量行数据(行须含 id 作 rowKey);传信号或 computed 时数据变化自动换源,排序 / 筛选 / 页码保持用户原意"
},
{
"n": "columns",
"t": "ColumnDef<T>[]",
"r": 1,
"d": "列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应"
},
{
"n": "filterKey",
"t": "string",
"d": "工具栏筛选的列;缺省取第一列"
},
{
"n": "pageSize",
"t": "number",
"d": "每页行数;缺省 5",
"df": "5"
},
{
"n": "pageSizes",
"t": "number[]",
"d": "分页条「每页条数」候选档;缺省 [5, 10, 20],当前 pageSize 不在候选里会自动补进去",
"df": "[5, 10, 20]"
},
{
"n": "emptyText",
"t": "string",
"d": "无数据时的空状态文案;有筛选词时仍显示「没有符合「x」的行」并给一键清除(两种语义不混用)",
"df": "暂无数据"
},
{
"n": "selectable",
"t": "boolean",
"d": "启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false",
"df": "false"
},
{
"n": "page",
"t": "Signal<number>",
"d": "受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动"
},
{
"n": "loading",
"t": "boolean | Signal<boolean>",
"d": "加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy"
},
{
"n": "selected",
"t": "Signal<RowKey[]>",
"d": "受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写"
},
{
"n": "exportable",
"t": "boolean",
"d": "工具栏显示「导出 CSV」:导出筛选 + 排序后的全量行(不分页),列跟随当前可见列;缺省 false"
},
{
"n": "exportName",
"t": "string",
"d": "导出文件名;缺省 table.csv"
},
{
"n": "onRowClick",
"t": "(row: T) => void",
"k": 1,
"d": "整行点击 / Enter / 空格的回调;传了行才可聚焦可点(未传时行保持纯展示,不抢 Tab 焦点)"
},
{
"n": "columnToggle",
"t": "boolean",
"d": "工具栏显示「列」按钮:弹层内勾选控制列显隐,至少保留一列(最后一列的勾选框锁死),缺省 false",
"df": "false"
},
{
"n": "hiddenColumns",
"t": "Signal<string[]>",
"d": "受控隐藏列键集:与「列」面板双向同步,外部可据此持久化用户偏好;隐藏列不渲染、不进导出"
}
],
"example": "dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })",
"steps": [
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
"多列排序:按住 Shift 点第二个可排序列即叠加——内核按 rules 顺序逐列比较,表头标记带优先级(↑1 / ↓2),不按 Shift 则回到单列语义",
"列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)",
"数据源可以给信号或 computed(如筛选结果):变化时内核整体换源,选择状态按 rowKey 剔除已消失的行,排序与页码不被打断",
"列宽用内核字段 width:数字(像素)固定该列,'flex' 或不设则按内容自适应",
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
"表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供",
"数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复",
"分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码",
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)",
"可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充",
"需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出",
"整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮",
"需要让用户自己调列时开 columnToggle:工具栏多一个「列」按钮,弹层勾选即刻重绘表头与单元格(隐藏列不占 colspan,空状态与骨架行同步收窄)",
"想记住用户偏好就传 hiddenColumns 信号:面板勾选实时写回,外部赋值即整表跟随;隐藏筛选列后筛选条件仍然生效,属预期而非失效"
]
},
{
"id": "timeline",
"title": "时间线 Timeline",
"demo": "sec-timeline",
"props": [
{
"n": "items",
"t": "TimelineItem[]",
"r": 1,
"d": "条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant"
},
{
"n": "mode",
"t": "'left' | 'right' | 'alternate'",
"d": "轴线位置:left / right 单侧排布;alternate 双侧交替(轴线居中、奇偶左右分列,窄屏自动退回左侧)",
"df": "left"
},
{
"n": "pending",
"t": "boolean",
"d": "末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false",
"df": "false"
}
],
"example": "timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })",
"steps": [
"事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后",
"variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)",
"time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析",
"流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失",
"需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像",
"content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合",
"要做里程碑式双侧时间线就传 mode: 'alternate':轴线居中、条目按奇偶分列左右并在窄屏退回单侧,pending 虚节点同样参与交替计数"
]
},
{
"id": "segmented",
"title": "分段控制器 Segmented",
"demo": "sec-segmented",
"props": [
{
"n": "options",
"t": "OptionItem[]",
"r": 1,
"d": "分段选项列表(互斥),每项含 value / label / disabled"
},
{
"n": "value",
"t": "Signal<string>",
"r": 1,
"d": "当前选中值信号,双向绑定(点击写回,外部赋值即同步滑块)"
},
{
"n": "size",
"t": "'sm' | 'md'",
"d": "尺寸档位;缺省 md",
"df": "md"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "整组禁用:布尔直接置位,传信号则动态同步;单项禁用走 options[].disabled"
},
{
"n": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "切换回调(禁用项与重复点击当前项都不触发)"
}
],
"example": "segmented({ options: [{ value: 'day', label: '日' }, { value: 'week', label: '周' }], value: range })",
"steps": [
"和 tabs 的分工:tabs 是页签(下划线指示条 + 面板语义),segmented 是紧凑互斥切换(视图 / 单位 / 时间粒度)",
"value 必须传信号:点击写回信号,外部改信号滑块自动平移,不要在回调里另存一份状态",
"options[].disabled 置灰单项;disabled 传布尔或信号则整组失效(连滑块一起置灰)",
"键盘:←→ 在可用项间环形移动并选中,Tab 只落在当前项上(漫游 tabindex)",
"滑块几何由组件测量按钮位置得到,不要在外部改按钮宽度 / 内边距,否则需自行重渲染",
"需要带图标或更复杂布局的分段,改用 radioGroup + 自定义样式"
]
},
{
"id": "descriptions",
"title": "描述列表 Descriptions",
"demo": "sec-descriptions",
"props": [
{
"n": "items",
"t": "DescriptionItem[]",
"r": 1,
"d": "描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数)"
},
{
"n": "column",
"t": "1 | 2 | 3 | 4",
"d": "桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列",
"df": "3"
},
{
"n": "bordered",
"t": "boolean",
"d": "表格形态(带分隔线与字段名底纹);缺省 false 纯网格",
"df": "false"
},
{
"n": "size",
"t": "'sm' | 'md'",
"d": "尺寸档位;缺省 md",
"df": "md"
},
{
"n": "title",
"t": "string",
"d": "分组标题;缺省不渲染标题行"
},
{
"n": "layout",
"t": "'horizontal' | 'vertical'",
"df": "'horizontal'",
"d": "字段名位置:horizontal 在值左侧,vertical 在值上方(窄栏 / 长文本更耐读,配 bordered 时去掉标签区的分隔线)"
},
{
"n": "extra",
"t": "unknown",
"k": 1,
"d": "标题行右侧内容(如更新时间、刷新按钮);仅在传了 title 时渲染"
}
],
"example": "descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] })",
"steps": [
"详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验)",
"column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行",
"value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容",
"bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌",
"窄屏自动降为单列,无需在调用方写媒体查询",
"纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value",
"窄栏或长文本场景把 layout 设成 'vertical':字段名落到值上方,bordered 形态会自动去掉标签右侧的分隔线与底纹;标题行要放操作就传 extra(数组可放多个节点)"
]
},
{
"id": "statistic",
"title": "统计数值 Statistic",
"demo": "sec-statistic",
"props": [
{
"n": "title",
"t": "string",
"d": "指标名(小字灰标题);缺省不渲染"
},
{
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "数值信号:变化时滚动过渡到新值;倒计时模式可不传"
},
{
"n": "countdown",
"t": "number",
"d": "目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value"
},
{
"n": "precision",
"t": "number",
"d": "小数位,千分位分隔自动带;缺省 0",
"df": "0"
},
{
"n": "prefix",
"t": "string",
"d": "数字前缀(如 ¥);缺省不渲染"
},
{
"n": "suffix",
"t": "string",
"d": "数字后缀(如 元 / %);缺省不渲染"
},
{
"n": "trend",
"t": "'up' | 'down'",
"d": "环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染"
}
],
"example": "const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })",
"steps": [
"value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本",
"首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改",
"precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化",
"trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织",
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
"用户开启「减弱动效」时组件直接落终值,无需调用方判断",
"做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表"
]
},
{
"id": "list",
"title": "列表 List",
"demo": "sec-list",
"props": [
{
"n": "items",
"t": "ListItem[]",
"r": 1,
"d": "条目列表,每项 { title, description?, avatar?, extra? }"
},
{
"n": "header",
"t": "string",
"d": "列表头标题;缺省不渲染"
},
{
"n": "footer",
"t": "string",
"d": "列表尾说明;缺省不渲染"
},
{
"n": "bordered",
"t": "boolean",
"d": "是否带外边框与分隔线;缺省 false"
},
{
"n": "emptyText",
"t": "string",
"d": "无数据占位文案;缺省「暂无数据」"
},
{
"n": "onItemClick",
"t": "(item: ListItem, index: number) => void",
"k": 1,
"d": "条目点击回调;提供后条目可点击并带 hover 反馈"
}
],
"example": "list({ header: '待办', bordered: true, items: [{ title: '报销单 RB-2048', description: '等待复核', avatar: avatar({ name: '王' }) }], onItemClick: (item) => console.log(item.title) })",
"steps": [
"准备 items 数组,每项至少给 title,description 作次要说明",
"avatar 传头像组件(如 avatar({ name }))、extra 传右侧附加(如 tag(...)),不传该侧不留位",
"需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线",
"items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖"
]
},
{
"id": "carousel",
"title": "轮播 Carousel",
"demo": "sec-carousel",
"props": [
{
"n": "items",
"t": "unknown[]",
"r": 1,
"d": "每张幻灯片内容,顺序即播放顺序"
},
{
"n": "index",
"t": "Signal<number>",
"d": "当前下标信号;不传则内部维护"
},
{
"n": "autoplay",
"t": "number",
"d": "自动前进间隔(毫秒);缺省不自动播放"
},
{
"n": "height",
"t": "number",
"d": "固定高度像素;缺省由内容撑开"
},
{
"n": "label",
"t": "string",
"d": "区域无障碍名称;缺省「轮播」"
},
{
"n": "onChange",
"t": "(index: number) => void",
"k": 1,
"d": "切换回调:点击指示点/箭头/键盘/自动前进都会触发"
}
],
"example": "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) })",
"steps": [
"items 每项是任意节点(图、卡片、文字均可),顺序即播放顺序",
"index 传自己的信号即可外部受控(外部改信号轮播会跟),不传则由组件内部维护",
"height 控固定高度(缺省随内容撑开);autoplay 传毫秒数自动前进,鼠标悬停自动暂停",
"键盘:聚焦轮播后 ←→ 切换,事件不冒泡避免连带切换外层 tabs/menu",
"指示点与箭头都可点击,末张下一张环形回到首张"
]
},
{
"id": "tree",
"title": "树形 Tree",
"demo": "sec-tree",
"props": [
{
"n": "nodes",
"t": "TreeNode[]",
"r": 1,
"d": "根节点列表,每项 { key, label, children?, disabled? }"
},
{
"n": "expanded",
"t": "Signal<string[]>",
"d": "展开的 key 集合;不传则内部维护"
},
{
"n": "selected",
"t": "Signal<string>",
"d": "选中 key 信号;不传则内部维护"
},
{
"n": "defaultExpandAll",
"t": "boolean",
"d": "首屏展开全部分支;缺省 false"
},
{
"n": "searchable",
"t": "boolean",
"d": "顶部搜索框:按标签过滤,命中节点保留父链并自动展开;缺省 false"
},
{
"n": "onSelect",
"t": "(node: TreeNode) => void",
"k": 1,
"d": "选中回调:点击标签或 Enter/空格触发"
},
{
"n": "multi",
"t": "boolean",
"d": "是否显示勾选框并开启多选;缺省 false"
},
{
"n": "checked",
"t": "Signal<string[]>",
"d": "勾选 key 集合(multi 时生效);不传则内部维护"
},
{
"n": "onCheck",
"t": "(keys: string[]) => void",
"d": "勾选变更回调(父子已级联,传副本)"
}
],
"example": "const sel = signal(''); tree({ nodes: [{ key: 'src', label: 'src', children: [{ key: 'ui', label: 'ui' }] }], selected: sel, onSelect: (n) => console.log(n.key) })",
"steps": [
"节点树用 { key, label, children } 描述,key 唯一即可(选中与展开都按 key 记)",
"传 selected 信号即可外部受控;不传则组件内部维护选中态",
"点箭头只展开/收起,点标签才选中;叶子节点箭头位置留空以对齐",
"键盘:↑↓ 在可见节点间移动、Home/End 到首末行(只移动焦点),Enter/空格 选中、→ 展开、← 收起;disabled 节点点击与键盘都不响应",
"defaultExpandAll 首屏展开全部分支(外部已给 expanded 初值时以外部为准)",
"multi: true 时每行前置勾选框:勾父级下钻全选子孙(跳过禁用项),子孙全选后父级自动上勾、部分勾选显示半选",
"勾选框点击不触发选中(stopPropagation),键盘在多选模式下 Enter/空格改为切换勾选",
"节点多时开 searchable:输入即过滤,命中节点连同父链一起显示,隐藏的枝不参与方向键遍历"
]
},
{
"id": "upload",
"title": "文件选择 Upload",
"demo": "sec-upload",
"props": [
{
"n": "accept",
"t": "string",
"d": "接受的类型(同原生 accept),如 'image/*,.pdf'"
},
{
"n": "multiple",
"t": "boolean",
"d": "允许多选;缺省 false(单选时新选择覆盖旧的)"
},
{
"n": "hint",
"t": "string",
"d": "提示文案;缺省「点击或拖拽文件到此处」"
},
{
"n": "maxSize",
"t": "number",
"d": "单文件体积上限(字节);超限丢弃并在提示行说明,缺省不限"
},
{
"n": "maxCount",
"t": "number",
"d": "multiple 时的文件数上限;超出部分丢弃并提示,缺省不限"
},
{
"n": "disabled",
"t": "boolean",
"d": "禁用:不可点选、不接受拖拽"
},
{
"n": "onChange",
"t": "(files: File[]) => void",
"k": 1,
"d": "文件变化回调:选择/拖拽/移除后都触发,回传副本"
}
],
"example": "upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onChange: (files) => console.log(files.map((f) => f.name)) })",
"steps": [
"把 upload 放进表单,accept 限定类型、multiple 允许多选",
"需要拦体积就给 maxSize(字节),限制数量给 maxCount",
"被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验)",
"onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)",
"选中的图片会带 28px 缩略图(对象 URL,随文件离场即 revoke,不会泄漏);非图片只显示文件名与体积,点 × 移除"
]
},
{
"id": "notification",
"title": "通知卡片 Notification",
"demo": "sec-notification",
"props": [
{
"n": "title",
"t": "string",
"r": 1,
"d": "通知标题"
},
{
"n": "description",
"t": "string",
"d": "补充说明;缺省不渲染"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色(左侧条与圆点);缺省 info"
},
{
"n": "duration",
"t": "number",
"d": "自动关闭毫秒;缺省 0 = 常驻不自动关,非 0 时悬停 / 聚焦暂停计时"
},
{
"n": "closable",
"t": "boolean",
"d": "是否显示关闭按钮;缺省 true"
},
{
"n": "action",
"t": "unknown",
"d": "操作区内容(如按钮);缺省不渲染"
},
{
"n": "placement",
"t": "'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'",
"df": "bottom-right",
"d": "停靠角落;每个角落独立堆叠(同角最多 3 条),右上与 toast 同侧,建议次要信息放左侧"
},
{
"n": "key",
"t": "string",
"d": "同 key 复用同一张卡:再次调用只原地改标题 / 描述 / 语义色,不再堆新条(返回的句柄带 update 方法可主动改)"
}
],
"example": "const { close } = notification({ title: '导出完成', description: 'report.csv 已开始下载', variant: 'success', duration: 4000 }); // close() 手动关闭",
"steps": [
"调用即入列:右下角堆叠,同屏最多 3 条,超出自动挤掉最早一条",
"缺省常驻(duration 0),需要自动消失就传毫秒数,如 duration: 4000",
"返回 { el, close } 句柄:程序化关闭用 close(),需要自己挂交互就用 el",
"action 放操作区(常见为按钮);variant 决定左侧条与圆点语义色",
"与 toast 的分工:toast 右上角轻量瞬时,notification 右下角可长读、可操作",
"停靠角落用 placement 调:四个角各自独立堆叠与节流,顶部角落新通知从屏幕边缘长出、旧的往下让",
"进度类通知传 key: 'sync-job':第一次返回句柄,后续 job.update({ title, description, variant }) 就地把同一条卡改成 60% / 完成,不会像以前那样堆出三条;job.close() 收掉时同时释放 key 登记"
]
},
{
"id": "result",
"title": "结果页 Result",
"demo": "sec-result",
"props": [
{
"n": "status",
"t": "'success' | 'warning' | 'danger' | 'info'",
"d": "终态语义色(图标底色);缺省 info"
},
{
"n": "title",
"t": "string",
"r": 1,
"d": "主标题"
},
{
"n": "description",
"t": "string",
"d": "补充说明;缺省不渲染"
},
{
"n": "extra",
"t": "unknown",
"d": "操作区内容(常见为主次按钮);缺省不渲染"
}
],
"example": "result({ status: 'success', title: '提交成功', description: '工单 #2049 已受理', extra: button({ label: '查看工单', variant: 'primary', onClick: () => go() }) })",
"steps": [
"status 决定图标底色(success 绿 / warning 黄 / danger 红 / info 蓝),整体居中排布",
"title 必填;description 用于说明原因或下一步,超过一行自动居中换行",
"extra 放操作区,常见为主按钮 + 幽灵按钮;不传则不渲染",
"组件自带 role=\"status\",插入页面后读屏会播报终态文案"
]
},
{
"id": "transfer",
"title": "穿梭框 Transfer",
"demo": "sec-transfer",
"props": [
{
"n": "dataSource",
"t": "TransferItem[]",
"r": 1,
"d": "全量条目,左右两栏都按是否在 targetKeys 里派生"
},
{
"n": "targetKeys",
"t": "Signal<string[]>",
"d": "右侧集合信号;不传则组件内部维护,传了即外部受控"
},
{
"n": "titles",
"t": "[string, string]",
"d": "左右栏标题;缺省「待选列表 / 已选列表」"
},
{
"n": "searchable",
"t": "boolean",
"d": "是否显示搜索框;缺省 true,逐栏独立过滤"
},
{
"n": "height",
"t": "number",
"d": "列表区固定高度像素;缺省走样式表 220,超出滚动"
},
{
"n": "onChange",
"t": "(keys: string[]) => void",
"d": "右侧集合变化回调(搬运后触发,传副本)"
}
],
"example": "transfer({ targetKeys: enabled, titles: ['待选', '已启用'], onChange: (keys) => toast.info(`已启用 ${keys.length} 项`), dataSource: [{ key: 'a11y', label: '无障碍语义' }, { key: 'ssr', label: 'SSR(暂不可用)', disabled: true }] })",
"steps": [
"dataSource 是唯一数据源,条目归属由 targetKeys 决定;搬运只改 targetKeys,不删条目",
"两栏独立勾选,勾选后点中间 → / ← 整体搬运,搬完两栏勾选自动清空;无勾选时按钮自动禁用",
"每栏均有搜索框与全选:全选只覆盖当前搜索结果里未禁用的条目,部分勾选时全选框显示半选",
"disabled 条目不可勾选、不计入全选基数、也不会被搬运",
"传入 targetKeys 信号即为受控用法,外部写入即可程序化搬运(如「一键全选」)"
]
},
{
"id": "cascader",
"title": "级联选择 Cascader",
"demo": "sec-cascader",
"props": [
{
"n": "options",
"t": "CascaderOption[]",
"r": 1,
"d": "根级选项,children 递归出列,value 逐级拼成路径"
},
{
"n": "value",
"t": "Signal<string[]>",
"d": "选中路径信号;不传则内部维护,传了即外部受控"
},
{
"n": "placeholder",
"t": "string",
"d": "未选时占位文案;缺省「请选择」"
},
{
"n": "separator",
"t": "string",
"d": "路径分隔符;缺省「 / 」"
},
{
"n": "changeOnSelect",
"t": "boolean",
"d": "允许停在中间层;缺省 false 必须选到叶子才提交"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用触发器;支持信号响应式禁用"
},
{
"n": "onChange",
"t": "(path: string[]) => void",
"d": "选中回调(叶子或中间层,传副本)"
}
],
"example": "cascader({ value: areaPath, changeOnSelect: true, options: [{ value: 'east', label: '华东', children: [{ value: 'sh', label: '上海' }] }] })",
"steps": [
"传入嵌套 options,第一列是根级选项,悬停带 children 的项即展开下一列",
"点击叶子提交;changeOnSelect 为 true 时点中间层也提交(适合「省/市」这类粗粒度就够用的场景)",
"value 是路径数组(逐级 value),传信号即受控;触发器文案按 separator 拼接已提交路径",
"键盘:↑↓ 同列移动、→ 下钻、← 回退、Enter/空格 提交、Esc 收起(与弹窗共用一个 Esc 栈)",
"面板空间不足时自动向上翻转;disabled 选项不可点也不会展开下一列"
]
},
{
"id": "splitter",
"title": "可拖拽分栏 Splitter",
"demo": "sec-splitter",
"props": [
{
"n": "panes",
"t": "unknown[]",
"r": 1,
"d": "各栏内容(节点或纯文本),顺序即排列顺序"
},
{
"n": "direction",
"t": "'horizontal' | 'vertical'",
"d": "分割方向;缺省 horizontal(左右)"
},
{
"n": "sizes",
"t": "Signal<number[]>",
"d": "各栏百分比数组(和为 100);不传则内部均分"
},
{
"n": "min",
"t": "number",
"d": "每栏最小百分比;缺省 10,拖到边界即停"
},
{
"n": "onChange",
"t": "(sizes: number[]) => void",
"d": "拖拽 / 键盘调整后回调(传副本)"
}
],
"example": "splitter({ sizes: ratio, min: 15, panes: [leftPanel, rightPanel], onChange: (s) => ratio([...s]) })",
"steps": [
"panes 长度决定栏数与分隔条数(n 栏对应 n-1 条),栏宽由 sizes 百分比驱动",
"指针按住分隔条拖动即可改比例,拖动过程两栏之和恒定、各自不低于 min",
"分隔条可 Tab 聚焦,← → / ↑ ↓ 每次调 2%,按住 Shift 调 10%;双击复位为均分",
"direction 传 vertical 变为上下分栏,键盘方向键与光标同步切换",
"传入 sizes 信号即受控,外部写入可做「预设布局」切换(如 1:1 / 2:1)"
]
},
{
"id": "autoComplete",
"title": "自动补全 AutoComplete",
"demo": "sec-autocomplete",
"props": [
{
"n": "options",
"t": "AutoCompleteOption[] | ((q: string) => AutoCompleteOption[])",
"r": 1,
"d": "候选项:静态列表或按关键字计算的函数(可远程联想)"
},
{
"n": "value",
"t": "Signal<string>",
"d": "输入值信号;不传则内部维护,传了即外部受控"
},
{
"n": "placeholder",
"t": "string",
"d": "占位文案,同时作为无障碍名称"
},
{
"n": "emptyText",
"t": "string",
"d": "无匹配时的提示文案;缺省「无匹配项」"
},
{
"n": "maxItems",
"t": "number",
"d": "最多展示条数;缺省 8"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用输入与弹层"
},
{
"n": "onInput",
"t": "(query: string) => void",
"d": "每次输入回调,适合触发远程请求"
},
{
"n": "onSelect",
"t": "(option: AutoCompleteOption) => void",
"d": "选中回调(点击或 Enter)"
}
],
"example": "autoComplete({ value: acValue, placeholder: '输入组件名', options: [{ value: 'dataTable', hint: '表格' }], onSelect: (o) => toast.success(o.value) })",
"steps": [
"输入任何字符即按包含匹配(忽略大小写)过滤候选,空串时弹层不展开",
"↑↓ 在候选间环形移动并高亮,Enter 选中,Esc 收起,Tab 失焦也会收起",
"候选行支持右侧 hint(分类 / 拼音),选中后值写回输入框并触发 onSelect",
"options 传函数时每次输入都会用当前关键字调用,可在此发起远程请求并返回新数组",
"value 传信号即受控:外部写入会同步回填输入框内容"
]
},
{
"id": "datePicker",
"title": "日期选择 DatePicker",
"demo": "sec-datepicker",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "选中日期(YYYY-MM-DD)信号;区间模式下是起始日,不传则内部维护"
},
{
"n": "range",
"t": "boolean",
"df": "false",
"d": "区间模式:首次点击定起点(弹层不收起),再次点击定终点;点早了自动交换先后"
},
{
"n": "end",
"t": "Signal<string>",
"d": "区间模式的结束日信号;不传则内部维护"
},
{
"n": "onRangeChange",
"t": "(start: string, end: string) => void",
"k": 1,
"d": "区间两端都选定后的回调,参数已按先后排序(单日模式不触发)"
},
{
"n": "placeholder",
"t": "string",
"d": "未选时占位文案;缺省「请选择日期」(区间模式缺省「请选择日期范围」)"
},
{
"n": "min",
"t": "string",
"d": "最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点"
},
{
"n": "max",
"t": "string",
"d": "最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用触发器"
},
{
"n": "presets",
"t": "DatePreset[]",
"d": "快捷区间按钮组(仅区间模式渲染):每项给 label + days(相对今天含今天),或 label + from/to 绝对日期;越界结果按 min/max 钳制,点击即写入 value/end 并收起"
},
{
"n": "onChange",
"t": "(date: string) => void",
"d": "选中回调(点击日期或 Enter)"
}
],
"example": "datePicker({ value: dateVal, min: '2026-10-20', max: '2026-10-31', onChange: (d) => toast.success(d) })",
"steps": [
"点触发器展开当月网格,打开时视图自动对准已选日期(无选中则今天)",
"‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色",
"键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页",
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)",
"选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条",
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号",
"传 presets 在弹层顶部加一排快捷区间按钮:{ label: '最近 7 天', days: 7 } 走相对今天,{ label: '本月', from: '2026-10-01', to: '2026-10-31' } 走绝对日期(两者都给时 days 优先),点一下即写入并收起,回调照旧走 onRangeChange"
]
},
{
"id": "backTop",
"title": "返回顶部 BackTop",
"demo": "sec-backtop",
"props": [
{
"n": "target",
"t": "HTMLElement | Window",
"d": "监听的滚动容器;缺省 window(整页)"
},
{
"n": "threshold",
"t": "number",
"d": "滚动超过多少像素才浮现;缺省 300"
},
{
"n": "progress",
"t": "boolean",
"d": "是否显示滚动进度环;缺省 true"
},
{
"n": "onClick",
"t": "() => void",
"d": "回顶之后的回调(适合埋点)"
}
],
"example": "backTop({ threshold: 300, onClick: () => toast.info('已回到顶部') })",
"steps": [
"把返回的元素挂到页面任意位置即可,组件自身 position: fixed 定位在右下角",
"滚动越过 threshold 才浮现(淡入 + 上移微动画),回到阈值内自动隐藏",
"progress 为 true 时外环弧长等于已滚比例;内滚容器用 target 传入该元素",
"点击平滑回顶;系统开启「减少动效」时改为瞬间跳转"
]
},
{
"id": "colorPicker",
"title": "颜色选择 ColorPicker",
"demo": "sec-colorpicker",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "颜色值信号(CSS 颜色串,支持 var(--ua-*));不传则内部维护"
},
{
"n": "presets",
"t": "string[]",
"d": "预设色板;缺省取语义令牌色(accent/success/warning/danger/text/border)"
},
{
"n": "placeholder",
"t": "string",
"d": "未选时占位文案;缺省「请选择颜色」"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用触发器"
},
{
"n": "onChange",
"t": "(color: string) => void",
"d": "选定回调(点色板、取色器或手输回车)"
}
],
"example": "colorPicker({ value: colorVal, onChange: (c) => toast.success(c) })",
"steps": [
"点触发器展开弹层:上方是预设色板,下方是原生取色器 + 手输框",
"预设缺省直接用 --ua-* 令牌色,所以暗色主题切换后色块自动跟着变,无需维护两套色值",
"点色块即提交;手输框支持任意 CSS 颜色串(含 var(--ua-color-accent)),回车提交",
"触发器左侧色块始终预览当前值,点击外部或 Esc 收起"
]
},
{
"id": "tour",
"title": "引导漫游 Tour",
"demo": "sec-tour",
"props": [
{
"n": "steps",
"t": "TourStep[]",
"r": 1,
"d": "步骤序列:每步含 target 选择器、title、可选 description"
},
{
"n": "padding",
"t": "number",
"d": "聚光区相对目标的外扩像素;缺省 6"
},
{
"n": "onFinish",
"t": "() => void",
"d": "走完最后一步的回调"
},
{
"n": "onClose",
"t": "() => void",
"d": "中途跳过(× 或 Esc)的回调"
}
],
"example": "const t = tour({ steps: [{ target: '#sec-tour', title: '引导漫游', description: '按选择器定位' }], onFinish: () => toast.success('完成') }); document.body.append(t.el); t.start();",
"steps": [
"tour() 返回 { el, start, close } 句柄:el 必须挂到 body(fixed 定位,不占布局)",
"start() 从第一步播放,可重复调用重播;close() 立即退出并按跳过处理",
"聚光框按 target 选择器定位并外扩 padding,其余区域由超大 box-shadow 压暗(省掉独立遮罩层)",
"卡片优先落在目标下方,空间不足自动翻到上方,左右夹在视口内;滚动 / 缩放会实时重算",
"末步按钮文案变「完成」并触发 onFinish;× 或 Esc 触发 onClose,焦点归还开启前的元素"
]
},
{
"id": "watermark",
"title": "水印 Watermark",
"demo": "sec-watermark",
"props": [
{
"n": "text",
"t": "string",
"r": 1,
"d": "水印文字,支持 \\n 多行"
},
{
"n": "opacity",
"t": "number",
"d": "透明度;缺省 0.12"
},
{
"n": "rotate",
"t": "number",
"d": "旋转角度(度);缺省 -20"
},
{
"n": "gap",
"t": "number",
"d": "平铺间距像素;缺省 140"
},
{
"n": "fontSize",
"t": "number",
"d": "字号像素;缺省 14"
},
{
"n": "color",
"t": "string",
"d": "颜色(CSS 串或 var(--ua-*) 令牌);缺省取 --ua-color-text-muted 的实色"
},
{
"n": "children",
"t": "...unknown[]",
"d": "被水印覆盖的内容,作为第二个及之后的实参传入"
}
],
"example": "watermark({ text: '内部资料\\n仅限内网', gap: 160 }, card({ title: '受保护内容' }, h('p', null, '正文')))",
"steps": [
"第一个参数是配置,其后所有实参都被当作内容渲染在水印层下方",
"组件用一块 gap 见方的 canvas 画单个旋转单元,再 background-repeat 铺满,因此不需要监听尺寸变化",
"color 传 var(--ua-*) 时会先解析成计算值(canvas 不认 CSS 变量),所以暗色主题下颜色会自动跟随",
"覆盖层为 pointer-events:none,下方按钮/输入框照常可点;watermark 根节点自带圆角与裁剪"
]
},
{
"id": "checkboxGroup",
"title": "多选组 CheckboxGroup",
"demo": "sec-checkgroup",
"props": [
{
"n": "options",
"t": "CheckOption[]",
"r": 1,
"d": "选项集合:value / label / 可选 disabled"
},
{
"n": "value",
"t": "Signal<string[]>",
"d": "选中值数组信号;不传则内部维护"
},
{
"n": "min",
"t": "number",
"d": "最少选几项:到下限后已选项禁用,防止取消"
},
{
"n": "max",
"t": "number",
"d": "最多选几项:到上限后未选项禁用"
},
{
"n": "checkAll",
"t": "boolean",
"d": "是否显示全选行(含半选);缺省 false"
},
{
"n": "direction",
"t": "'horizontal' | 'vertical'",
"d": "排列方向;缺省 horizontal"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "整组禁用"
},
{
"n": "onChange",
"t": "(values: string[]) => void",
"d": "变更回调(传副本)"
}
],
"example": "checkboxGroup({ value: permPick, min: 1, max: 3, checkAll: true, options: [{ value: 'read', label: '读取' }, { value: 'write', label: '写入' }] })",
"steps": [
"options 顺序即渲染顺序;勾选后把整份数组写回 value 信号并触发 onChange",
"max 生效时未选项自动禁用,min 生效时已选项自动禁用——用禁用表达边界,不抛错也不静默回滚",
"checkAll 行的选中/半选只统计未禁用项,禁用项不计入基数",
"direction 传 vertical 改为纵向排列;disabled 支持信号,整组响应式禁用"
]
},
{
"id": "affix",
"title": "固钉 Affix",
"demo": "sec-affix",
"props": [
{
"n": "offset",
"t": "number",
"d": "距视口顶部多少像素后转为固定;缺省 0"
},
{
"n": "position",
"t": "'top' | 'bottom'",
"d": "固定边:贴顶放头部工具栏,贴底放底部操作条;缺省 top"
},
{
"n": "target",
"t": "HTMLElement | Window",
"d": "监听的滚动容器;缺省 window(整页)"
},
{
"n": "children",
"t": "...unknown[]",
"d": "被固定的内容,作为第二个及之后的实参传入"
}
],
"example": "affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正文')))",
"steps": [
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置",
"底部操作条传 position: 'bottom':占位容器底边越过「视口底 - offset」时才固定,对应的内联边距写 bottom"
]
},
{
"id": "multiSelect",
"title": "多选下拉 MultiSelect",
"demo": "sec-multiselect",
"props": [
{
"n": "options",
"t": "CheckOption[]",
"r": 1,
"d": "选项集合:value / label / 可选 disabled;每项可加 group 指定分组小标题"
},
{
"n": "value",
"t": "Signal<string[]>",
"d": "选中值数组信号;不传则内部维护"
},
{
"n": "placeholder",
"t": "string",
"d": "未选时占位文案;已选时让位给标签"
},
{
"n": "max",
"t": "number",
"d": "最多选几项:到上限后未选项自动禁用"
},
{
"n": "maxTagCount",
"t": "number",
"d": "标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示"
},
{
"n": "searchable",
"t": "boolean",
"d": "是否可输入过滤;缺省 true"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用整个下拉"
},
{
"n": "onChange",
"t": "(values: string[]) => void",
"d": "变更回调(传副本)"
},
{
"n": "clearable",
"t": "boolean",
"d": "开启后有选中值时在右缘显示一键清空按钮;缺省不渲染"
}
],
"example": "multiSelect({ value: tagPick, max: 3, placeholder: '选择技术栈', options: [{ value: 'ts', label: 'TypeScript' }, { value: 'css', label: 'CSS 令牌' }] })",
"steps": [
"选中项以标签形式回显,点标签上的 × 或空输入时按退格即可删除末个标签",
"点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)",
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
"value 传数组信号即受控,外部写入会同步标签区",
"选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算",
"候选分组同样用 group 字段:小标题是不可点击的 li(不占键盘高亮下标),选中后的标签顺序仍按点击顺序而非分组顺序"
]
},
{
"id": "anchor",
"title": "锚点导航 Anchor",
"demo": "sec-anchor",
"props": [
{
"n": "items",
"t": "AnchorItem[]",
"d": "锚点项集合,每项含 href(形如 #sec-x)、title、可选 disabled"
},
{
"n": "offset",
"t": "number",
"d": "定位与高亮判定预留的顶部距离,适合有吸顶栏的页面;缺省 0"
},
{
"n": "container",
"t": "HTMLElement | Window",
"d": "监听的滚动容器;缺省 window(整页滚动)"
},
{
"n": "label",
"t": "string",
"d": "nav 的无障碍名称;缺省「锚点导航」"
},
{
"n": "onChange",
"t": "(href: string) => void",
"d": "跳转后的回调,参数为命中的 href(适合埋点或联动)"
}
],
"example": "anchor({ items: [{ href: '#sec-button', title: '按钮' }, { href: '#sec-card', title: '卡片' }], offset: 72 })",
"steps": [
"在页面任意位置放一个容器,调用 anchor 并把返回的 nav 塞进去(固定侧栏或吸顶栏均可)",
"items 里每条 href 指向页面上真实存在的区块 id,标题即链接文案;目标缺失的项在滚动判定时自动跳过",
"页面有吸顶导航时把它的高度传给 offset,跳转与高亮判定都会预留这段距离",
"滚动容器不是整页时用 container 传入该元素;点击走平滑滚动,系统开启「减少动效」时改为瞬移,hash 用 replaceState 回写不堆历史"
]
},
{
"id": "popconfirm",
"title": "二次确认 Popconfirm",
"demo": "sec-popconfirm",
"props": [
{
"n": "title",
"t": "string",
"r": 1,
"d": "气泡标题,通常写成一句问句(如「删除这条记录?」)"
},
{
"n": "description",
"t": "string",
"d": "补充说明:影响范围、是否可撤销;缺省给一句通用提示"
},
{
"n": "confirmText",
"t": "string",
"d": "确认按钮文案;缺省「确定」"
},
{
"n": "cancelText",
"t": "string",
"d": "取消按钮文案;缺省「取消」"
},
{
"n": "variant",
"t": "'default' | 'danger'",
"d": "确认按钮语义色;删除类操作传 danger,缺省 default"
},
{
"n": "placement",
"t": "'top' | 'bottom'",
"d": "气泡方位;缺省 top,压在触发器上方不盖住被操作的行"
},
{
"n": "onConfirm",
"t": "() => void",
"k": 1,
"d": "点确定后的回调:普通回调先收起再执行;返回 Promise 时按钮转「处理中…」并禁用,成功才收起、失败留在气泡里可重试"
},
{
"n": "onCancel",
"t": "() => void",
"k": 1,
"d": "取消 / 点外部 / Esc 收起时的回调"
},
{
"n": "open",
"t": "Signal<boolean>",
"d": "受控开合信号;不传则内部维护,传了可由外部编程开合"
}
],
"example": "popconfirm({ title: '删除这条记录?', description: '删除后无法恢复', variant: 'danger', confirmText: '删除', onConfirm: () => toast.success('已删除') }, button({ label: '删除', variant: 'danger' }))",
"steps": [
"把要保护的触发器元素(按钮 / 图标)作为第二个参数传进去,返回的包裹节点替换原位置",
"写清 title(问句)与 description(影响范围),删除类操作把 variant 设成 danger,确认按钮会自动变成危险色",
"点确定先收起弹层再触发 onConfirm,回调里可以放心同步改数据;取消、点外部、Esc 一律走 onCancel",
"展开时焦点自动落在「取消」上,误按回车不会执行危险操作;需要外部控制开合时传 open 信号",
"删除 / 归档这类要打后端的操作把 onConfirm 写成 async:气泡不会立刻收起,确认按钮变「处理中…」并禁用,resolve 后才关,reject 时保持展开供重试"
]
},
{
"id": "calendar",
"title": "月历 Calendar",
"demo": "sec-calendar",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "选中日期(YYYY-MM-DD)信号;不传则内部维护"
},
{
"n": "month",
"t": "Signal<string>",
"d": "展示月份(YYYY-MM);不传则内部维护,初始跟随 value 或今天"
},
{
"n": "marker",
"t": "(date: string) => string | null",
"k": 1,
"d": "每日标记:返回文案即渲染小徽标(如「3 项」),同时写进 aria-label;返回 null 不标记"
},
{
"n": "onSelect",
"t": "(date: string) => void",
"k": 1,
"d": "选中日期回调(点击或 Enter / 空格)"
},
{
"n": "onMonthChange",
"t": "(month: string) => void",
"k": 1,
"d": "翻月回调,参数为 YYYY-MM(可在此拉取当月数据)"
}
],
"example": "const day = signal(''); calendar({ value: day, marker: (d) => (d === '2026-10-26' ? '2 项' : null), onSelect: (d) => toast.info(d) })",
"steps": [
"把 calendar 放在页面容器里即可,它自带标题行(月份 + 上下月箭头)、星期表头与 6 行网格",
"选中日期用 value 信号双向绑定;不传则组件内部维护,onSelect 里拿字符串自己处理",
"需要显示每天有几件事就传 marker:返回非空文案即渲染小徽标,并拼进 aria-label 让读屏也听得到",
"键盘:Tab 进入网格一次,←→ 逐日、↑↓ 逐周、Enter / 空格 选中;跨月会自动翻页并交接焦点"
]
},
{
"id": "typography",
"title": "排版文本 Typography",
"demo": "sec-typography",
"props": [
{
"n": "content",
"t": "string | Node",
"r": 1,
"d": "文本内容:字符串按纯文本渲染;传 DOM 则原样插入(此时不参与截断与复制)"
},
{
"n": "variant",
"t": "'title' | 'text' | 'paragraph'",
"df": "text",
"d": "排版形态,决定语义标签与字号:title 出 h1-h5、paragraph 出 p、text 出 span"
},
{
"n": "level",
"t": "1 | 2 | 3 | 4 | 5",
"df": "3",
"d": "标题层级,仅 variant=title 生效;越界值会被夹到 1-5"
},
{
"n": "type",
"t": "'secondary' | 'success' | 'warning' | 'danger'",
"d": "语义色调;缺省主文本色"
},
{
"n": "strong",
"t": "boolean",
"df": "false",
"d": "加粗强调"
},
{
"n": "underline",
"t": "boolean",
"df": "false",
"d": "下划线(可为链接式文案保留指引视觉)"
},
{
"n": "delete",
"t": "boolean",
"df": "false",
"d": "删除线,用于作废文案;会同时弱化文字颜色"
},
{
"n": "mark",
"t": "boolean",
"df": "false",
"d": "高亮底纹(警示色 24% 混透明),用于搜索命中片段"
},
{
"n": "code",
"t": "boolean",
"df": "false",
"d": "等宽代码样式,用于行内字段名 / 命令"
},
{
"n": "align",
"t": "'left' | 'center' | 'right'",
"d": "水平对齐;缺省左对齐"
},
{
"n": "ellipsis",
"t": "number",
"d": "截断行数(1 起):超出显示省略号,纯文本自动挂 title 供悬停看全文;缺省不截断"
},
{
"n": "copyable",
"t": "boolean",
"df": "false",
"d": "右侧附复制按钮:点击写剪贴板并回显「已复制 / 复制失败」,1.2 秒后复原"
}
],
"example": "typography({ variant: 'title', level: 2, content: '部署说明' }); typography({ content: 'RB-2048', code: true, copyable: true }); typography({ variant: 'paragraph', ellipsis: 2, content: '一段很长的说明……' })",
"steps": [
"正文一律用 variant='text' 或 'paragraph',标题用 variant='title' + level,标签自动出 h1-h5,别自己写 h 标签再叠样式",
"文内强调用装饰位组合:strong 加粗、mark 高亮、code 等宽、underline / delete 表示链接与作废,一个组件内可叠加",
"长描述(卡片摘要、表格说明)用 ellipsis 给行数:超出显示省略号并自动挂 title,悬停即看全文,不必额外做展开按钮",
"订单号 / 密钥 / 命令这类要复制的内容传 copyable:按钮独立于文本节点,复制失败会回显「复制失败」而不是静默",
"content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)",
"段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙"
]
},
{
"id": "grid",
"title": "栅格容器 Grid",
"demo": "sec-grid",
"props": [
{
"n": "columns",
"t": "number",
"df": "3",
"d": "固定列数(按等宽铺开);窄屏(≤640px)由样式表统一降为单列"
},
{
"n": "min",
"t": "string",
"d": "自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,列数随可用宽度自然增减,此时忽略 columns"
},
{
"n": "gutter",
"t": "SpaceSize",
"df": "md",
"d": "列间距:sm/md/lg 走令牌,数字按像素"
},
{
"n": "align",
"t": "'start' | 'center' | 'end' | 'stretch'",
"df": "stretch",
"d": "交叉轴对齐:同行子项高度不一时决定各自贴哪边或拉满"
},
{
"n": "dense",
"t": "boolean",
"df": "false",
"d": "紧凑填充:小项回填前面留下的空洞(瀑布式排布)"
}
],
"example": "grid({ columns: 3, gutter: 'md' }, card({ title: '实例数' }, statistic({ value: total })), card({ title: '错误率' }, statistic({ value: errRate }))); grid({ min: '240px', align: 'start' }, ...items)",
"steps": [
"页面骨架用 grid({ columns: n }) 铺等宽卡片:子项宽度由列模板算,别在子项上写 width,窄屏会自动变成单列",
"卡片宽度要求「不窄于 X」时用 min(如 '240px'):auto-fit 会按可用宽度决定列数,比手写断点稳,此时不要再传 columns",
"列间距统一走 gutter(sm/md/lg 令牌或像素),不要在子项上写 margin——相邻 margin 会叠加成双倍间距",
"同行卡片高度参差时:要等高(默认 stretch),要各自顶端对齐传 align: 'start'",
"子项必须是单个元素(card / div / 自定义块),grid 只负责排布且已对子项设 min-width: 0,长文本不会撑破列宽",
"只要横向一排展示且元素不等宽,用 space 更轻;grid 面向二维对齐"
]
},
{
"id": "floatButton",
"title": "悬浮操作按钮 FloatButton",
"demo": "sec-floatbutton",
"props": [
{
"n": "icon",
"t": "string | Node",
"d": "图标:字符串按字符(emoji / 符号)渲染,传 DOM 原样插入"
},
{
"n": "label",
"t": "string",
"d": "文字标签;给了就出「图标 + 文字」胶囊,缺省为纯图标圆形"
},
{
"n": "badge",
"t": "string | number",
"d": "角标(数字或短文案,如 3 / '新');缺省不渲染"
},
{
"n": "tooltip",
"t": "string",
"d": "悬停提示,同时用作 aria-label;纯图标时必传,否则读屏听到默认「快捷操作」"
},
{
"n": "position",
"t": "'right-bottom' | 'left-bottom'",
"df": "right-bottom",
"d": "停靠角落;右下与 backTop 同侧,需同时出现时错开一左一右"
},
{
"n": "offset",
"t": "number",
"df": "24",
"d": "距边距离(px);同角落堆叠多个时按倍数递增"
},
{
"n": "onClick",
"t": "() => void",
"k": 1,
"d": "点击回调"
}
],
"example": "document.body.append(floatButton({ icon: '+', label: '新建工单', position: 'left-bottom', onClick: () => toast.success('已新建') })); document.body.append(floatButton({ icon: '?', tooltip: '帮助中心', badge: 3 }))",
"steps": [
"把返回节点挂到 document.body(不是页面容器内):它是 fixed 定位,挂在容器里会被 overflow: hidden 裁剪",
"只有图标时务必传 tooltip:它同时是 aria-label,读屏用户靠它知道按钮干什么;有 label 时 tooltip 可省",
"label 存在即「图标 + 文字」胶囊,只有图标即 44px 圆形;两种形态都保证触摸目标不小于 44px",
"同一角落要放多个按钮时递增 offset(如 24 / 80),或把它们并进同一个 flex 容器统一排布——组件自身不做堆叠计算",
"右下角已被 backTop 占用,页面同时有返回顶部时用 position: 'left-bottom'",
"纯展示 + 单回调,没有选中态与开合状态;需要多级操作请用 menu / popover 挂在 onClick 上"
]
},
{
"id": "mentions",
"title": "@提及输入 Mentions",
"demo": "sec-mentions",
"props": [
{
"n": "items",
"t": "MentionItem[]",
"r": 1,
"d": "候选池:每项含 name(插入正文的名字)、label(显示名)、description(说明)、keywords(只参与过滤的额外匹配词)"
},
{
"n": "value",
"t": "Signal<string>",
"d": "受控文本信号;不传则组件内部维护(拿不到外部读取时不要传)"
},
{
"n": "rows",
"t": "number",
"df": "3",
"d": "输入框行数"
},
{
"n": "placeholder",
"t": "string",
"d": "占位文案"
},
{
"n": "onSelect",
"t": "(item: MentionItem) => void",
"k": 1,
"d": "选中某人后的回调(弹层已收起,正文已替换)"
},
{
"n": "onInput",
"t": "(text: string) => void",
"k": 1,
"d": "每次输入的回调,参数为当前全文(已写回 value 信号)"
}
],
"example": "const text = signal(''); mentions({ items: [{ name: 'zhangsan', label: '张三', description: '前端', keywords: ['zs'] }], value: text, placeholder: '输入 @ 叫人', onSelect: (it) => toast.info(it.label) })",
"steps": [
"在输入框里键入 @ 触发弹层,继续打字按 name / keywords 过滤(拼音、工号放 keywords,不出现在候选行里)",
"键盘:↑↓ 环形移动高亮、Enter 或 Tab 选中、Esc 只收这一层弹层(stopPropagation,不会连带关掉外层弹窗)",
"选中后组件按光标位置把 @片段替换成「@名字 + 空格」,光标落在其后,可以接着打字",
"受控用法:传 value 信号并在 onSelect / onInput 里读最新全文;外部改信号会同步回输入框,别同时再手写 input 事件",
"弹层贴在输入框下沿而非跟随光标行(多行输入里更稳定,也不会被行高算错),需要跟随光标请自绘",
"要限制可选人范围就在 items 上做过滤,组件不做远程搜索;远程场景用 onInput 拉数据后重建 items"
]
},
{
"id": "treeSelect",
"title": "树形选择 TreeSelect",
"demo": "sec-treeselect",
"props": [
{
"n": "nodes",
"t": "TreeNode[]",
"r": 1,
"d": "根节点列表,结构同 tree:key / label / children / disabled"
},
{
"n": "value",
"t": "Signal<string>",
"d": "单选选中 key;不传则内部维护"
},
{
"n": "checked",
"t": "Signal<string[]>",
"d": "多选勾选 key 集合(multi 时生效);不传则内部维护"
},
{
"n": "multi",
"t": "boolean",
"df": "false",
"d": "多选模式:显示勾选框、父子级联、选中后弹层不收起"
},
{
"n": "searchable",
"t": "boolean",
"df": "false",
"d": "弹层内带搜索框(按标签过滤并保留父链)"
},
{
"n": "placeholder",
"t": "string",
"df": "请选择",
"d": "未选中时触发器文案"
},
{
"n": "disabled",
"t": "boolean",
"df": "false",
"d": "禁用整个触发器(不响应点击)"
},
{
"n": "onSelect",
"t": "(node: TreeNode) => void",
"k": 1,
"d": "单选选中回调(此时弹层已收起)"
},
{
"n": "onChange",
"t": "(keys: string[]) => void",
"k": 1,
"d": "多选勾选变更回调,传副本且父子已级联"
}
],
"example": "const dept = signal(''); treeSelect({ nodes: [{ key: 'tech', label: '技术中心', children: [{ key: 'fe', label: '前端组' }] }], value: dept, searchable: true, onSelect: (n) => toast.info(n.label) })",
"steps": [
"表单里选层级归属(部门 / 分类 / 区域)用它,不要用 cascader——cascader 要求逐级选到叶子,treeSelect 允许选中间节点,也允许任意深度",
"单选默认选中即收起;要连续看结构就传 multi,勾选后弹层保持展开,触发器回显「已选 N 项」",
"需要在大树里找人/找分类就开 searchable:搜索按标签过滤并保留命中节点的父链,路径不会断",
"Esc 与其他弹层共享键盘栈(只关最上层),点外部也会收起;展开时焦点自动落到搜索框",
"选中值只存 key(单选 value / 多选 checked 数组),显示名由 nodes 反查——改 label 不需要迁移数据",
"禁用单个节点用 nodes 里的 disabled(该节点不可选,子节点仍可选);禁用整个控件用 disabled"
]
},
{
"id": "link",
"title": "链接 Link",
"demo": "sec-link",
"props": [
{
"n": "href",
"t": "string",
"r": 1,
"d": "目标地址:站内相对路径或绝对 URL"
},
{
"n": "label",
"t": "string",
"r": 1,
"d": "链接文案"
},
{
"n": "target",
"t": "'_self' | '_blank' | '_parent' | '_top'",
"d": "打开方式;缺省同窗口。传 _blank 会自动按站外处理(补 rel 与图标)"
},
{
"n": "external",
"t": "boolean",
"df": "false",
"d": "站外链接:补 ↗ 图标、强制新窗口、加 rel=noopener noreferrer,并给读屏播报「在新窗口打开」"
},
{
"n": "muted",
"t": "boolean",
"df": "false",
"d": "弱化色,用于次要链接与页脚"
},
{
"n": "disabled",
"t": "boolean",
"df": "false",
"d": "禁用:去掉 href、拦点击,保留可聚焦与 aria-disabled(仍能被 Tab 到,读屏能听出不可用)"
},
{
"n": "onClick",
"t": "(e: Event) => void",
"k": 1,
"d": "点击回调(禁用时不触发;可用于埋点,returnValue 不影响默认跳转)"
}
],
"example": "link({ href: '/docs', label: '查看文档' }); link({ href: 'https://example.com', label: '外部站点', external: true }); link({ href: '#', label: '说明页', muted: true })",
"steps": [
"站内跳转用相对路径、站外用 external: true:会自动补 ↗、target=_blank、rel=noopener noreferrer,并给读屏播报开了新窗口",
"页脚 / 次要说明用 muted 弱化;正文里的行内链接保持默认主色,与 typography 的 underline 区分用途(后者不跳转)",
"需要「点了先确认再跳」这类拦截,在 onClick 里做判断后自行处理,组件不接管默认行为(除禁用外都放行)",
"禁用态只是去掉 href 并保持 aria-disabled,不做视觉上的「像普通文字」处理——需要完全不可聚焦请在外层控制渲染",
"纯样式的「看起来像链接」用 typography({ underline: true }),不要为了样式引入带 href 的 a 标签"
]
},
{
"id": "avatarGroup",
"title": "头像组 AvatarGroup",
"demo": "sec-avatar",
"props": [
{
"n": "items",
"t": "AvatarItem[]",
"r": 1,
"d": "成员列表:每项含 name(姓名)、src(图片地址)、variant(首字底色语义档)"
},
{
"n": "max",
"t": "number",
"df": "4",
"d": "最多展示几个,超出折叠成 +N(至少展示 1 个)"
},
{
"n": "size",
"t": "number",
"df": "32",
"d": "头像直径像素,与 avatar 的 size 同义"
}
],
"example": "avatarGroup({ items: [{ name: '张三' }, { name: '李四', variant: 'success' }, { name: '王五' }], max: 4 })",
"steps": [
"协作列表 / 评论头部展示参与人用它,比并排 avatar 省横向空间:叠放用负边距 + 底色描边切出入口",
"成员多时设 max:前 max 个照常显示,其余折叠成一个 +N,悬停任意头像都能看到全部姓名",
"只传 name 即首字占位,有图片就传 src;底色语义档用 variant(default / success / warning / danger)",
"整组是展示型元素:要有点击行为就在外面包 button 或 link,不要给单个头像加交互",
"size 与单个 avatar 一致,混排时把并排 avatar 与 avatarGroup 传同一个 size,视觉才对得上"
]
},
{
"id": "virtualList",
"title": "虚拟列表 VirtualList",
"demo": "sec-vlist",
"props": [
{
"n": "items",
"t": "unknown[]",
"r": 1,
"d": "全量数据(不切片,由组件按滚动位置取窗口)"
},
{
"n": "itemHeight",
"t": "number",
"r": 1,
"d": "每行固定高度像素;虚拟化前提是行高一致"
},
{
"n": "height",
"t": "number",
"r": 1,
"d": "容器可视高度像素,超出即容器内滚动"
},
{
"n": "overscan",
"t": "number",
"d": "上下各多渲染的缓冲条数;缺省 5,滚动更平滑",
"df": "5"
},
{
"n": "renderItem",
"t": "(item, index) => unknown",
"r": 1,
"d": "行内容渲染器:返回字符串 / DOM / 响应式值"
}
],
"example": "const vlist = virtualList({ items: users, itemHeight: 44, height: 320, renderItem: (u) => u.name }); vlist.scrollTo(500);",
"steps": [
"itemHeight 必须与每行真实高度一致:行高由组件钉死,行内容超高会溢出,请保持单行或内部滚动",
"height 是容器可视高度(像素),列表在容器内滚动,不会撑开页面",
"renderItem 第二参是原始下标(全量数据中的位置),不是窗口内序号",
"滚回某条用句柄 scrollTo(index),越界自动钳制;DOM 里只存在可视窗口的行,勿按全量下标查行节点",
"数据整体变化(换数据源)请销毁重建实例:items 是创建时快照,不响应外部数组突变",
"滚动事件内部已 rAF 合帧,滚动回调无需节流"
]
},
{
"id": "commandPalette",
"title": "命令面板 CommandPalette",
"demo": "sec-cmdk",
"props": [
{
"n": "open",
"t": "Signal<boolean>",
"r": 1,
"d": "显隐信号:外部写 true 唤起,组件内部关闭也会写回"
},
{
"n": "items",
"t": "CommandPaletteItem[]",
"r": 1,
"d": "全量命令,创建时快照;含 id / label / group / hint / keywords"
},
{
"n": "placeholder",
"t": "string",
"d": "搜索框占位文案;缺省「搜索命令…」"
},
{
"n": "emptyText",
"t": "string",
"d": "无匹配时的空状态文案;缺省「没有匹配的命令」"
},
{
"n": "hotkey",
"t": "boolean",
"d": "是否注册 ⌘K / Ctrl+K 全局唤起;缺省 true,多实例场景可关掉自己绑",
"df": "true"
},
{
"n": "onSelect",
"t": "(item) => void",
"r": 1,
"d": "回车或点击选中后回调:先收起并归还焦点,再回调"
}
],
"example": "const cmdk = commandPalette({ open: signal(false), items: [{ id: 'save', label: '保存当前页', group: '文件', hint: '⌘S' }, { id: 'invite', label: '邀请成员', group: '成员' }], onSelect: (it) => toast.info(`执行 ${it.label}`) }); document.body.append(cmdk.el); cmdk.open();",
"steps": [
"把返回的 el 挂到 body(面板是 fixed + 遮罩,挂在 section 里也定位正确),之后用 open 信号或句柄 open()/close() 控制显隐",
"items 是创建时快照:命令表变化(权限变化后菜单增减)请重建实例,不要直接改数组",
"过滤看 label / hint / keywords 三层:中文别名与拼音首字母塞进 keywords 即可支持,不必改组件",
"同组连续排列才合并成一段并画组标题;分组顺序按 items 顺序,组件不排序",
"键盘:↑↓ 环形选择、Home/End 跳首尾、回车执行、Esc 交给弹层栈(嵌套弹窗时只关最上层);鼠标划过即高亮",
"hotkey 缺省注册 ⌘K / Ctrl+K,同一页面多个面板请只留一个开 hotkey,其余传 hotkey: false"
]
},
{
"id": "timePicker",
"title": "时间选择 TimePicker",
"demo": "sec-timepicker",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "选中时间信号(HH:mm);不传则内部维护"
},
{
"n": "minuteStep",
"t": "number",
"d": "分钟步长;缺省 5,超过 30 按 30 处理(如 15 只有 00/15/30/45)"
},
{
"n": "clearable",
"t": "boolean",
"d": "显示清空按钮 ×(仅在有值时出现);缺省 false,键盘 Delete / Backspace 等价"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"d": "触发器高度档位,与 textField / select 同口径;缺省 md"
},
{
"n": "placeholder",
"t": "string",
"d": "未选时占位文案;缺省「请选择时间」"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用触发器"
},
{
"n": "onChange",
"t": "(time: string) => void",
"d": "写回回调:选时即触发一次(分默认 00),选完分再触发一次"
}
],
"example": "const at = signal('09:30'); timePicker({ value: at, minuteStep: 15, onChange: (t) => toast.info(`定在 ${t}`) })",
"steps": [
"创建信号 at = signal('09:30') 并传入 value:组件读它显示、选完写回,双向绑定",
"点触发器展开时 / 分两列:当前值所在格高亮并自动滚到列中部;还没选过时停在此刻(当前小时 / 当前分钟档),打开即可就近选",
"先点时列定小时(分默认落到首个步长值,值立刻可用),再点分列即完成并收起,焦点回到触发器",
"列内可用 ↑↓ 环形移动、Home/End 直达首末;Tab、Esc、点击外部都能正常进出与收起",
"需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档",
"需要允许回到「未选」态就传 clearable: true:有值时触发器右侧出现 ×(不进 Tab 序),聚焦触发器按 Delete / Backspace 也能清空,回调收到空串",
"要和输入框并排对齐就传 size(sm / md / lg):高度走 --ua-control-h-* 三档,与 textField、select 的档位完全同源,不会出现「旁边的输入框矮 6px」",
"传入值不在步长档上时(如 09:07 + 15 分钟档)不会「没有高亮」:分列按最近档高亮并滚到位,点它即落到 09:15;原值在触发器上照常显示,直到用户重新选",
"收起态键盘微调:聚焦触发器按 ↑↓ 按步长加减(12:30 步长 15 → 12:45),Shift + ↑↓ 按小时加减,跨 00:00 / 23:xx 自动环形;改完同样触发 onChange",
"打开弹层后焦点直接落在时列当前格(键盘用户不必再 Tab 找):列内 ↑↓ 立即生效,Esc 收起后焦点回到触发器"
]
},
{
"id": "pinInput",
"title": "验证码输入 PinInput",
"demo": "sec-pininput",
"props": [
{
"n": "value",
"t": "Signal<string>",
"r": 1,
"d": "当前验证码(数字拼接串)信号,双向绑定:格子输入写回、外部赋值同步回格子"
},
{
"n": "length",
"t": "number",
"df": "6",
"d": "格数;缺省 6"
},
{
"n": "mask",
"t": "boolean",
"df": "false",
"d": "密文显示(type=password,并声明 one-time-code 便于短信验证码自动填充);缺省明文"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "任一格变化回调(参数为当前已输入拼接串)"
}
],
"example": "const code = signal(''); pinInput({ value: code, length: 6, onChange: (v) => console.log(v) })",
"steps": [
"用 signal('') 创建验证码信号并传入 value;拼接串随输入实时更新,长度满格即验证码就绪",
"输入一位自动进下一格;退格在本格为空时回删上一格;←/→ 在格间移动;聚焦即全选,直接敲键覆盖",
"粘贴会自动分发:整段验证码(位数 ≥ 格数)从首格铺起,零散片段从当前格续填",
"mask: true 走密文显示并声明 one-time-code,短信验证码可自动填充;disabled 支持布尔或信号禁用"
]
},
{
"id": "copyButton",
"title": "复制按钮 CopyButton",
"demo": "sec-copybutton",
"props": [
{
"n": "text",
"t": "string | (() => string)",
"r": 1,
"d": "待复制内容;传函数则在点击瞬间取实时值(如输入框当前值)"
},
{
"n": "label",
"t": "string",
"df": "复制",
"d": "按钮文案"
},
{
"n": "copiedLabel",
"t": "string",
"df": "已复制",
"d": "复制成功后的短暂文案(2s 后还原为 label)"
},
{
"n": "variant",
"t": "'primary' | 'ghost' | 'danger'",
"df": "ghost",
"d": "视觉强度,沿用按钮三档"
},
{
"n": "size",
"t": "'sm' | 'md' | 'lg'",
"df": "md",
"d": "高度档位"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步"
},
{
"n": "onCopied",
"t": "(text: string) => void",
"k": 1,
"d": "复制成功回调(参数为实际写入的内容)"
}
],
"example": "copyButton({ text: 'npm install @ui-arch/ui', onCopied: (t) => console.log(t) })",
"steps": [
"传 text 即可用,缺省幽灵按钮、文案「复制」;复制成功后短暂换成「已复制」,2s 自动还原",
"内容需要实时取就传函数:text: () => textFieldValue(),点击瞬间才求值",
"label / copiedLabel 可改文案;variant / size 透传给内部 button,视觉与普通按钮完全一致",
"非安全上下文(http)或旧浏览器自动降级为隐藏 textarea + execCommand,无需业务处理兼容"
]
},
{
"id": "countdown",
"title": "倒计时 Countdown",
"demo": "sec-countdown",
"props": [
{
"n": "seconds",
"t": "number",
"r": 1,
"d": "总时长(秒),> 0"
},
{
"n": "running",
"t": "boolean",
"df": "true",
"d": "创建即开始倒数;传 false 用句柄 start 手动启动"
},
{
"n": "format",
"t": "(seconds: number) => string",
"d": "展示格式化;缺省 mm:ss 两位补零"
},
{
"n": "onFinish",
"t": "() => void",
"k": 1,
"d": "归零回调(自动停表后触发)"
}
],
"example": "const cd = countdown({ seconds: 60, onFinish: () => console.log('结束') }); // cd.start / stop / reset",
"steps": [
"传 seconds 即可用,缺省挂载即开始、mm:ss 展示;返回句柄 { el, start, stop, reset }",
"running: false 先摆好再手动 start,典型场景是「发送验证码」后才开始 60s 重发倒计时",
"归零自动停表并触发 onFinish;之后 start() 会重开一轮,reset() 复位到总时长",
"format 自定义展示:如只显示秒 (s) => `${s}s`,或带上小时位"
]
},
{
"id": "kbd",
"title": "键盘按键 Kbd",
"demo": "sec-kbd",
"props": [
{
"n": "keys",
"t": "string | string[]",
"r": 1,
"d": "按键内容:单键字符串或多键组合数组(组合以 + 连接展示)"
}
],
"example": "kbd({ keys: 'Esc' })、kbd({ keys: ['Ctrl', 'K'] })",
"steps": [
"传字符串展示单键帽:kbd({ keys: \"Esc\" }),适合文档里说明单个按键",
"传数组展示组合键:kbd({ keys: [\"Ctrl\", \"K\"] }),键帽之间以弱化的 + 连接(+ 对读屏隐藏)",
"常与提示文案混排:行内 baseline 对齐,键帽底边加厚一线出立体感",
"组件只画键帽,不做平台映射:Ctrl 与 ⌘ 的替换由调用方按平台判断后传进来(写死 Ctrl 在 Mac 上就是错的)"
]
},
{
"id": "highlight",
"title": "关键词高亮 Highlight",
"demo": "sec-highlight",
"props": [
{
"n": "text",
"t": "string | Signal<string>",
"r": 1,
"d": "原文;传信号则随信号变化响应式重画"
},
{
"n": "keyword",
"t": "string | Signal<string>",
"r": 1,
"d": "关键词(大小写不敏感、全部命中,首尾空白忽略);空串不高亮、原样展示"
}
],
"example": "highlight({ text: 'Alpha Beta', keyword: 'beta' })",
"steps": [
"传 text + keyword 即可,命中片段以淡黄 mark 底展示,适合搜索结果 / 补全列表的匹配说明",
"两个入参都接受信号:搜索框输入实时联动高亮,无需手动重渲染",
"大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略",
"keyword 为空或纯空白时原样返回文本,既不抛错也不全高亮:搜索框清空那一刻不用特判"
]
},
{
"id": "inputTag",
"title": "标签输入 InputTag",
"demo": "sec-inputtag",
"props": [
{
"n": "value",
"t": "Signal<string[]>",
"r": 1,
"d": "已选标签集合,双向绑定:成标签与删 chip 都写回信号"
},
{
"n": "placeholder",
"t": "string",
"r": 0,
"d": "未满时的占位文案;缺省「输入后回车添加」"
},
{
"n": "max",
"t": "number",
"r": 0,
"d": "最多允许的标签条数;缺省 0 表示不限,满额后输入框禁用并提示上限"
},
{
"n": "maxLength",
"t": "number",
"r": 0,
"d": "单个标签最大字符数;缺省 24,超出直接截断"
},
{
"n": "allowDuplicate",
"t": "boolean",
"r": 0,
"d": "是否允许重复标签;缺省 false(重复的静默丢弃)"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"r": 0,
"d": "禁用态:布尔或信号,true 时不可输入也不可删"
},
{
"n": "ariaLabel",
"t": "string",
"r": 0,
"d": "输入框无障碍标签;缺省「标签输入」"
},
{
"n": "onChange",
"t": "(tags: string[]) => void",
"r": 0,
"d": "标签集合变化回调,参数是变化后的完整集合"
}
],
"example": "const tags = signal(['前端', '设计']);\ninputTag({ value: tags, max: 5, onChange: (list) => console.log(list.length) })",
"steps": [
"传一个 signal<string[]> 作为 value:回车或逗号把当前输入转成 chip,写回信号后立即重画",
"点 chip 上的 × 删除单个标签,删完焦点交回输入框(chip 一删按钮就消失,不接住焦点键盘用户要从头 Tab);输入框为空时按退格键删掉最后一个(手不离键盘也能改)",
"输入后直接失焦也会成标签,不必强行回车;max 满额后输入框禁用,占位文案变成「最多 N 个标签」",
"整段粘贴「上海,北京,深圳」这类带分隔符的文字会自动拆成多个标签(半角/全角逗号、换行、制表符都认),单个值仍走浏览器默认粘贴",
"要区分大小写重复判断就传 allowDuplicate: true;默认按原样去重",
"样式复用输入组与标签:外框聚焦光环、chip 圆角与语义色都跟既有组件一致,无新增视觉字面量"
]
},
{
"id": "layout",
"title": "布局骨架 Layout",
"demo": "sec-layout",
"props": [
{
"n": "header",
"t": "unknown",
"d": "页头内容(文案 / 节点);缺省不渲染页头"
},
{
"n": "sider",
"t": "unknown",
"d": "侧栏内容(通常是 menu);缺省则内容区独占整行"
},
{
"n": "footer",
"t": "unknown",
"d": "页脚内容;缺省不渲染页脚"
},
{
"n": "siderWidth",
"t": "number",
"d": "侧栏展开宽度(px);缺省 220"
},
{
"n": "collapsible",
"t": "boolean",
"d": "是否可折叠:true 时页头左侧出现折叠开关"
},
{
"n": "collapsed",
"t": "Signal<boolean>",
"d": "折叠态信号;传入则双向绑定(外部可读数),缺省内部自管"
},
{
"n": "sticky",
"t": "boolean",
"d": "页头是否吸顶;缺省 false(长内容里建议打开)"
}
],
"example": "layout({ header: '控制台', sider: menu({ items }), footer: '© 2026', collapsible: true, sticky: true }, card({ title: '内容区' }, ...))",
"steps": [
"把整页交给 layout:children 就是内容区,页头 / 侧栏 / 页脚用 header / sider / footer 三个入参一次给全",
"侧栏通常放 menu 或 anchor,宽度用 siderWidth 调(缺省 220px,够放四项中文菜单)",
"需要折叠时传 collapsible: true,页头左侧自动出现开关;想把折叠态接到别处(比如只显示图标栏)就传 collapsed 信号读它的值",
"长内容页面打开 sticky,页头吸顶;内容区自己滚动,不会把整页撑高",
"只要内容不要骨架时省略 header / sider / footer:不套多余容器,内容区独占整行"
]
},
{
"id": "jsonViewer",
"title": "JSON 树查看 JsonViewer",
"demo": "sec-jsonviewer",
"props": [
{
"n": "value",
"t": "unknown",
"d": "任意可序列化数据:对象 / 数组 / 标量都行"
},
{
"n": "depth",
"t": "number",
"d": "默认展开层数;缺省 2(看得到一级结构又不至于刷屏)"
},
{
"n": "rootLabel",
"t": "string",
"d": "根节点键名;缺省 root"
},
{
"n": "maxHeight",
"t": "number",
"d": "最高高度(px),超出在框内滚动;缺省不限高"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名;缺省「JSON 数据」"
}
],
"example": "jsonViewer({ value: payload, depth: 2, rootLabel: 'response', maxHeight: 320 })",
"steps": [
"把接口返回的原始对象直接交给 value:不用自己写 JSON.stringify 再塞进 pre,组件会按层折叠",
"默认展开两层;数据层级深时调小 depth(1 层只看顶层键),扁平数据可调大",
"长数据给 maxHeight,超出在框内滚动,滚动条已是统一细条(暗色下不会露出原生亮条)",
"数组下标与对象键都走同一套行渲染,收起时右侧显示「3 项 / 2 个键」,先看规模再决定要不要展开",
"要展示编辑结果时不要用它(只读):输入侧请用 textarea,展示侧再交给 jsonViewer"
]
},
{
"id": "diffView",
"title": "文本差异对比 DiffView",
"demo": "sec-diff",
"props": [
{
"n": "before",
"t": "string",
"d": "旧文本(按行比对,CRLF 与 LF 等价)"
},
{
"n": "after",
"t": "string",
"d": "新文本"
},
{
"n": "showStats",
"t": "boolean",
"d": "是否在顶部显示 +N / −N 统计条;缺省 true"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名;缺省自动播报「新增 N 行,删除 M 行」"
},
{
"n": "ignoreWhitespace",
"t": "boolean",
"d": "比较时忽略行内空白差异(格式化提交不再淹没真实改动);缺省 false"
}
],
"example": "diffView({ before: oldText, after: newText })",
"steps": [
"把改前 / 改后两份文本整串交给 before 与 after:不用先切行,CRLF 与 LF 视为同一行",
"行级对齐先剪公共前后缀、再对中间段做 LCS:改两行不会把整篇标成差异",
"增删行同时有底色与行首 + / − 符号两条通道,色盲用户也能分辨;行号列不参与复制",
"顶部统计条给 +N / −N,读屏在容器上直接播报同一组数字,不必逐行听",
"超大文本(中间段行数乘积超过 40 万)会退化成「整段替换」——语义仍对,只是不再逐行对齐;要逐行对齐就先分段比对",
"格式化提交制造的大量「只差缩进 / 行尾空格」噪音,打开 ignoreWhitespace 后再看:显示仍是原文缩进,只是比对时把行内空白归一"
]
},
{
"id": "codeBlock",
"title": "代码块 CodeBlock",
"demo": "sec-code",
"props": [
{
"n": "code",
"t": "string",
"d": "代码原文:显示与复制都用它(不读 DOM,避免行号混进剪贴板)"
},
{
"n": "lang",
"t": "string",
"d": "语言名(如 ts / css);无 title 时作头部标题"
},
{
"n": "title",
"t": "string",
"d": "头部标题(如文件名);优先于 lang"
},
{
"n": "lineNumbers",
"t": "boolean",
"d": "是否显示行号列;缺省 true(单行代码自动省略)"
},
{
"n": "wrap",
"t": "boolean",
"d": "长行是否折行(窄屏 / 长日志场景);缺省 false(横向滚动,行号保持对齐)"
},
{
"n": "maxHeight",
"t": "number",
"d": "代码区最高高度(px),超出在框内滚动;缺省不限高"
},
{
"n": "copyable",
"t": "boolean",
"d": "右上角是否出现复制按钮;缺省 true"
},
{
"n": "onCopy",
"t": "(text: string) => void",
"d": "复制成功回调(参数为实际写入剪贴板的内容)"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名;缺省「代码块」"
}
],
"example": "codeBlock({ code: source, title: 'main.ts', lang: 'ts', maxHeight: 320 })",
"steps": [
"把代码原文交给 code:显示与复制都读这一份,不要从 DOM 里取文本(行号会污染剪贴板)",
"头部标题用 title(文件名)或 lang(语言名),两者都给时 title 优先",
"长代码给 maxHeight,超出在框内滚动;滚动条已是统一细条,暗色下不会露原生亮条",
"单行代码自动省掉行号列;想彻底不要行号传 lineNumbers: false",
"不需要复制按钮(例如代码本身是操作步骤)传 copyable: false;连标题都没有时头部整条省掉",
"窄屏或长日志要折行就传 wrap: true:长行按容器宽度换行,行号仍只占首行左侧;对齐要求高的代码别开,折行会让缩进失去参照"
]
},
{
"id": "signaturePad",
"title": "手写签名 SignaturePad",
"demo": "sec-sign",
"props": [
{
"n": "value",
"t": "Signal<string>",
"d": "签字结果 dataURL 信号,双向绑定;清空写空串"
},
{
"n": "height",
"t": "number",
"d": "画布高度(px);缺省 160"
},
{
"n": "penWidth",
"t": "number",
"d": "笔宽(px);缺省 2"
},
{
"n": "hint",
"t": "string",
"d": "底部提示文案;缺省「在上方区域手写签名」"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔或信号,禁用时不响应手写"
},
{
"n": "ariaLabel",
"t": "string",
"d": "画布可访问名;缺省「手写签名区」"
},
{
"n": "onChange",
"t": "(dataUrl: string) => void",
"d": "收笔 / 清空时回调(清空回调空串)"
}
],
"example": "const pad = signaturePad({ value: sign, height: 140 }); mount('#box', () => pad.el); pad.toDataURL(); pad.clear();",
"steps": [
"把画布挂上去即可手写:宽度跟随容器、高度由 height 给(缺省 160),不用自己算 DP R",
"签字结果在收笔瞬间写回 value 信号(PNG dataURL);要提交表单就把它塞进字段值,清空会写回空串",
"底部「清空」按钮在未落笔或禁用时自动不可点,避免空操作;也能用返回的 handle.clear() 外部触发",
"需要判空用 handle.isEmpty(),取图用 handle.toDataURL();两者与信号同源,不会出现「有图但信号是空」",
"禁用传布尔或信号:布尔直接置位,信号随状态变灰并挡住手写;移动端画布已关掉浏览器手势,不会边签边滚页"
]
},
{
"id": "loading",
"title": "区域加载遮罩 Loading",
"demo": "sec-loading",
"props": [
{
"n": "active",
"t": "boolean | Signal<boolean>",
"d": "是否加载中:布尔直接置位,传信号则随状态显隐"
},
{
"n": "text",
"t": "string",
"d": "遮罩文案;缺省只显示转圈(读屏仍报「内容加载中」)"
},
{
"n": "delay",
"t": "number",
"d": "出场延迟(ms);缺省 120,快请求不闪遮罩"
},
{
"n": "spinnerSize",
"t": "number",
"d": "转圈直径(px);缺省 20"
},
{
"n": "minHeight",
"t": "number",
"d": "最小高度(px);内容为空时兜住遮罩高度"
}
],
"example": "loading({ active: busy, text: '正在刷新订单' }, dataTable({ ... }))",
"steps": [
"把要遮挡的那块内容作为 children 交进来,active 传布尔或信号:刷新时盖上遮罩,数据回来后自动收起",
"内容不隐藏、只降透明度——布局与尺寸原地保留,遮罩收起时不会跳一下",
"默认延迟 120ms 出场:接口快于这个时间就不闪遮罩;想立刻显示传 delay: 0",
"遮罩文案写明在等什么(「正在刷新订单」),读屏同一句也会播报;只要转圈就省略 text",
"内容可能为空时给 minHeight,否则高度塌成 0 遮罩无处可盖"
]
},
{
"id": "relativeTime",
"title": "相对时间 RelativeTime",
"demo": "sec-reltime",
"props": [
{
"n": "value",
"t": "string | number | Date",
"d": "目标时间:ISO 串 / 时间戳(10 位按秒、13 位按毫秒)/ Date"
},
{
"n": "refresh",
"t": "number",
"d": "自动刷新间隔(ms);缺省 30000,传 0 不刷新"
},
{
"n": "threshold",
"t": "number",
"d": "超过该秒数改用绝对日期;缺省 7 天"
},
{
"n": "fallback",
"t": "string",
"d": "时间非法时的占位文案;缺省「—」"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名(如「下单时间」),让读屏知道这个时间是什么"
}
],
"example": "relativeTime({ value: row.createdAt, ariaLabel: '下单时间' })",
"steps": [
"把后端给的时间原样交进来:ISO 串、10 位秒级戳、13 位毫秒戳、Date 都认,不用自己 new Date",
"默认每 30 秒刷新一次文案(「3 分钟前」会在不刷新页面的情况下变「4 分钟前」);列表里条数极多时可把 refresh 调大或传 0",
"超过 7 天自动改显示绝对日期(YYYY-MM-DD HH:mm),想更早切换就调小 threshold",
"精确时间放在 title:鼠标悬停可看,不必为「想知道具体哪天」再点开详情",
"时间非法(空串 / 格式错)不猜也不抛,显示 fallback(缺省「—」),列表不会因一条脏数据空白"
]
},
{
"id": "sparkline",
"title": "迷你趋势线 Sparkline",
"demo": "sec-spark",
"props": [
{
"n": "values",
"t": "number[]",
"d": "数值序列(按时间顺序);非有限数会被剔除"
},
{
"n": "width",
"t": "number",
"d": "宽度(px);缺省 120"
},
{
"n": "height",
"t": "number",
"d": "高度(px);缺省 32"
},
{
"n": "area",
"t": "boolean",
"d": "是否填充折线下方面积;缺省 true"
},
{
"n": "marker",
"t": "boolean",
"d": "是否标记末点;缺省 true(一眼看出当前值位置)"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名;缺省「趋势图」"
}
],
"example": "sparkline({ values: last7Days, width: 140, height: 36 })",
"steps": [
"把按时间顺序的数值数组交给 values:不用自己算最大最小与像素坐标,组件按 viewBox 等比缩放",
"和 statistic 并排用:主数值走 statistic、走向走 sparkline,趋势线不画坐标轴也不带交互",
"不要面积就传 area: false,不要末点圆点传 marker: false(极窄的表格单元格里圆点会糊成一团)",
"改颜色在父级设 color:线、面积、圆点都读 currentColor,放进 card 或表格行自动跟随文字色",
"少于两个点时画不出趋势,组件返回空图——请在外面判空并给「暂无数据」,别让它空着占位"
]
},
{
"id": "heatmap",
"title": "日历热力图 Heatmap",
"demo": "sec-heat",
"props": [
{
"n": "values",
"t": "{ date: string; count: number }[]",
"d": "每一项 = 某天与当天数值;date 用 YYYY-MM-DD,同日多条自动求和"
},
{
"n": "weeks",
"t": "number",
"d": "显示最近多少周;缺省 12"
},
{
"n": "cellSize",
"t": "number",
"d": "格子边长(px);缺省 11"
},
{
"n": "legendText",
"t": "string",
"d": "图例左端文案;缺省「少」(右端固定「多」)"
},
{
"n": "ariaLabel",
"t": "string",
"d": "可访问名;缺省「最近 N 周活跃度」"
},
{
"n": "formatValue",
"t": "(count: number, date: string) => string",
"d": "悬停提示文案;缺省「日期 · 数量」"
}
],
"example": "heatmap({ values: activity, weeks: 12, formatValue: (n, d) => d + ' 提交 ' + n + ' 次' })",
"steps": [
"values 只给「有数据的那几天」即可:缺失的日期自动按 0 渲染,不必把 84 天全部补齐",
"末列永远对齐到本周(周一到周日完整七行),所以数据是滚动的,不会出现半截列",
"同一天多条记录会求和(合并成一个格子),上游按天预聚合也行,两种都吃",
"格子颜色是相对深浅:按序列最大值归一成四档——所以它说的是「哪天更密」,不是绝对值高低",
"默认 12 周、11px 格子;嵌进窄卡片就调小 weeks,看不出格子边界就调大 cellSize"
]
},
{
"id": "progressRing",
"title": "环形进度 ProgressRing",
"demo": "sec-ring",
"props": [
{
"n": "value",
"t": "number | Signal<number>",
"d": "当前进度 0-100;超出范围自动夹取,信号变化时自动重绘"
},
{
"n": "size",
"t": "number",
"d": "外径(px);缺省 96"
},
{
"n": "thickness",
"t": "number",
"d": "环宽(px);缺省 8"
},
{
"n": "showText",
"t": "boolean",
"d": "是否显示中心文字;缺省 true(关掉时请自行保证 aria 可读)"
},
{
"n": "label",
"t": "string",
"d": "中心文字;缺省「N%」"
}
],
"example": "progressRing({ value: pct, size: 96 })",
"steps": [
"value 给数字就是静态环,给信号就是活环:不用自己算弧长,组件按 0-100 换算 dasharray",
"0 点固定在 12 点方向顺时针走,和进度条的心理模型一致,不需要额外传旋转参数",
"要放进表格或卡片角落就用 size: 56 + showText: false 的小环,文字由附近的主体数字承担",
"换色在父级设 color:进度弧读 currentColor,底环固定描边色(--ua-color-border),不引入第二个色源",
"这是个「环形进度条」,不是仪表盘:只表达 0-100 单一进度,多段占比请用饼图类组件"
]
},
{
"id": "noticeBar",
"title": "通知条 NoticeBar",
"demo": "sec-notice",
"props": [
{
"n": "text",
"t": "string",
"d": "通知正文;单行不换行,超宽需配合 scroll"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info"
},
{
"n": "icon",
"t": "string",
"d": "左侧符号;缺省按 variant 取(ℹ / ✓ / ! / ×)"
},
{
"n": "scroll",
"t": "boolean",
"d": "正文超宽时跑马灯滚动;缺省 false(静态,超出裁切)"
},
{
"n": "closable",
"t": "boolean",
"d": "是否显示关闭按钮;缺省 true"
},
{
"n": "onClose",
"t": "() => void",
"d": "关闭回调"
}
],
"example": "noticeBar({ text: '系统将于今晚 23:00 维护 10 分钟', scroll: true })",
"steps": [
"和 alert 的分工:alert 可多行带标题、占块;通知条只占一行,适合页顶常驻播报(维护公告、试用期提醒)",
"文案比容器宽就传 scroll: true:正文从右向左匀速走,悬停自动暂停,读得完不用追",
"只想播报不需要关闭就传 closable: false:右侧不再留 × 位,横条更紧凑",
"颜色只是第二线索:左侧符号按 variant 自动配(ℹ / ✓ / ! / ×),色弱或灰度打印也能分辨",
"跑马灯在减少动效偏好下自动停成静态首屏——完整原文始终在 title 里,不必为可访问性另写一份",
"点 × 是先淡出再隐藏(200ms,和 alert 同一档),回调 onClose 立刻触发:想同步清理外部状态就在回调里做,别等隐藏"
]
},
{
"id": "sortableList",
"title": "拖拽排序列表 SortableList",
"demo": "sec-sortable",
"props": [
{
"n": "items",
"t": "string[]",
"r": 1,
"d": "全部条目文案;文案即排序键,要求唯一(重名会让顺序回读撞车)"
},
{
"n": "value",
"t": "Signal<string[]>",
"d": "受控顺序信号;传了则内部改动写回,外部改动即重排(拖拽中跳过)"
},
{
"n": "onChange",
"t": "(next: string[]) => void",
"k": 1,
"d": "顺序变化回调,拖放落定 / 键盘搬位 / move 调用后各触发一次"
},
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用拖拽与键盘搬位;缺省 false",
"df": "false"
},
{
"n": "emptyText",
"t": "string",
"d": "无条目占位文案;缺省「暂无可排序项」"
}
],
"example": "const sort = sortableList({ items: ['需求评审', '接口联调', '灰度发布'], onChange: (next) => console.log(next) });\ncontainer.append(sort.el);",
"steps": [
"items 传全部条目文案、顺序即当前顺序:文案同时是排序键,所以必须唯一(重名会分不清谁被搬动)",
"鼠标 / 触摸按住任意一行拖动,指针越过邻行中线就换位;松手后写回 value 信号并触发 onChange,拖拽期间不重排避免手势中断",
"键盘等价:Tab 聚焦某行后按 Alt+↑ / Alt+↓ 上下搬一格、Alt+Home / Alt+End 搬到首尾(裸 ↑↓ 留给页面滚动)",
"被拖的行以虚线边框 + 淡色虚影标示,落点由指针位置决定,不会出现「拖了三格只动一格」的错觉",
"外部要按代码调整顺序用句柄 move(from, to),下标越界或禁用时静默忽略;order() 以 DOM 为唯一真相回读当前顺序",
"什么时候别用:条目超过一屏(拖到边缘不会自动滚动,改用筛选或上下按钮)、需要跨列表搬运(用两个实例并自己管状态)"
]
}
]
}
}