{ "$schema": "ui-arch-registry-index/0.1", "generatedAt": "2026-10-26T14:21:24.531Z", "repoRoot": ".", "packages": [ { "name": "@ui-arch/frame", "type": "headless-core", "description": "自研微框架:signal / computed / effect 信号系统 + hyperscript(h)DOM 层。函数值属性与函数型子节点自动响应式;renderList 做列表整体替换;零依赖、无虚拟 DOM。", "tags": [ "framework", "signal", "reactive", "hyperscript" ], "api": { "entry": "src/index.ts", "exports": [ "signal(initial) -> Signal:无参读值并收集依赖,传参写值并触发订阅", "effect(fn):立即执行并追踪依赖,信号变化自动重跑", "computed(fn):由信号派生的只读计算值", "h(tag, props, ...children):hyperscript;on* 绑事件,函数值属性/子节点自动响应式", "renderList(el, fn):信号变化时整体替换容器子节点", "mount(target, view):挂载视图" ], "keyTypes": [ "Signal" ] }, "usage": "const count = signal(0); mount('#app', () => h('button', { onClick: () => count(count() + 1) }, () => `count = ${count()}`))", "conventions": [ "组件 = 返回 DOM 节点的普通函数,不做类组件与生命周期抽象", "跨系统状态(如 table-core)用桥接信号:subscribe 里递增 version 信号" ], "docs": { "intro": "零依赖微框架:组件 = 返回 DOM 的普通函数。signal/effect/computed 管状态,h() 管 DOM,无虚拟 DOM 无生命周期抽象。", "steps": [ "引入:import { signal, computed, effect, h, mount } from '@ui-arch/frame'", "声明状态:const count = signal(0);无参调用读值 count(),传参写值 count(1)", "渲染视图:mount('#app', () => h('div', null, () => `count = ${count()}`))——函数型子节点即响应式文本", "绑定事件:props 里 on 开头且值为函数的键(onClick 等)自动 addEventListener", "派生数据:const double = computed(() => count() * 2),视图里直接调用 double(),依赖自动追踪", "列表渲染:renderList(tbody, () => rows.map(r => h('tr', null, r.name))),信号变化整体替换子节点" ] }, "version": "0.1.0", "dir": "packages/core/frame" }, { "name": "@ui-arch/table-core", "type": "headless-core", "description": "Headless table state machine: immutable state, dispatch/reducer, composable derive pipeline (filter -> sort -> paginate) with per-stage memoization. No DOM, no framework dependency.", "tags": [ "table", "headless", "state-machine", "datagrid" ], "api": { "entry": "src/index.ts", "exports": [ "createTable(options) -> TableInstance: create headless table instance", "sortPlugin(options?) -> multi-column sorting plugin (asc -> desc -> off cycle)", "filterPlugin(options?) -> column filter plugin (default substring match, custom predicate supported)", "paginationPlugin() -> client-side pagination with page clamping, exposes setPage/nextPage/prevPage/getPageInfo", "selectionPlugin() -> rowKey-based selection, exposes selectAllVisible" ], "keyTypes": [ "ColumnDef", "TableState", "TablePlugin", "RowModel", "Action" ] }, "usage": "const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()], initialState: { pagination: { page: 1, pageSize: 20 } } }); table.dispatch({ type: 'toggleSort', key: 'name' }); table.getVisibleRows()", "conventions": [ "selection/expanded must store rowKey, never row index", "derive pipeline order is fixed: filter -> sort -> paginate", "actions are the only way to mutate state; state is immutable" ], "docs": { "intro": "Headless 表格内核:不渲染任何 DOM,只管理状态与派生。任何渲染层通过 getVisibleRows() 拿行模型,配 subscribe() 刷新。", "steps": [ "创建实例:const table = createTable({ data, columns, rowKey: 'id', plugins: [filterPlugin(), sortPlugin(), paginationPlugin()] })", "取派生数据:table.getVisibleRows() 返回 { rows, total };total 语义固定 = 筛选后、分页前的行数", "变更状态:只能 dispatch action,如 table.dispatch({ type: 'toggleSort', key: 'name' });状态不可变", "订阅刷新:table.subscribe(fn),fn 里重新 getVisibleRows() 并交给渲染层", "分页信息:api.getPageInfo() 返回 { page, pageSize, totalPages };越界页码自动钳制", "扩展能力:实现 TablePlugin(derive / patchState / settle / api 四钩子)传入 plugins 即插入管线" ] }, "version": "0.1.0", "dir": "packages/core/table-core" }, { "name": "@ui-arch/tokens", "type": "design-tokens", "description": "Single source of design tokens. Skin packages consume CSS variables only - hardcoded colors inside skins are forbidden. Dark theme overrides values under [data-theme='dark'], never names.", "tags": [ "tokens", "theme", "css-variables" ], "api": { "entry": "tokens.css", "exports": [ "tokens.css: import '@ui-arch/tokens/tokens.css' once at app root", "tokens.json: programmatic access for tools and agents" ], "keyTypes": [ "--ua-color-*", "--ua-space-*", "--ua-radius-*" ] }, "usage": "import '@ui-arch/tokens/tokens.css'; then use var(--ua-color-accent) etc. in any skin package", "docs": { "intro": "设计令牌唯一来源:CSS 变量 --ua-* + 同名 JSON。皮肤只允许消费变量,禁止硬编码颜色;暗色只换值不换名。", "steps": [ "引入:在应用入口 import '@ui-arch/tokens/tokens.css'", "消费:组件样式一律 var(--ua-color-accent) 这类变量,不写死色值", "暗色:根元素设 data-theme=dark,变量值自动切换", "扩展:新令牌先同时加进 tokens.css 与 tokens.json,再重新生成文档站" ] }, "version": "0.1.0", "dir": "packages/design/tokens" }, { "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 满额自动禁输" ], "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", "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", "df": "false", "d": "加载态:转圈并禁点(aria-busy 播报忙碌);布尔直接置位,传信号则动态同步" } ], "example": "button({ label: '提交', variant: 'primary', onClick: () => console.log('clicked') })", "steps": [ "确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger", "绑定 onClick 回调处理点击逻辑", "需要禁用态时传 disabled(布尔或信号);异步提交用 loading:开始时置 true,完成后置 false,按钮转圈并自动禁点", "放进
里需要提交时显式传 type: \"submit\";缺省 button 不会触发原生提交" ] }, { "id": "textField", "title": "文本输入 TextField", "demo": "sec-bind", "props": [ { "n": "value", "t": "Signal", "d": "双向绑定信号:输入写回信号,信号变化写回输入框" }, { "n": "placeholder", "t": "string", "d": "占位文案" }, { "n": "disabled", "t": "boolean | Signal", "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", "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", "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", "d": "双向绑定信号" }, { "n": "rows", "t": "number", "d": "初始行数;缺省 3", "df": "3" }, { "n": "placeholder", "t": "string", "d": "占位文案" }, { "n": "disabled", "t": "boolean | Signal", "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", "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", "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", "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", "d": "勾选状态信号,双向绑定" }, { "n": "label", "t": "string", "d": "右侧文案;缺省只渲染勾选框" }, { "n": "indeterminate", "t": "boolean | Signal", "d": "半选态(批量选择时部分子项选中):仅影响视觉与读屏 mixed 播报,不改变 checked 值;点一下即回到全选" }, { "n": "disabled", "t": "boolean | Signal", "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", "d": "开关状态信号,双向绑定" }, { "n": "ariaLabel", "t": "string", "d": "无可见文案时的可访问名(如纯图标开关);缺省取 label" }, { "n": "label", "t": "string", "d": "左侧文案" }, { "n": "loading", "t": "boolean | Signal", "d": "加载中:锁定交互、滑块换成转圈并挂 aria-busy(异步提交防误点);与 disabled 同时生效" }, { "n": "disabled", "t": "boolean | Signal", "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", "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", "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:触发器只显示当前值文案,读屏需要「这个下拉在选什么" ] }, { "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", "d": "选中值信号,双向绑定" }, { "n": "disabled", "t": "boolean | Signal", "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", "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(渲染 );单页应用想自行接管时不给 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", "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", "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", "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", "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", "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", "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", "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 切虚线弱化分隔感" ] }, { "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", "r": 1, "d": "当前页码信号,双向绑定:外部赋值自动翻页,点击页码写回信号" }, { "n": "pageSize", "t": "number", "r": 1, "d": "每页条数" }, { "n": "total", "t": "number", "r": 1, "d": "总条数" }, { "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" ] }, { "id": "modal", "title": "弹窗 Modal", "demo": "sec-tabs-modal", "props": [ { "n": "open", "t": "Signal", "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", "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", "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", "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": "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(...)) 会占满两列,窄屏单列时自动忽略跨列(否则会撑出横向滚动)" ] }, { "id": "dataTable", "title": "数据表格 DataTable", "propsType": "DataTableProps", "demo": "sec-table", "props": [ { "n": "data", "t": "T[] | Signal", "r": 1, "d": "全量行数据(行须含 id 作 rowKey);传信号或 computed 时数据变化自动换源,排序 / 筛选 / 页码保持用户原意" }, { "n": "columns", "t": "ColumnDef[]", "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": "selectable", "t": "boolean", "d": "启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false", "df": "false" }, { "n": "page", "t": "Signal", "d": "受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动" }, { "n": "loading", "t": "boolean | Signal", "d": "加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy" }, { "n": "selected", "t": "Signal", "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,不需要转义)", "数据源可以给信号或 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 序列,纯展示表不会被读屏当成按钮" ] }, { "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", "r": 1, "d": "当前选中值信号,双向绑定(点击写回,外部赋值即同步滑块)" }, { "n": "size", "t": "'sm' | 'md'", "d": "尺寸档位;缺省 md", "df": "md" }, { "n": "disabled", "t": "boolean | Signal", "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", "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", "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", "d": "展开的 key 集合;不传则内部维护" }, { "n": "selected", "t": "Signal", "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", "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", "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", "d": "选中路径信号;不传则内部维护,传了即外部受控" }, { "n": "placeholder", "t": "string", "d": "未选时占位文案;缺省「请选择」" }, { "n": "separator", "t": "string", "d": "路径分隔符;缺省「 / 」" }, { "n": "changeOnSelect", "t": "boolean", "d": "允许停在中间层;缺省 false 必须选到叶子才提交" }, { "n": "disabled", "t": "boolean | Signal", "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", "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", "d": "输入值信号;不传则内部维护,传了即外部受控" }, { "n": "placeholder", "t": "string", "d": "占位文案,同时作为无障碍名称" }, { "n": "emptyText", "t": "string", "d": "无匹配时的提示文案;缺省「无匹配项」" }, { "n": "maxItems", "t": "number", "d": "最多展示条数;缺省 8" }, { "n": "disabled", "t": "boolean | Signal", "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", "d": "选中日期(YYYY-MM-DD)信号;区间模式下是起始日,不传则内部维护" }, { "n": "range", "t": "boolean", "df": "false", "d": "区间模式:首次点击定起点(弹层不收起),再次点击定终点;点早了自动交换先后" }, { "n": "end", "t": "Signal", "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", "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", "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", "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", "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", "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", "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", "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", "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", "d": "选中日期(YYYY-MM-DD)信号;不传则内部维护" }, { "n": "month", "t": "Signal", "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", "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", "d": "单选选中 key;不传则内部维护" }, { "n": "checked", "t": "Signal", "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", "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", "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", "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", "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", "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", "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 对齐,键帽底边加厚一线出立体感" ] }, { "id": "highlight", "title": "关键词高亮 Highlight", "demo": "sec-highlight", "props": [ { "n": "text", "t": "string | Signal", "r": 1, "d": "原文;传信号则随信号变化响应式重画" }, { "n": "keyword", "t": "string | Signal", "r": 1, "d": "关键词(大小写不敏感、全部命中,首尾空白忽略);空串不高亮、原样展示" } ], "example": "highlight({ text: 'Alpha Beta', keyword: 'beta' })", "steps": [ "传 text + keyword 即可,命中片段以淡黄 mark 底展示,适合搜索结果 / 补全列表的匹配说明", "两个入参都接受信号:搜索框输入实时联动高亮,无需手动重渲染", "大小写不敏感且全部命中(非只第一处);关键词两侧空白自动忽略" ] }, { "id": "inputTag", "title": "标签输入 InputTag", "demo": "sec-inputtag", "props": [ { "n": "value", "t": "Signal", "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", "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 作为 value:回车或逗号把当前输入转成 chip,写回信号后立即重画", "点 chip 上的 × 删除单个标签;输入框为空时按退格键删掉最后一个(手不离键盘也能改)", "输入后直接失焦也会成标签,不必强行回车;max 满额后输入框禁用,占位文案变成「最多 N 个标签」", "要区分大小写重复判断就传 allowDuplicate: true;默认按原样去重", "样式复用输入组与标签:外框聚焦光环、chip 圆角与语义色都跟既有组件一致,无新增视觉字面量" ] } ] }, "version": "0.1.0", "dir": "packages/skin/ui" }, { "name": "@ui-arch/cli", "type": "tooling", "description": "ua 命令行与 MCP 注册表服务:把各包 ua.manifest.json 汇总成机器可读索引(.ua/index.json)、Agent 全量文档(llms-full.txt)与人类文档站,并提供清单一致性门禁。", "tags": [ "cli", "tooling", "mcp", "docs", "gate" ], "api": { "entry": "bin/ua.js", "exports": [ "ua doctor:11 项环境与自洽预检(node / 依赖 / git 钩子与身份 / .ua 元数据可解析 / usage 串 ↔ dispatch 一致 / check 链覆盖全部门禁步骤 / CLI 清单声明的命令都有实现),任一失败退出码 1", "ua index:扫描 packages/ 两层目录的 ua.manifest.json,生成 .ua/index.json 与 llms.txt", "ua list:列出注册表内所有包与组件数;--json 输出「包 + 组件 id」结构化数组,--components 逐组件展开", "ua info <包名|组件id>:包名返回摘要,组件 id 返回 props 表 + 步骤 + 示例;--json 输出原始清单,--md 直接给可粘贴的 Markdown 片段(标题 + 参数表 + 用法 + 示例)", "ua tokens [关键词] [--json|--unused]:打印 --ua-* 令牌清单(颜色带暗色值);--unused 反向列出皮肤包与令牌自身都没引用的令牌,用来收口令牌面", "ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt", "ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1", "ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全)", "ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1", "ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)", "ua audit:contrast [--json|--list]:对比度门禁,把令牌里 16 组「文字色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1;--json 出逐对明细,--list 只列配对表", "ua audit:motion [--json]:动效兜底门禁,两类必查——① 带 infinite 的动画逐个核对是否在 prefers-reduced-motion 块里 animation: none;② 悬停 / 按压的位移(translate)是否在减少动效下归零(认 ua- 类通配的全局规则)。缺兜底退出码 1(--json 出逐条明细)", "ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 支持 format: md 直接取可粘贴的 Markdown 片段" ], "keyTypes": [ "ua.manifest.json 清单结构", "MCP tool 入参 schema" ] }, "usage": "node packages/tools/cli/bin/ua.js [--json|--md]|docs|tokens [--json|--unused]|style|verify|commit-lint|audit:visual|audit:contrast|audit:motion|doctor|mcp>;仓库内等价于 npm run index / docs / style / verify / audit:visual / audit:contrast / audit:motion / doctor", "conventions": [ "零依赖:只用 node 标准库,Node >= 18", "清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生", "门禁只增不改:新增校验必须能被人手复现,报错要指出文件与字段" ], "docs": { "intro": "工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。", "steps": [ "接手先跑 ua doctor(11 项:node / 依赖 / git 钩子与身份 / 元数据 / 命令面三方一致 / 门禁链完整,秒级),再跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify → lint-iterations → audit:visual → audit:contrast → audit:motion)", "查组件:ua list 看全量,ua search <关键词> 按 id/标题/参数名/参数说明/步骤五层 + 配方三层(标题/配方组件/要点)模糊找(命中非 id 标题会标出命中字段,配方命中行直接给 ua recipes <编号> 指路;拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单);要给文档 / issue / 回复贴组件说明时加 --md,直接拿到标题 + 参数表 + 用法 + 示例的 Markdown 片段", "查配方:ua recipes 列全量(编号 / 标题 / 用到的组件),ua recipes <编号> 看骨架代码与要点(--json 给脚本用;MCP 里对应 list_recipes / get_recipe)", "查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码。令牌分十三组:颜色 / 混色浓度 / 圆角 / 间距 / 字号 / 字重行高 / 边框 / 阴影 / 动效 / 位移 / 焦点环 / 控件高度 / 层级,color-mix 混入浓度用 --ua-alpha-soft/tint/line/mid/strong/heavy,弹层阴影用 --ua-shadow-1..4,过渡用 --ua-duration-* + --ua-ease,transform 位移用 --ua-translate-*(负号 calc(-1 * var(...))),焦点环用 --ua-focus-ring(_inset)", "收口令牌面用 ua tokens --unused:列出皮肤样式表与令牌自身都没引用的令牌(当前 3 个:surface-3 / text-secondary / font-size-xl),删之前先确认没有外部消费方在吃它们", "改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)", "改完样式跑 npm run audit:visual:各类硬编码计数不得高于 .ua/visual-baseline.json(棘轮只减不增);字面量可用 npm run visual:migrate 幂等改写成令牌", "改完颜色令牌跑 npm run audit:contrast:16 组文字/底色配对在亮/暗 × 常规/高对比四套主题下都必须达标(正文 7 / 常规 4.5 / 禁用 2.6),跌破即退出码 1——调一个色值就能让全站辅助文字掉到 4.4,人眼看不出但门禁看得出", "改完动效跑 npm run audit:motion:带 infinite 的动画必须在 prefers-reduced-motion 块里被关掉,悬停 / 按压的位移(translate)必须归零,缺一条即退出码 1——循环动画漏兜底会把「减少动效」用户整页交给抖动,位移漏兜底则每次悬停都在推搡前庭", "在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 传 format: md 拿到与 ua info --md 同款的 Markdown 片段" ], "components": [] }, "version": "0.1.0", "dir": "packages/tools/cli" } ] }