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

3465 lines
150 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 = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)",
"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 列出全部姓名"
],
"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": "28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"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": "点击回调"
}
],
"example": "button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') })",
"steps": [
"确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger",
"绑定 onClick 回调处理点击逻辑",
"需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染",
"放进 <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,并在输入框内侧显示实时计数;缺省不限"
}
],
"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 在下方);缺省不渲染"
}
],
"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,并在下方附加块显示实时计数;缺省不限"
}
],
"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 }"
},
{
"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": "onChange",
"t": "(value: string) => void",
"k": 1,
"d": "选中回调"
}
],
"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 等价"
]
},
{
"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 给出读屏名(如「有未读消息」)"
]
},
{
"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-feedback",
"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 秒后自动淡出并移除节点",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入",
"同屏最多 4 条,超出自动挤掉最早一条;需要用户主动确认的消息用 showToast({ closable: true }) 给个 ×",
"要「撤销」这类操作时给 showToast 传 actionLabel 与 onAction:按钮出现在文案右侧,点完先收起再回调,duration 建议放到 5000 让用户来得及点"
]
},
{
"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,出场时按该方向剩余空间自动翻到对侧"
]
},
{
"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)"
}
],
"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": "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 同步业务状态"
]
},
{
"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": "评分变化回调"
}
],
"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 切虚线弱化分隔感"
]
},
{
"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",
"r": 1,
"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 拿新条数重算数据"
]
},
{
"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": "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 焦点锁在弹窗内,关闭自动把焦点还给打开前的元素"
]
},
{
"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": "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)"
]
},
{
"id": "dataTable",
"title": "数据表格 DataTable",
"propsType": "DataTableProps",
"demo": "sec-table",
"props": [
{
"n": "data",
"t": "T[]",
"r": 1,
"d": "全量行数据,每行须含 id 字段作 rowKey"
},
{
"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": "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 焦点)"
}
],
"example": "dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })",
"steps": [
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
"列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)",
"列宽用内核字段 width:数字(像素)固定该列,'flex' 或不设则按内容自适应",
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
"表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供",
"数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复",
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)",
"可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充",
"需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出",
"整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮"
]
},
{
"id": "timeline",
"title": "时间线 Timeline",
"demo": "sec-timeline",
"props": [
{
"n": "items",
"t": "TimelineItem[]",
"r": 1,
"d": "条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant"
},
{
"n": "mode",
"t": "'left' | 'right'",
"d": "轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右",
"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 组合"
]
},
{
"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[] 是副本,自行发请求上传(组件不内置网络层)"
]
},
{
"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"
},
{
"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": "变更回调(传副本)"
}
],
"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 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算"
]
},
{
"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": "点确定后的回调(此时弹层已收起)"
},
{
"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 信号"
]
},
{
"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,视觉才对得上"
]
}
]
}
}