Compare commits
29 Commits
62d4ce648d
...
qa/probe
| Author | SHA1 | Date | |
|---|---|---|---|
| 511a3901a7 | |||
| b6c948998b | |||
| 8d439f068f | |||
| 9651c39f83 | |||
| 86c72f7090 | |||
| c0bb8d705d | |||
| 5131977f54 | |||
| bfa70132da | |||
| d5ac26af31 | |||
| 33979b23a0 | |||
| 0c42c6f71e | |||
| 98f557cba8 | |||
| 7ce8bf72d4 | |||
| a5439fe4a6 | |||
| 35c9f00e26 | |||
| 265060fdb3 | |||
| fbe0348b47 | |||
| 99ce0cdd63 | |||
| f1363e81f7 | |||
| f57da4c74e | |||
| 3fb3cd2395 | |||
| f2fc286eeb | |||
| b9b05f53fb | |||
| c19f83b413 | |||
| 67374c78c3 | |||
| cbf1d4a248 | |||
| c457e0cc33 | |||
| 84d008b5fd | |||
| fd4ee018e6 |
@@ -300,6 +300,118 @@
|
|||||||
"acceptance": "packages/skin/ui 新增 rate.ts:rate({value, max?, disabled?, onChange?}),max 缺省 5,hover 预览移出还原,键盘 ←→/↑↓ 调节,aria 星标语义;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
|
"acceptance": "packages/skin/ui 新增 rate.ts:rate({value, max?, disabled?, onChange?}),max 缺省 5,hover 预览移出还原,键盘 ←→/↑↓ 调节,aria 星标语义;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"notes": "rate(props):hover 预览用组件内局部 hover 信号(容器 mouseleave 归零),shown = hover || value 决定点亮数;value/hover 变化整条重渲染(与 stepper 同款 effect + replaceWith 模式);键盘 →/↑ 加分 ←/↓ 减分并 clamp [1,max];role=slider + aria-valuenow;选中色取 --ua-color-warning 金黄;demo 新增 sec-rate(可交互 + 只读两实例共用 score 信号);README 同步 31 组件"
|
"notes": "rate(props):hover 预览用组件内局部 hover 信号(容器 mouseleave 归零),shown = hover || value 决定点亮数;value/hover 变化整条重渲染(与 stepper 同款 effect + replaceWith 模式);键盘 →/↑ 加分 ←/↓ 减分并 clamp [1,max];role=slider + aria-valuenow;选中色取 --ua-color-warning 金黄;demo 新增 sec-rate(可交互 + 只读两实例共用 score 信号);README 同步 31 组件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e04",
|
||||||
|
"title": "新增 empty 空状态组件",
|
||||||
|
"motivation": "列表、表格、搜索无结果时需要统一的占位提示,dataTable 内置空态样式但独立场景(卡片、面板、筛选结果)没有可复用组件",
|
||||||
|
"acceptance": "packages/skin/ui 新增 empty.ts:empty({title?, description?, action?}),居中排版,action 为可选 DOM 操作区(常见放按钮);只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "empty(props):纯静态组件(无信号),role=status 由读屏播报;装饰符号 ⌀ 弱化为边框色且 aria-hidden;min-height 120px 撑留白(令牌只到 space-4);demo 新增 sec-empty(信号切换有数据/无结果对比,内容用响应式函数渲染);README 同步 32 组件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e05",
|
||||||
|
"title": "新增 divider 分隔线组件",
|
||||||
|
"motivation": "表单分组、卡片内区块、工具栏之间需要统一分隔语义,目前只有 menu 内部私有分隔线,独立场景无组件可用",
|
||||||
|
"acceptance": "packages/skin/ui 新增 divider.ts:divider({label?, vertical?, dashed?}),水平/垂直两种方向,可带居中文案标签;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "divider(props):水平线用 ::before/::after 双线夹 label 居中文案;vertical 单线(伪元素清空,dashed 走 border-left);dashed 水平线 border-top 替代背景色;role=separator + aria-label;demo 新增 sec-divider(文案线/虚线/工具栏竖线三种用法);README 同步 33 组件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f01",
|
||||||
|
"title": "组件数漂移治理:AGENTS/ARCHITECTURE 数字纳入 verify 门禁",
|
||||||
|
"motivation": "组件数第三次漂移(AGENTS.md 与 ARCHITECTURE.md 仍写 27,实际 33),ua verify 只锁 README 区块锁不住散文数字",
|
||||||
|
"acceptance": "ua verify 新增规则:扫描 AGENTS.md 与 ARCHITECTURE.md 中的「NN 个组件」模式数字,与 manifest docs.components 长度比对不一致即报错;当前两处 27 改为 33;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:2026-10-23 第二次冷启动审计应修 #1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f02",
|
||||||
|
"title": "check 链描述同步:AGENTS.md 与 ITERATION.md 补 verify 第六步",
|
||||||
|
"motivation": "AGENTS.md:10 与 ITERATION.md:31 写 5 步,package.json 实为 6 步(含 verify),文档与实际门禁不一致",
|
||||||
|
"acceptance": "两处描述改为 build → style → typecheck → smoke → index → verify 六步;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:审计应修 #2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f03",
|
||||||
|
"title": "新增 ua lint-iterations 门禁:留痕格式机器校验",
|
||||||
|
"motivation": "iterations.log 41 行中 36 行违规(time 字段混用、UTC 时间戳、缺 commit),靠人工反复修补,规范缺机器校验",
|
||||||
|
"acceptance": "ua 新增 lint-iterations 子命令:校验每行 JSON 字段恰为 {ts,id,result,commit,summary}、ts 为本地 +08:00 ISO 且可用 Date 解析、commit 为 7-40 位哈希或 aborted 时允许为空、result 枚举 done/aborted;并入 npm run check;历史 36 条违规行按规范修复(ts 以对应 git 提交的 author date 本地时区回填,commit 补哈希);npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:审计应修 #3;历史行修复用 git log --format=%cI 按条目 id 关联提交时间回填"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f04",
|
||||||
|
"title": "commit-lint 收严:chore/docs 等强制 scope,杜绝无 scope 放行",
|
||||||
|
"motivation": "COMMITTING.md 规定 <type>(<scope>) 格式,但 chore:、docs: 无 scope 被 lint 放行,历史还有 17 条 iter: 缺 scope",
|
||||||
|
"acceptance": "commit-lint 规则收严为:除 Merge 外所有 type 必须带 scope(scope 允许 cli/skin/core/backlog/留痕等枚举或条目 id);反向用例 chore: xxx / docs: xxx / iter: xxx 必须被拒;正例全部通过;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:审计应修 #4;历史 iter 缺 scope 的提交不回改(已发布历史不动),只管增量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f05",
|
||||||
|
"title": "ua index 幂等化:generatedAt 仅内容变化时更新",
|
||||||
|
"motivation": "ua index 每次重写 .ua/index.json 的 generatedAt,npm run check 必然弄脏工作区,与「提交前 check」工作流冲突",
|
||||||
|
"acceptance": "index 命令改为:若除 generatedAt 外内容与现有文件一致则不写入(或 generatedAt 改为内容哈希派生);连续两次 npm run check 后 git status 必须干净",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:审计应修 #5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f06",
|
||||||
|
"title": "pnpm-lock.yaml 入库 + 目录树补全",
|
||||||
|
"motivation": "lockfile 缺失导致依赖树不可复现;ARCHITECTURE.md 目录树缺 scripts/、COMMITTING.md、ITERATION.md、.ua/ 清单等条目,新代理接手有盲区",
|
||||||
|
"acceptance": "pnpm-lock.yaml 提交入库;ARCHITECTURE.md 目录树补 scripts/(gitea.mjs/gitea-api.mjs 一句话说明)、COMMITTING.md、ITERATION.md、.ua/backlog.json、.ua/iterations.log、pnpm-workspace.yaml;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "来源:审计应修 #6 + 建议 #7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g01",
|
||||||
|
"title": "ua docs 幂等化:llms-full.txt 时间戳不弄脏工作区",
|
||||||
|
"motivation": "llms-full.txt 头部含 generatedAt,每次 ua docs 重写必然弄脏工作区,与 index 幂等化(f05)同类问题",
|
||||||
|
"acceptance": "内容与现有文件仅时间戳之差时不写入;npm run docs 连跑两次 git status 干净",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:延续 f05 方案"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g02",
|
||||||
|
"title": "新增 image 图片组件(缩略图 + 点击预览)",
|
||||||
|
"motivation": "皮肤包缺常规媒体组件,img 缩略图 + 遮罩预览是常规表单/详情页刚需",
|
||||||
|
"acceptance": "image({src, alt, width?}) 返回缩略图,点击开遮罩大图(Esc/点击遮罩关闭,reduced-motion 兜底);manifest/docs/llms-full/README/demo 同步;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:组件补充方向"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g03",
|
||||||
|
"title": "textField/textarea/inputNumber 补 prefix/suffix 附加内容槽",
|
||||||
|
"motivation": "搜索框放大镜、金额单位等前后缀是输入组件高频需求,目前只能外部拼装",
|
||||||
|
"acceptance": "三个输入组件新增 prefix/suffix(string|HTMLElement),渲染为输入框内侧前后附加块;manifest 参数表与 llms-full 同步;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:可用性细节方向"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g04",
|
||||||
|
"title": "dataTable 筛选框用 prefix 放搜索图标",
|
||||||
|
"motivation": "g03 落地了 prefix 附加块,dataTable 工具栏筛选框是最佳实战落点,同时验证包装不影响事件冒泡",
|
||||||
|
"acceptance": "dataTable 筛选输入框带 🔍 prefix;筛选功能回归正常(smoke 不变绿转红);npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:g03 实战验证"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g05",
|
||||||
|
"title": "ua verify 增加 llms-full.txt 生成同步校验",
|
||||||
|
"motivation": "llms-full.txt 是手工触发 ua docs 生成的产物,改 manifest 后忘记跑 docs 会产生文档漂移,目前无门禁",
|
||||||
|
"acceptance": "verify 内联重渲染 Agent 文档(时间戳归一化)与根目录 llms-full.txt 比对,不一致即报错提示 npm run docs;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:工具链防漂移方向"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g06",
|
||||||
|
"title": "组件数门禁扩展 README 计数文本",
|
||||||
|
"motivation": "f01 的计数漂移校验只覆盖 AGENTS/ARCHITECTURE,README 的「N 个 DOM 组件」文本仍可漂移",
|
||||||
|
"acceptance": "checkCountDocs 扫描列表加入 README.md,漂移即 verify 失败;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "自补:f01 收尾"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+134
-10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-23T10:03:21.198Z",
|
"generatedAt": "2026-10-23T11:03:12.689Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@ui-arch/ui",
|
"name": "@ui-arch/ui",
|
||||||
"type": "skin-dom",
|
"type": "skin-dom",
|
||||||
"description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
|
"description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
|
||||||
"tags": [
|
"tags": [
|
||||||
"components",
|
"components",
|
||||||
"dom",
|
"dom",
|
||||||
@@ -143,9 +143,9 @@
|
|||||||
"entry": "src/index.ts",
|
"entry": "src/index.ts",
|
||||||
"exports": [
|
"exports": [
|
||||||
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
||||||
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块",
|
||||||
"inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取",
|
"inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块",
|
||||||
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块",
|
||||||
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
||||||
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
@@ -169,11 +169,14 @@
|
|||||||
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
|
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
|
||||||
"stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳",
|
"stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳",
|
||||||
"rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配",
|
"rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配",
|
||||||
|
"empty(props):空状态占位,title/description 可选,action 放操作区",
|
||||||
|
"divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线",
|
||||||
"modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区",
|
"modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区",
|
||||||
"drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭",
|
"drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭",
|
||||||
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
|
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
|
||||||
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
|
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
|
||||||
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core"
|
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core",
|
||||||
|
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽"
|
||||||
],
|
],
|
||||||
"keyTypes": [
|
"keyTypes": [
|
||||||
"ButtonProps",
|
"ButtonProps",
|
||||||
@@ -230,6 +233,37 @@
|
|||||||
"读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM"
|
"读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM"
|
||||||
],
|
],
|
||||||
"components": [
|
"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": "缩略图宽度像素,高度自适应;缺省走样式默认"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
|
||||||
|
"steps": [
|
||||||
|
"基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜",
|
||||||
|
"点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图",
|
||||||
|
"宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width",
|
||||||
|
"alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "button",
|
"id": "button",
|
||||||
"title": "按钮 Button",
|
"title": "按钮 Button",
|
||||||
@@ -291,13 +325,24 @@
|
|||||||
"t": "(value: string) => void",
|
"t": "(value: string) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "输入回调,每次按键触发"
|
"d": "输入回调,每次按键触发"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"先用 signal('') 创建字符串信号",
|
"先用 signal('') 创建字符串信号",
|
||||||
"传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显",
|
"传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显",
|
||||||
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -337,13 +382,24 @@
|
|||||||
"t": "(value: number) => void",
|
"t": "(value: number) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "值变化回调(已夹取到合法区间)"
|
"d": "值变化回调(已夹取到合法区间)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
||||||
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
||||||
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用"
|
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -377,13 +433,24 @@
|
|||||||
"t": "(value: string) => void",
|
"t": "(value: string) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "输入回调"
|
"d": "输入回调"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"创建信号并传入 value",
|
"创建信号并传入 value",
|
||||||
"用 rows 控制初始高度,用户可纵向拉伸",
|
"用 rows 控制初始高度,用户可纵向拉伸",
|
||||||
"字数统计等派生逻辑直接在响应式文本里读 bio().length"
|
"字数统计等派生逻辑直接在响应式文本里读 bio().length",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1181,6 +1248,62 @@
|
|||||||
"max 可调整星标总数;disabled 支持布尔或信号禁用"
|
"max 可调整星标总数;disabled 支持布尔或信号禁用"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "empty",
|
||||||
|
"title": "空状态 Empty",
|
||||||
|
"demo": "sec-empty",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "title",
|
||||||
|
"t": "string",
|
||||||
|
"d": "主标题;缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "description",
|
||||||
|
"t": "string",
|
||||||
|
"d": "补充说明;缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "action",
|
||||||
|
"t": "unknown",
|
||||||
|
"d": "操作区内容(常见为按钮);缺省不渲染"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })",
|
||||||
|
"steps": [
|
||||||
|
"在列表 / 表格 / 筛选结果为空时渲染 empty 占位",
|
||||||
|
"title 一句话说明状态,description 补充原因或建议",
|
||||||
|
"action 传入 DOM(如 ghost 按钮)引导用户下一步操作"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "pagination",
|
"id": "pagination",
|
||||||
"title": "分页器 Pagination",
|
"title": "分页器 Pagination",
|
||||||
@@ -1482,6 +1605,7 @@
|
|||||||
"ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt",
|
"ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt",
|
||||||
"ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1",
|
"ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1",
|
||||||
"ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)",
|
"ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)",
|
||||||
|
"ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1",
|
||||||
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components"
|
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components"
|
||||||
],
|
],
|
||||||
"keyTypes": [
|
"keyTypes": [
|
||||||
@@ -1489,7 +1613,7 @@
|
|||||||
"MCP tool 入参 schema"
|
"MCP tool 入参 schema"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"usage": "node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|mcp>;仓库内等价于 npm run index / docs / style / verify",
|
"usage": "node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|commit-lint|mcp>;仓库内等价于 npm run index / docs / style / verify",
|
||||||
"conventions": [
|
"conventions": [
|
||||||
"零依赖:只用 node 标准库,Node >= 18",
|
"零依赖:只用 node 标准库,Node >= 18",
|
||||||
"清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生",
|
"清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生",
|
||||||
|
|||||||
+50
-36
@@ -1,39 +1,53 @@
|
|||||||
{"time":"2026-10-23T15:47:00+08:00","id":"b01","result":"done","summary":"新增 tooltip 悬浮提示组件(包裹式 API / 150ms 延迟 / Esc 收起 / reduced-motion 兜底),同步 manifest + llms-full.txt + demo 区块,并修复 ua style 对 URL 中 // 的误判;npm run check 全绿"}
|
{"ts": "2026-10-23T15:34:23+08:00", "id": "b01", "result": "done", "commit": "a57577a", "summary": "新增 tooltip 悬浮提示组件(包裹式 API / 150ms 延迟 / Esc 收起 / reduced-motion 兜底),同步 manifest + llms-full.txt + demo 区块,并修复 ua style 对 URL 中 // 的误判;npm run check 全绿"}
|
||||||
{"time":"2026-10-23T15:50:03+08:00","id":"b02","result":"done","summary":"新增 toast 消息提示组件:四语义工厂 + showToast,右上角堆叠,默认 3s 自动消失,微动画 + reduced-motion 兜底;manifest/docs/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T15:50:20+08:00", "id": "b02", "result": "done", "commit": "ece38bc", "summary": "新增 toast 消息提示组件:四语义工厂 + showToast,右上角堆叠,默认 3s 自动消失,微动画 + reduced-motion 兜底;manifest/docs/llms 同步,check 全绿"}
|
||||||
{"time":"2026-10-23T15:53:33+08:00","id":"b03","result":"done","summary":"新增 skeleton 骨架屏组件:text/rect/circle 三形状 + 扫光微动画 + reduced-motion 静态化;manifest/docs/llms/demo 同步,check 全绿"}
|
{"ts": "2026-10-23T15:53:34+08:00", "id": "b03", "result": "done", "commit": "2882057", "summary": "新增 skeleton 骨架屏组件:text/rect/circle 三形状 + 扫光微动画 + reduced-motion 静态化;manifest/docs/llms/demo 同步,check 全绿"}
|
||||||
{"time":"2026-10-23T16:00:11+08:00","id":"b04","result":"done","summary":"新增独立 pagination 分页器(page 信号双向绑定 + 省略号折叠 + 键盘可达),dataTable 新增 page 受控桥接实现联动;demo 联动卡片,check 全绿"}
|
{"ts": "2026-10-23T16:00:13+08:00", "id": "b04", "result": "done", "commit": "8c9a63b", "summary": "新增独立 pagination 分页器(page 信号双向绑定 + 省略号折叠 + 键盘可达),dataTable 新增 page 受控桥接实现联动;demo 联动卡片,check 全绿"}
|
||||||
{"time":"2026-10-23T16:03:46+08:00","id":"b05","result":"done","summary":"文档站核查确认已 manifest 驱动;补齐默认值信息量缺口:CLI propsTable 新增默认值列(df 字段)+ llms-full 默认标注,16 处默认值提取;check 全绿"}
|
{"ts": "2026-10-23T16:03:47+08:00", "id": "b05", "result": "done", "commit": "83bc1e2", "summary": "文档站核查确认已 manifest 驱动;补齐默认值信息量缺口:CLI propsTable 新增默认值列(df 字段)+ llms-full 默认标注,16 处默认值提取;check 全绿"}
|
||||||
{"time":"2026-10-23T16:08:17+08:00","id":"c01","result":"done","summary":"button/checkbox/switchButton 补 disabled(boolean|Signal),复用 h() 布尔/信号既有语义零辅助代码;CSS 通用禁用态 + demo 信号联动示例;check 全绿"}
|
{"ts": "2026-10-23T16:08:19+08:00", "id": "c01", "result": "done", "commit": "2a5892a", "summary": "button/checkbox/switchButton 补 disabled(boolean|Signal),复用 h() 布尔/信号既有语义零辅助代码;CSS 通用禁用态 + demo 信号联动示例;check 全绿"}
|
||||||
{"time":"2026-10-23T16:10:31+08:00","id":"c02","result":"done","summary":"textField/textarea 补 disabled(boolean|Signal),与 c01 同模式;禁用态 CSS 覆盖输入类;demo locked 信号联动;check 全绿"}
|
{"ts": "2026-10-23T16:10:32+08:00", "id": "c02", "result": "done", "commit": "f9324dd", "summary": "textField/textarea 补 disabled(boolean|Signal),与 c01 同模式;禁用态 CSS 覆盖输入类;demo locked 信号联动;check 全绿"}
|
||||||
{"time": "2026-10-23T16:55:00+08:00", "id": "d01", "result": "done", "summary": "新增下拉菜单 menu:触发器 + 自绘弹层,disabled/danger/divider 条目,↑↓ 环形导航 + Esc 回焦,reduced-motion 兜底;manifest/llms-full/demo 同步,check 全绿"}
|
{"ts": "2026-10-23T16:46:00+08:00", "id": "d01", "result": "done", "commit": "84277a7", "summary": "新增下拉菜单 menu:触发器 + 自绘弹层,disabled/danger/divider 条目,↑↓ 环形导航 + Esc 回焦,reduced-motion 兜底;manifest/llms-full/demo 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T17:02:00+08:00", "id": "d02", "result": "done", "summary": "select/radioGroup 补 disabled(布尔|信号),并修复 select 根节点缺 ua-open 导致箭头不旋转的既有缺陷;CSS :has() 行级弱化;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:47:03+08:00", "id": "d02", "result": "done", "commit": "6b9fa95", "summary": "select/radioGroup 补 disabled(布尔|信号),并修复 select 根节点缺 ua-open 导致箭头不旋转的既有缺陷;CSS :has() 行级弱化;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:47:56+08:00", "id": "d03", "result": "done", "summary": "新增折叠面板 accordion:单开/多开 + active 受控 + 禁用面板,grid 行高 0fr→1fr 过渡免测量,aria 关联完整;manifest/llms/demo 同步,check 全绿"}
|
{"ts": "2026-10-23T16:47:56+08:00", "id": "d03", "result": "done", "commit": "df2fb34", "summary": "新增折叠面板 accordion:单开/多开 + active 受控 + 禁用面板,grid 行高 0fr→1fr 过渡免测量,aria 关联完整;manifest/llms/demo 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:48:36+08:00", "id": "d04", "result": "done", "summary": "dataTable 新增 loading(布尔|信号):表体换骨架行、筛选与翻页禁用、aria-busy;demo 加开关,manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:48:36+08:00", "id": "d04", "result": "done", "commit": "ccbdcb9", "summary": "dataTable 新增 loading(布尔|信号):表体换骨架行、筛选与翻页禁用、aria-busy;demo 加开关,manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:49:45+08:00", "id": "d05", "result": "done", "summary": "新增面包屑 breadcrumb:nav/ol 语义结构 + 末项 aria-current,href 走 <a>、无 href 走 onNavigate,分隔符可配;manifest/llms/demo 同步,check 全绿(中途被 style 门禁拦下多行注释,已改单行)"}
|
{"ts": "2026-10-23T16:49:45+08:00", "id": "d05", "result": "done", "commit": "60c0524", "summary": "新增面包屑 breadcrumb:nav/ol 语义结构 + 末项 aria-current,href 走 <a>、无 href 走 onNavigate,分隔符可配;manifest/llms/demo 同步,check 全绿(中途被 style 门禁拦下多行注释,已改单行)"}
|
||||||
{"time": "2026-10-23T16:50:49+08:00", "id": "d06", "result": "done", "summary": "新增 ua verify 门禁:清单 id/exports/props/steps/example 与 demo 锚点一致性,纳入 npm run check;顺带补齐 badge/alert/spinner 手把手第三条,check 全绿"}
|
{"ts": "2026-10-23T16:50:49+08:00", "id": "d06", "result": "done", "commit": "582318a", "summary": "新增 ua verify 门禁:清单 id/exports/props/steps/example 与 demo 锚点一致性,纳入 npm run check;顺带补齐 badge/alert/spinner 手把手第三条,check 全绿"}
|
||||||
{"time": "2026-10-23T16:51:48+08:00", "id": "d07", "result": "done", "summary": "新增可交互气泡卡片 popover:点击/hover 触发、点外部与 Esc 收起、内容可交互且内部点击不误关;manifest/llms/demo 同步,check 全绿"}
|
{"ts": "2026-10-23T16:51:48+08:00", "id": "d07", "result": "done", "commit": "fb9ab88", "summary": "新增可交互气泡卡片 popover:点击/hover 触发、点外部与 Esc 收起、内容可交互且内部点击不误关;manifest/llms/demo 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:52:28+08:00", "id": "d08", "result": "done", "summary": "演示页改双栏 shell:sticky 侧边目录由 .sec 自动反推 + IntersectionObserver 滚动高亮,窄屏隐藏、reduced-motion 关平滑滚动;check 全绿"}
|
{"ts": "2026-10-23T16:52:28+08:00", "id": "d08", "result": "done", "commit": "1a4b2c1", "summary": "演示页改双栏 shell:sticky 侧边目录由 .sec 自动反推 + IntersectionObserver 滚动高亮,窄屏隐藏、reduced-motion 关平滑滚动;check 全绿"}
|
||||||
{"time": "2026-10-23T16:53:06+08:00", "id": "d09", "result": "done", "summary": "新增滑块 slider:value 信号双向绑定 + min/max/step + 布尔|信号禁用 + showValue,原生 range 承载键盘与触屏,accent-color 令牌化;manifest/llms/demo 同步,check 全绿"}
|
{"ts": "2026-10-23T16:53:06+08:00", "id": "d09", "result": "done", "commit": "a11157b", "summary": "新增滑块 slider:value 信号双向绑定 + min/max/step + 布尔|信号禁用 + showValue,原生 range 承载键盘与触屏,accent-color 令牌化;manifest/llms/demo 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:53:44+08:00", "id": "d10", "result": "done", "summary": "toast 新增 closable(× 手动关闭)与同屏上限 4 条,退场统一走 dismiss() 双兜底;is-closable 才接收指针;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:53:44+08:00", "id": "d10", "result": "done", "commit": "a08f157", "summary": "toast 新增 closable(× 手动关闭)与同屏上限 4 条,退场统一走 dismiss() 双兜底;is-closable 才接收指针;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:54:36+08:00", "id": "d11", "result": "done", "summary": "modal 新增 size 三档(sm/md/lg)与 footer 操作区(右对齐 + 上分割线),缺省行为不变;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:54:36+08:00", "id": "d11", "result": "done", "commit": "d1a2864", "summary": "modal 新增 size 三档(sm/md/lg)与 footer 操作区(右对齐 + 上分割线),缺省行为不变;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:55:10+08:00", "id": "d12", "result": "done", "summary": "新增可关闭标签 tag(五档语义色 + closable/onClose,无状态委托调用方重渲染);demo 用 renderList 演示真实删除;manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:55:10+08:00", "id": "d12", "result": "done", "commit": "9eefdcb", "summary": "新增可关闭标签 tag(五档语义色 + closable/onClose,无状态委托调用方重渲染);demo 用 renderList 演示真实删除;manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:55:55+08:00", "id": "d13", "result": "done", "summary": "dataTable 列定义新增皮肤层 TableColumn(align + format),内核 ColumnDef 不变;表头/单元格共用对齐类,format 走 textContent;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:55:55+08:00", "id": "d13", "result": "done", "commit": "730ee1a", "summary": "dataTable 列定义新增皮肤层 TableColumn(align + format),内核 ColumnDef 不变;表头/单元格共用对齐类,format 走 textContent;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:56:36+08:00", "id": "d14", "result": "done", "summary": "新增抽屉 drawer:open 信号驱动侧滑、placement/width 可配、footer 操作区、遮罩/×/Esc 关闭并聚焦首元素;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:56:36+08:00", "id": "d14", "result": "done", "commit": "d3fc754", "summary": "新增抽屉 drawer:open 信号驱动侧滑、placement/width 可配、footer 操作区、遮罩/×/Esc 关闭并聚焦首元素;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:57:22+08:00", "id": "d15", "result": "done", "summary": "CLI 包补 ua.manifest.json(type: tooling,七个子命令 + 约定),注册表 5 包;修正 AGENTS/ARCHITECTURE 过期组件数 17→27;check 全绿"}
|
{"ts": "2026-10-23T16:57:22+08:00", "id": "d15", "result": "done", "commit": "1c03c81", "summary": "CLI 包补 ua.manifest.json(type: tooling,七个子命令 + 约定),注册表 5 包;修正 AGENTS/ARCHITECTURE 过期组件数 17→27;check 全绿"}
|
||||||
{"time": "2026-10-23T16:58:51+08:00", "id": "d16", "result": "done", "summary": "dataTable 消费内核列宽 width:colgroup 固定像素 / flex 自适应,选择列 44px;demo 三列演示宽档;manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T16:58:52+08:00", "id": "d16", "result": "done", "commit": "fa168b9", "summary": "dataTable 消费内核列宽 width:colgroup 固定像素 / flex 自适应,选择列 44px;demo 三列演示宽档;manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T16:59:46+08:00", "id": "d17", "result": "done", "summary": "表单错误态打通包裹式控件:focusout/change 取代 blur + aria-invalid + .ua-invalid 后代选择器覆盖 select/radio/slider;demo 加必填 select;check 全绿"}
|
{"ts": "2026-10-23T16:59:46+08:00", "id": "d17", "result": "done", "commit": "bdc1fec", "summary": "表单错误态打通包裹式控件:focusout/change 取代 blur + aria-invalid + .ua-invalid 后代选择器覆盖 select/radio/slider;demo 加必填 select;check 全绿"}
|
||||||
{"time": "2026-10-23T17:00:55+08:00", "id": "d18", "result": "done", "summary": "ua verify 新增皮肤硬编码颜色门禁(#hex 禁止,rgba 仅限阴影/遮罩),清掉 ui.css 5 处 #fff 改令牌;负向测试确认能拦;check 全绿"}
|
{"ts": "2026-10-23T17:00:55+08:00", "id": "d18", "result": "done", "commit": "4012923", "summary": "ua verify 新增皮肤硬编码颜色门禁(#hex 禁止,rgba 仅限阴影/遮罩),清掉 ui.css 5 处 #fff 改令牌;负向测试确认能拦;check 全绿"}
|
||||||
{"time": "2026-10-23T17:01:46+08:00", "id": "d19", "result": "done", "summary": "dataTable 新增 selected 受控信号:与内核选择双向同步(内容比较 + 拷贝保护),demo 展示已选计数与清空;check 全绿"}
|
{"ts": "2026-10-23T17:01:46+08:00", "id": "d19", "result": "done", "commit": "4555542", "summary": "dataTable 新增 selected 受控信号:与内核选择双向同步(内容比较 + 拷贝保护),demo 展示已选计数与清空;check 全绿"}
|
||||||
{"time": "2026-10-23T17:02:24+08:00", "id": "d20", "result": "done", "summary": "progress 支持不确定态:value 可选,缺省时 35% 填充块往复 + aria-label 进行中;定量态补 aria-valuenow;demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T17:02:24+08:00", "id": "d20", "result": "done", "commit": "9cfdb43", "summary": "progress 支持不确定态:value 可选,缺省时 35% 填充块往复 + aria-label 进行中;定量态补 aria-valuenow;demo/manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T17:03:02+08:00", "id": "d21", "result": "done", "summary": "无障碍收尾:modal × 补 aria-label/type、spinner role=status、skeleton aria-hidden、tablist aria-label + 禁用页签 aria-disabled;conventions 记录基线;check 全绿"}
|
{"ts": "2026-10-23T17:03:02+08:00", "id": "d21", "result": "done", "commit": "00a6104", "summary": "无障碍收尾:modal × 补 aria-label/type、spinner role=status、skeleton aria-hidden、tablist aria-label + 禁用页签 aria-disabled;conventions 记录基线;check 全绿"}
|
||||||
{"time": "2026-10-23T17:03:49+08:00", "id": "d22", "result": "done", "summary": "新增头像 avatar(src 图片 / 首字占位 + size + 四档语义底色),demo 独立 sec-avatar;中途被 verify 的 demo 锚点检查拦下已补;check 全绿"}
|
{"ts": "2026-10-23T17:03:49+08:00", "id": "d22", "result": "done", "commit": "f86da98", "summary": "新增头像 avatar(src 图片 / 首字占位 + size + 四档语义底色),demo 独立 sec-avatar;中途被 verify 的 demo 锚点检查拦下已补;check 全绿"}
|
||||||
{"time": "2026-10-23T17:04:27+08:00", "id": "d23", "result": "done", "summary": "alert 新增 title / closable / onClose 与 role=alert,关闭走 200ms 退场后摘除;demo 独立 sec-alert;manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T17:04:28+08:00", "id": "d23", "result": "done", "commit": "f361a19", "summary": "alert 新增 title / closable / onClose 与 role=alert,关闭走 200ms 退场后摘除;demo 独立 sec-alert;manifest/llms 同步,check 全绿"}
|
||||||
{"time": "2026-10-23T17:05:46+08:00", "id": "d24", "result": "done", "summary": "重写 README(定位/快速开始/28 组件/目录/门禁/Agent 接入/流水线),组件清单做成标记区块并由 ua verify 校验防漂移;负向测试通过,check 全绿"}
|
{"ts": "2026-10-23T17:05:46+08:00", "id": "d24", "result": "done", "commit": "6efe3ab", "summary": "重写 README(定位/快速开始/28 组件/目录/门禁/Agent 接入/流水线),组件清单做成标记区块并由 ua verify 校验防漂移;负向测试通过,check 全绿"}
|
||||||
{"time": "2026-10-23T17:06:17+08:00", "id": "d25", "result": "done", "summary": "menu 条目新增 shortcut 快捷键提示(右侧 kbd 灰字,两端对齐 + 单行省略,不绑按键);demo/manifest/llms 同步,check 全绿"}
|
{"ts": "2026-10-23T17:06:17+08:00", "id": "d25", "result": "done", "commit": "6a5e662", "summary": "menu 条目新增 shortcut 快捷键提示(右侧 kbd 灰字,两端对齐 + 单行省略,不绑按键);demo/manifest/llms 同步,check 全绿"}
|
||||||
{"ts": "2026-10-23T09:09:16+00:00", "round": 26, "id": "d26", "result": "done", "commit": "82f48aa", "summary": "select 键盘导航:↑↓ 环形移动、Home/End 跳转、Esc 回焦触发器"}
|
{"ts": "2026-10-23T17:08:53+08:00", "id": "d26", "result": "done", "commit": "82f48aa", "summary": "select 键盘导航:↑↓ 环形移动、Home/End 跳转、Esc 回焦触发器"}
|
||||||
{"ts": "2026-10-23T09:11:42+00:00", "round": 27, "id": "d27", "result": "done", "commit": "fac74a3", "summary": "modal 焦点陷阱 + dialog 语义"}
|
{"ts": "2026-10-23T17:10:37+08:00", "id": "d27", "result": "done", "commit": "fac74a3", "summary": "modal 焦点陷阱 + dialog 语义"}
|
||||||
{"ts": "2026-10-23T09:11:42+00:00", "round": 28, "id": "d28", "result": "done", "commit": "", "summary": "drawer 焦点陷阱对齐 modal"}
|
{"ts": "2026-10-23T17:11:31+08:00", "id": "d28", "result": "done", "commit": "8e16829", "summary": "drawer 焦点陷阱对齐 modal"}
|
||||||
{"ts": "2026-10-23T09:12:49+00:00", "round": 29, "id": "d29", "result": "done", "commit": "", "summary": "menu Home/End 跳转对齐 select"}
|
{"ts": "2026-10-23T17:12:43+08:00", "id": "d29", "result": "done", "commit": "f8934ba", "summary": "menu Home/End 跳转对齐 select"}
|
||||||
{"ts":"2026-10-23T17:50:14+08:00","id":"e01","result":"done","commit":"20d7b94","summary":"新增 inputNumber 数字输入:−/+ 按钮 + 键盘上下步进,信号双向绑定,min/max/step 夹取,门禁全绿"}
|
{"ts":"2026-10-23T17:50:14+08:00","id":"e01","result":"done","commit":"20d7b94","summary":"新增 inputNumber 数字输入:−/+ 按钮 + 键盘上下步进,信号双向绑定,min/max/step 夹取,门禁全绿"}
|
||||||
{"ts":"2026-10-23T17:57:31+08:00","id":"e02","result":"done","commit":"fc7a1e8","summary":"新增 stepper 步骤条:三态流转 + active 信号双向绑定 + clickable 回跳,门禁全绿"}
|
{"ts":"2026-10-23T17:57:31+08:00","id":"e02","result":"done","commit":"fc7a1e8","summary":"新增 stepper 步骤条:三态流转 + active 信号双向绑定 + clickable 回跳,门禁全绿"}
|
||||||
{"ts":"2026-10-23T18:03:34+08:00","id":"e03","result":"done","commit":"ee20322","summary":"新增 rate 评分组件:星标点选 + hover 预览 + 键盘方向键调节,信号双向绑定,门禁全绿"}
|
{"ts":"2026-10-23T18:03:34+08:00","id":"e03","result":"done","commit":"ee20322","summary":"新增 rate 评分组件:星标点选 + hover 预览 + 键盘方向键调节,信号双向绑定,门禁全绿"}
|
||||||
|
{"ts":"2026-10-23T18:11:46+08:00","id":"e04","result":"done","commit":"fd4ee01","summary":"新增 empty 空状态组件:title/description/action 居中占位,静态零依赖,门禁全绿"}
|
||||||
|
{"ts":"2026-10-23T18:14:46+08:00","id":"e05","result":"done","commit":"c457e0c","summary":"新增 divider 分隔线组件:水平/垂直 + 居中文案 + 虚线,门禁全绿"}
|
||||||
|
{"ts":"2026-10-23T18:42:15+08:00","id":"f01","result":"done","commit":"c19f83b","summary":"组件数漂移纳入 ua verify 门禁:AGENTS/ARCHITECTURE 的「N 个组件」与 skin-dom 清单实际数比对,修正 27→33"}
|
||||||
|
{"ts":"2026-10-23T18:44:08+08:00","id":"f02","result":"done","commit":"f2fc286","summary":"AGENTS.md 与 ITERATION.md 的 npm run check 描述补上 verify,与实际六步链一致"}
|
||||||
|
{"ts":"2026-10-23T18:46:55+08:00","id":"f03","result":"done","commit":"f57da4c","summary":"ua 新增 lint-iterations 子命令校验留痕格式并并入 check 链,36 条历史违规行按 git author date 回填修复"}
|
||||||
|
{"ts":"2026-10-23T18:48:36+08:00","id":"f04","result":"done","commit":"99ce0cd","summary":"commit-lint 收严:非 iter type 必须带 scope,裸 chore:/docs:/iter: 反向用例全部拒绝,COMMITTING.md 同步"}
|
||||||
|
{"ts":"2026-10-23T18:52:12+08:00","id":"f05","result":"done","commit":"265060f","summary":"ua index 幂等化:剔除 generatedAt 比对且行尾归一化,内容无变化跳写,连续两次 check 后工作区干净"}
|
||||||
|
{"ts":"2026-10-23T18:53:33+08:00","id":"f06","result":"done","commit":"a5439fe","summary":"pnpm-lock.yaml 经 pnpm install --lockfile-only 生成入库;ARCHITECTURE.md 目录树补 scripts/、COMMITTING、ITERATION、pnpm-workspace、.ua 明细"}
|
||||||
|
{"ts":"2026-10-23T18:55:24+08:00","id":"g01","result":"done","commit":"98f557c","summary":"writeIfChanged 支持归一化比较,llms-full.txt 仅时间戳之差时跳写,docs 双跑工作区干净;backlog 灌入 g01-g03"}
|
||||||
|
{"ts":"2026-10-23T18:59:34+08:00","id":"g02","result":"done","commit":"33979b2","summary":"新增 image 组件:缩略图点击/回车开遮罩大图,Esc/点击遮罩关闭并归还焦点;组件 33→34,manifest/docs/README/demo 同步"}
|
||||||
|
{"ts":"2026-10-23T19:04:29+08:00","id":"g03","result":"done","commit":"bfa7013","summary":"textField/textarea/inputNumber 新增 prefix/suffix(addonWrap 零侵入包装,无附加不包裹),样式聚焦光环由组承载;demo 与 manifest 同步"}
|
||||||
|
{"ts":"2026-10-23T19:05:48+08:00","id":"g04","result":"done","commit":"c0bb8d7","summary":"dataTable 筛选输入框用 g03 的 prefix 放搜索图标,事件冒泡与筛选回归正常,门禁全绿"}
|
||||||
|
{"ts":"2026-10-23T19:07:37+08:00","id":"g05","result":"done","commit":"9651c39","summary":"verify 内联重渲染 Agent 文档与 llms-full.txt 归一化比对,漂移即拦截提示 npm run docs"}
|
||||||
|
{"ts":"2026-10-23T19:08:37+08:00","id":"g06","result":"done","commit":"b6c9489","summary":"checkCountDocs 扫描列表加入 README.md,三份文档组件计数文本全部纳入漂移门禁"}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
1. **包管理器必须用 pnpm**:包间依赖用 `workspace:*` 协议,npm 不支持。安装依赖:`pnpm install`。
|
1. **包管理器必须用 pnpm**:包间依赖用 `workspace:*` 协议,npm 不支持。安装依赖:`pnpm install`。
|
||||||
2. **构建工具**:TypeScript 5(`tsc`)。若环境未全局安装,可使用托管工作区的副本:`C:/Users/shawn/.workbuddy/binaries/node/workspace/node_modules/typescript`(本仓库历史即如此构建)。
|
2. **构建工具**:TypeScript 5(`tsc`)。若环境未全局安装,可使用托管工作区的副本:`C:/Users/shawn/.workbuddy/binaries/node/workspace/node_modules/typescript`(本仓库历史即如此构建)。
|
||||||
3. **Node**:>= 18,ESM(`"type": "module"`)。
|
3. **Node**:>= 18,ESM(`"type": "module"`)。
|
||||||
4. **验证自检**:装好后先跑 `npm run check`(build → style → typecheck → smoke → index)确认基线全绿,再开始改代码。
|
4. **验证自检**:装好后先跑 `npm run check`(build → style → typecheck → smoke → index → verify)确认基线全绿,再开始改代码。
|
||||||
|
|
||||||
## 编码规范(强制)
|
## 编码规范(强制)
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
|
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
|
||||||
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
|
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
|
||||||
- `packages/skin/ui`(@ui-arch/ui):27 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
- `packages/skin/ui`(@ui-arch/ui):34 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
||||||
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
|
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
|
||||||
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
|
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
|
||||||
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
||||||
|
|||||||
+10
-2
@@ -34,16 +34,24 @@ ui-arch-mvp/
|
|||||||
│ │ ├── table-core/ # 表格状态内核
|
│ │ ├── table-core/ # 表格状态内核
|
||||||
│ │ └── frame/ # 信号 + hyperscript 微框架
|
│ │ └── frame/ # 信号 + hyperscript 微框架
|
||||||
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
|
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
|
||||||
│ │ └── ui/ # 27 个组件 + ui.css
|
│ │ └── ui/ # 34 个组件 + ui.css
|
||||||
│ ├── design/ # 资产层(可发布)
|
│ ├── design/ # 资产层(可发布)
|
||||||
│ │ └── tokens/ # --ua-* 设计令牌
|
│ │ └── tokens/ # --ua-* 设计令牌
|
||||||
│ └── tools/ # 工程层(private,不发布)
|
│ └── tools/ # 工程层(private,不发布)
|
||||||
│ └── cli/ # ua 命令行 + MCP 注册表服务
|
│ └── cli/ # ua 命令行 + MCP 注册表服务
|
||||||
|
├── scripts/ # 工程脚本:gitea.mjs(git 推送封装)、gitea-api.mjs(Gitea REST API)
|
||||||
├── ARCHITECTURE.md # 本文件:结构规范
|
├── ARCHITECTURE.md # 本文件:结构规范
|
||||||
├── AGENTS.md # Agent 接入手册(兼项目入口文档)
|
├── AGENTS.md # Agent 接入手册(兼项目入口文档)
|
||||||
├── CODING.md # 编码规范
|
├── CODING.md # 编码规范
|
||||||
|
├── COMMITTING.md # 提交信息规范(commit-lint 机器校验)
|
||||||
|
├── ITERATION.md # 无人值守自我迭代协议(自动化 Agent 执行)
|
||||||
|
├── pnpm-workspace.yaml # pnpm 工作区定义
|
||||||
|
├── pnpm-lock.yaml # 依赖锁定(入库,保证依赖树可复现)
|
||||||
├── llms-full.txt # Agent 全量文档(ua docs 生成)
|
├── llms-full.txt # Agent 全量文档(ua docs 生成)
|
||||||
└── .ua/index.json + llms.txt # 注册表索引(ua index 生成,提交入库)
|
└── .ua/ # 迭代元数据
|
||||||
|
├── index.json # 注册表索引(ua index 生成,提交入库)
|
||||||
|
├── backlog.json # 迭代待办清单(open/done)
|
||||||
|
└── iterations.log # 每轮迭代留痕(lint-iterations 机器校验)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 包规范(每包强制)
|
## 包规范(每包强制)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
- 应用:`demo` / `docs`
|
- 应用:`demo` / `docs`
|
||||||
- 元数据:`backlog` / `repo` / `protocol`
|
- 元数据:`backlog` / `repo` / `protocol`
|
||||||
- `iter` 提交的 scope 固定为条目 id:`b02`、`c01`、`d29` …
|
- `iter` 提交的 scope 固定为条目 id:`b02`、`c01`、`d29` …
|
||||||
|
- **scope 强制**:除 Merge/Revert 自动信息外,所有 type 必须带 scope;`ua commit-lint` 已机器校验,裸 type(`chore:` / `docs:`)一律拒绝
|
||||||
|
|
||||||
## subject 规则
|
## subject 规则
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ qa/probe ──门禁通过──▶ qa/stable ──人工审阅──▶ main
|
|||||||
1. **同步**:`git fetch --all --prune`,切到 `qa/stable` 并 `git pull`;从其最新提交强制重建 `qa/probe` 并切换过去。
|
1. **同步**:`git fetch --all --prune`,切到 `qa/stable` 并 `git pull`;从其最新提交强制重建 `qa/probe` 并切换过去。
|
||||||
2. **选题**:读 `.ua/backlog.json`,取第一条 `status === "open"` 的条目;若全部关闭,按《选题规则》自行补充一条新点子后继续。
|
2. **选题**:读 `.ua/backlog.json`,取第一条 `status === "open"` 的条目;若全部关闭,按《选题规则》自行补充一条新点子后继续。
|
||||||
3. **实现**:按 `CODING.md` 编码规范实现,保持低侵入,单文件 < 200 行,涉及组件需同步 manifest/docs/llms-full.txt。
|
3. **实现**:按 `CODING.md` 编码规范实现,保持低侵入,单文件 < 200 行,涉及组件需同步 manifest/docs/llms-full.txt。
|
||||||
4. **门禁**:跑 `npm run check`(build → style → typecheck → smoke → index)。
|
4. **门禁**:跑 `npm run check`(build → style → typecheck → smoke → index → verify)。
|
||||||
- **通过**:提交到 `qa/probe`,提交信息按 `COMMITTING.md` 规范(`iter(<条目id>): <中文祈使句>`,元数据变更用 `chore(backlog): 记录 <id> 完成`;commit-msg 钩子自动校验),推送 `qa/probe`,fast-forward 合并到 `qa/stable` 并推送;将 backlog 条目 `status` 置为 `done` 并随迭代提交。
|
- **通过**:提交到 `qa/probe`,提交信息按 `COMMITTING.md` 规范(`iter(<条目id>): <中文祈使句>`,元数据变更用 `chore(backlog): 记录 <id> 完成`;commit-msg 钩子自动校验),推送 `qa/probe`,fast-forward 合并到 `qa/stable` 并推送;将 backlog 条目 `status` 置为 `done` 并随迭代提交。
|
||||||
- **失败**:尝试修复一次;仍失败则放弃本轮——`qa/probe` 直接重置回 `qa/stable`,backlog 条目在 `notes` 里追加失败原因、`status` 保持 `open`,提交并推送该元数据变更(仅改 `.ua/backlog.json`,不碰代码)。
|
- **失败**:尝试修复一次;仍失败则放弃本轮——`qa/probe` 直接重置回 `qa/stable`,backlog 条目在 `notes` 里追加失败原因、`status` 保持 `open`,提交并推送该元数据变更(仅改 `.ua/backlog.json`,不碰代码)。
|
||||||
5. **留痕**:在 `.ua/iterations.log` 追加一行 JSON,**字段统一**:`{"ts":"本地ISO时间","id":"条目id","result":"done|aborted","commit":"短哈希","summary":"一句话"}`。硬性规范:`ts` 必须来自 `date` 命令实测(本地时间,带 +08:00 偏移,**禁止手写或用 UTC 换算**);时长结论必须来自两次时间查询之差,禁止使用「大约/接近」等估算词;并发实例格式不得自创变体。随本轮提交。
|
5. **留痕**:在 `.ua/iterations.log` 追加一行 JSON,**字段统一**:`{"ts":"本地ISO时间","id":"条目id","result":"done|aborted","commit":"短哈希","summary":"一句话"}`。硬性规范:`ts` 必须来自 `date` 命令实测(本地时间,带 +08:00 偏移,**禁止手写或用 UTC 换算**);时长结论必须来自两次时间查询之差,禁止使用「大约/接近」等估算词;并发实例格式不得自创变体。随本轮提交。
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ npm run demo # 打开演示:apps/demo/dist/demo.html
|
|||||||
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## 组件(共 31 个)
|
## 组件(共 34 个)
|
||||||
|
|
||||||
<!-- ua:components -->
|
<!-- ua:components -->
|
||||||
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · drawer · form · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · toast · tooltip
|
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · toast · tooltip
|
||||||
<!-- /ua:components -->
|
<!-- /ua:components -->
|
||||||
|
|
||||||
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / tag / avatar / card)。
|
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / tag / avatar / card)、其他(divider)。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ accordion · alert · avatar · badge · breadcrumb · button · card · checkbo
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
||||||
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
||||||
| `packages/skin/ui` | @ui-arch/ui:31 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
| `packages/skin/ui` | @ui-arch/ui:34 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
||||||
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
||||||
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
||||||
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
||||||
|
|||||||
+62
-1
@@ -1,4 +1,12 @@
|
|||||||
import { computed, h, mount, renderList, signal } from '@ui-arch/frame';
|
import { computed, h, mount, renderList, signal } from '@ui-arch/frame';
|
||||||
|
|
||||||
|
/*** 演示用内联 SVG 图片(data URI,离线可用) ***/
|
||||||
|
const IMG_DEMO = 'data:image/svg+xml;utf8,' + encodeURIComponent(
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320">' +
|
||||||
|
'<rect width="480" height="320" fill="#185fa5"/>' +
|
||||||
|
'<circle cx="240" cy="160" r="90" fill="#ffffff" opacity="0.85"/>' +
|
||||||
|
'<text x="240" y="170" font-size="28" text-anchor="middle" fill="#185fa5">IMAGE DEMO</text></svg>',
|
||||||
|
);
|
||||||
import {
|
import {
|
||||||
accordion,
|
accordion,
|
||||||
alert,
|
alert,
|
||||||
@@ -9,7 +17,10 @@ import {
|
|||||||
card,
|
card,
|
||||||
checkbox,
|
checkbox,
|
||||||
dataTable,
|
dataTable,
|
||||||
|
divider,
|
||||||
drawer,
|
drawer,
|
||||||
|
empty,
|
||||||
|
image,
|
||||||
form,
|
form,
|
||||||
formItem,
|
formItem,
|
||||||
inputNumber,
|
inputNumber,
|
||||||
@@ -116,6 +127,8 @@ const volume = signal(40);
|
|||||||
|
|
||||||
// —— 数字输入:购买数量演示,与禁用态实例共用同一信号 ——
|
// —— 数字输入:购买数量演示,与禁用态实例共用同一信号 ——
|
||||||
const qty = signal(1);
|
const qty = signal(1);
|
||||||
|
const price = signal('9.9'); // 演示 suffix 附加块
|
||||||
|
const amount = signal(2); // 演示 inputNumber 前后缀
|
||||||
|
|
||||||
// —— 步骤条:下单流程演示,按钮推进、点已完成步骤回跳 ——
|
// —— 步骤条:下单流程演示,按钮推进、点已完成步骤回跳 ——
|
||||||
const stepIdx = signal(0);
|
const stepIdx = signal(0);
|
||||||
@@ -123,6 +136,9 @@ const stepIdx = signal(0);
|
|||||||
// —— 评分:满意度演示,与只读实例共用同一信号 ——
|
// —— 评分:满意度演示,与只读实例共用同一信号 ——
|
||||||
const score = signal(0);
|
const score = signal(0);
|
||||||
|
|
||||||
|
// —— 空状态:搜索无结果演示 ——
|
||||||
|
const noResult = signal(true);
|
||||||
|
|
||||||
// —— 受控选中:dataTable 内部勾选实时写回,外部可清空 ——
|
// —— 受控选中:dataTable 内部勾选实时写回,外部可清空 ——
|
||||||
const picked = signal<RowKey[]>([]);
|
const picked = signal<RowKey[]>([]);
|
||||||
|
|
||||||
@@ -155,8 +171,10 @@ const content = h(
|
|||||||
section(
|
section(
|
||||||
'sec-bind',
|
'sec-bind',
|
||||||
'双向绑定',
|
'双向绑定',
|
||||||
'textField 与信号双向同步:输入即回显。',
|
'textField 与信号双向同步:输入即回显;prefix/suffix 渲染为输入框内侧附加块。',
|
||||||
textField({ placeholder: '输入名字…', value: name }),
|
textField({ placeholder: '输入名字…', value: name }),
|
||||||
|
textField({ placeholder: '搜索…', value: name, prefix: '🔍' }),
|
||||||
|
textField({ value: price, suffix: '元' }),
|
||||||
h('span', { class: 'out' }, () => (name() ? `你好,${name()}!` : '等待输入…')),
|
h('span', { class: 'out' }, () => (name() ? `你好,${name()}!` : '等待输入…')),
|
||||||
),
|
),
|
||||||
section(
|
section(
|
||||||
@@ -253,6 +271,7 @@ const content = h(
|
|||||||
{ class: 'row' },
|
{ class: 'row' },
|
||||||
inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => toast.info(`数量 ${v}`) }),
|
inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => toast.info(`数量 ${v}`) }),
|
||||||
inputNumber({ value: qty, disabled: locked }),
|
inputNumber({ value: qty, disabled: locked }),
|
||||||
|
inputNumber({ value: amount, min: 0, prefix: '¥', suffix: '件' }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
section(
|
section(
|
||||||
@@ -379,6 +398,48 @@ const content = h(
|
|||||||
),
|
),
|
||||||
h('div', { class: 'row' }, rate({ value: score, disabled: true })),
|
h('div', { class: 'row' }, rate({ value: score, disabled: true })),
|
||||||
),
|
),
|
||||||
|
section(
|
||||||
|
'sec-image',
|
||||||
|
'图片',
|
||||||
|
'image:缩略图点击 / 回车打开遮罩大图,Esc 或点击遮罩关闭,焦点归还缩略图。',
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
image({ src: IMG_DEMO, alt: '演示图一', width: 180 }),
|
||||||
|
image({ src: IMG_DEMO, alt: '演示图二', width: 120 }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
section(
|
||||||
|
'sec-empty',
|
||||||
|
'空状态',
|
||||||
|
'empty:无数据 / 无结果的统一占位,action 放引导按钮,点击按钮切回有数据态可对比。',
|
||||||
|
h('div', { class: 'row' }, h('span', { class: 'out' }, () => (noResult() ? '当前:无结果' : '当前:有数据'))),
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
button({ label: '切换数据态', onClick: () => noResult(!noResult()) }),
|
||||||
|
),
|
||||||
|
() => (noResult()
|
||||||
|
? empty({ title: '暂无匹配结果', description: '试试放宽筛选条件,或清空关键词重新搜索。', action: button({ label: '重置筛选', variant: 'ghost', onClick: () => noResult(false) }) })
|
||||||
|
: card({ title: '搜索结果' }, h('p', null, '这里展示命中的数据列表。'))),
|
||||||
|
),
|
||||||
|
section(
|
||||||
|
'sec-divider',
|
||||||
|
'分隔线',
|
||||||
|
'divider:水平线可带居中文案,dashed 切虚线;vertical 行内竖线用于工具栏。',
|
||||||
|
divider({ label: '更多信息' }),
|
||||||
|
h('p', null, '分割线上下的内容各自成组。'),
|
||||||
|
divider({ dashed: true }),
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
button({ label: '保存', variant: 'ghost' }),
|
||||||
|
divider({ vertical: true }),
|
||||||
|
button({ label: '另存为', variant: 'ghost' }),
|
||||||
|
divider({ vertical: true, dashed: true }),
|
||||||
|
button({ label: '导出', variant: 'ghost' }),
|
||||||
|
),
|
||||||
|
),
|
||||||
section(
|
section(
|
||||||
'sec-menu',
|
'sec-menu',
|
||||||
'下拉菜单',
|
'下拉菜单',
|
||||||
|
|||||||
+85
-5
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-23T10:03:21.198Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-23T11:03:12.689Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -50,6 +50,29 @@
|
|||||||
|
|
||||||
28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
|
28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
|
||||||
|
|
||||||
|
### image — 图片 Image
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- src(必填):string —— 图片地址(缩略图与大图共用)
|
||||||
|
- alt(默认 ''):string —— 替代文本;缺省空串
|
||||||
|
- width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
image({ src: '/a.png', alt: '架构图', width: 160 })
|
||||||
|
```
|
||||||
|
|
||||||
|
手把手:
|
||||||
|
|
||||||
|
1. 基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜
|
||||||
|
2. 点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图
|
||||||
|
3. 宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width
|
||||||
|
4. alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它
|
||||||
|
|
||||||
|
现场演示:apps/docs/demo.html#sec-image
|
||||||
|
|
||||||
### button — 按钮 Button
|
### button — 按钮 Button
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -81,6 +104,8 @@ button({ label: '提交', variant: 'primary', onClick: () => console.log('clicke
|
|||||||
- placeholder:string —— 占位文案
|
- placeholder:string —— 占位文案
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||||||
- onInput(回调):(value: string) => void —— 输入回调,每次按键触发
|
- onInput(回调):(value: string) => void —— 输入回调,每次按键触发
|
||||||
|
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||||||
|
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@@ -93,6 +118,7 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输
|
|||||||
1. 先用 signal('') 创建字符串信号
|
1. 先用 signal('') 创建字符串信号
|
||||||
2. 传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显
|
2. 传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显
|
||||||
3. 在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`
|
3. 在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`
|
||||||
|
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-bind
|
现场演示:apps/docs/demo.html#sec-bind
|
||||||
|
|
||||||
@@ -106,6 +132,8 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输
|
|||||||
- step(默认 1):number —— 步长;缺省 1
|
- step(默认 1):number —— 步长;缺省 1
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||||||
- onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间)
|
- onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间)
|
||||||
|
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||||||
|
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@@ -118,6 +146,7 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC
|
|||||||
1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定
|
1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定
|
||||||
2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析
|
2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析
|
||||||
3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用
|
3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用
|
||||||
|
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-inputnumber
|
现场演示:apps/docs/demo.html#sec-inputnumber
|
||||||
|
|
||||||
@@ -130,6 +159,8 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC
|
|||||||
- placeholder:string —— 占位文案
|
- placeholder:string —— 占位文案
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||||||
- onInput(回调):(value: string) => void —— 输入回调
|
- onInput(回调):(value: string) => void —— 输入回调
|
||||||
|
- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染
|
||||||
|
- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@@ -142,6 +173,7 @@ const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话
|
|||||||
1. 创建信号并传入 value
|
1. 创建信号并传入 value
|
||||||
2. 用 rows 控制初始高度,用户可纵向拉伸
|
2. 用 rows 控制初始高度,用户可纵向拉伸
|
||||||
3. 字数统计等派生逻辑直接在响应式文本里读 bio().length
|
3. 字数统计等派生逻辑直接在响应式文本里读 bio().length
|
||||||
|
4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-form
|
现场演示:apps/docs/demo.html#sec-form
|
||||||
|
|
||||||
@@ -674,6 +706,50 @@ const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.l
|
|||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-rate
|
现场演示:apps/docs/demo.html#sec-rate
|
||||||
|
|
||||||
|
### empty — 空状态 Empty
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- title:string —— 主标题;缺省不渲染
|
||||||
|
- description:string —— 补充说明;缺省不渲染
|
||||||
|
- action:unknown —— 操作区内容(常见为按钮);缺省不渲染
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })
|
||||||
|
```
|
||||||
|
|
||||||
|
手把手:
|
||||||
|
|
||||||
|
1. 在列表 / 表格 / 筛选结果为空时渲染 empty 占位
|
||||||
|
2. title 一句话说明状态,description 补充原因或建议
|
||||||
|
3. action 传入 DOM(如 ghost 按钮)引导用户下一步操作
|
||||||
|
|
||||||
|
现场演示:apps/docs/demo.html#sec-empty
|
||||||
|
|
||||||
|
### divider — 分隔线 Divider
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- label:string —— 居中文案标签;缺省纯线条
|
||||||
|
- vertical:boolean —— 是否垂直分隔线(用于行内工具栏);缺省水平
|
||||||
|
- dashed:boolean —— 是否虚线;缺省实线
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
divider({ label: '更多信息' }) / divider({ vertical: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
手把手:
|
||||||
|
|
||||||
|
1. 在区块之间插入 divider 划分内容层次
|
||||||
|
2. 传 label 在水平线上显示居中文案,如「更多信息」
|
||||||
|
3. 工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感
|
||||||
|
|
||||||
|
现场演示:apps/docs/demo.html#sec-divider
|
||||||
|
|
||||||
### pagination — 分页器 Pagination
|
### pagination — 分页器 Pagination
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -825,9 +901,9 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓
|
|||||||
API 速览:
|
API 速览:
|
||||||
|
|
||||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||||
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块
|
||||||
- inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取
|
- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块
|
||||||
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块
|
||||||
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
||||||
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
@@ -851,11 +927,14 @@ API 速览:
|
|||||||
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
|
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
|
||||||
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
|
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
|
||||||
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配
|
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配
|
||||||
|
- empty(props):空状态占位,title/description 可选,action 放操作区
|
||||||
|
- divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线
|
||||||
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
|
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
|
||||||
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
|
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
|
||||||
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
|
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
|
||||||
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
|
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
|
||||||
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
|
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
|
||||||
|
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽
|
||||||
|
|
||||||
一行用法:
|
一行用法:
|
||||||
|
|
||||||
@@ -953,12 +1032,13 @@ API 速览:
|
|||||||
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
|
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
|
||||||
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
|
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
|
||||||
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
|
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
|
||||||
|
- ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1
|
||||||
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
|
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
|
||||||
|
|
||||||
一行用法:
|
一行用法:
|
||||||
|
|
||||||
```
|
```
|
||||||
node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|mcp>;仓库内等价于 npm run index / docs / style / verify
|
node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|commit-lint|mcp>;仓库内等价于 npm run index / docs / style / verify
|
||||||
```
|
```
|
||||||
|
|
||||||
约定:
|
约定:
|
||||||
|
|||||||
@@ -45,12 +45,12 @@
|
|||||||
## @ui-arch/ui (v0.1.0)
|
## @ui-arch/ui (v0.1.0)
|
||||||
|
|
||||||
- 类型: skin-dom · 目录: packages/skin/ui
|
- 类型: skin-dom · 目录: packages/skin/ui
|
||||||
- 描述: DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。
|
- 描述: DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。
|
||||||
- API:
|
- API:
|
||||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||||
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块
|
||||||
- inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取
|
- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块
|
||||||
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块
|
||||||
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
||||||
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
@@ -74,11 +74,14 @@
|
|||||||
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
|
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
|
||||||
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
|
- stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳
|
||||||
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配
|
- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配
|
||||||
|
- empty(props):空状态占位,title/description 可选,action 放操作区
|
||||||
|
- divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线
|
||||||
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
|
- modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区
|
||||||
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
|
- drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭
|
||||||
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
|
- form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit
|
||||||
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
|
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
|
||||||
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
|
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
|
||||||
|
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽
|
||||||
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
|
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
|
||||||
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||||||
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||||||
@@ -96,8 +99,9 @@
|
|||||||
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
|
- ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt
|
||||||
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
|
- ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1
|
||||||
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
|
- ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)
|
||||||
|
- ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1
|
||||||
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
|
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components
|
||||||
- 用法: `node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|mcp>;仓库内等价于 npm run index / docs / style / verify`
|
- 用法: `node packages/tools/cli/bin/ua.js <index|list|info <name>|docs|style|verify|commit-lint|mcp>;仓库内等价于 npm run index / docs / style / verify`
|
||||||
- 约定: 零依赖:只用 node 标准库,Node >= 18
|
- 约定: 零依赖:只用 node 标准库,Node >= 18
|
||||||
- 约定: 清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生
|
- 约定: 清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生
|
||||||
- 约定: 门禁只增不改:新增校验必须能被人手复现,报错要指出文件与字段
|
- 约定: 门禁只增不改:新增校验必须能被人手复现,报错要指出文件与字段
|
||||||
|
|||||||
+3
-2
@@ -12,9 +12,10 @@
|
|||||||
"style": "node packages/tools/cli/bin/ua.js style",
|
"style": "node packages/tools/cli/bin/ua.js style",
|
||||||
"index": "node packages/tools/cli/bin/ua.js index",
|
"index": "node packages/tools/cli/bin/ua.js index",
|
||||||
"docs": "node packages/tools/cli/bin/ua.js index && node packages/tools/cli/bin/ua.js docs",
|
"docs": "node packages/tools/cli/bin/ua.js index && node packages/tools/cli/bin/ua.js docs",
|
||||||
"check": "npm run build && npm run style && npm run typecheck && npm run smoke && npm run index && npm run verify",
|
"check": "npm run build && npm run style && npm run typecheck && npm run smoke && npm run index && npm run verify && npm run lint-iterations",
|
||||||
"ua": "node packages/tools/cli/bin/ua.js",
|
"ua": "node packages/tools/cli/bin/ua.js",
|
||||||
"verify": "node packages/tools/cli/bin/ua.js verify"
|
"verify": "node packages/tools/cli/bin/ua.js verify",
|
||||||
|
"lint-iterations": "node packages/tools/cli/bin/ua.js lint-iterations"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.6.0"
|
"typescript": "^5.6.0"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { h } from '@ui-arch/frame';
|
||||||
|
|
||||||
|
/*** 输入附加块包装:prefix/suffix 存在时把控件包进附加组,否则原样返回(零侵入,结构不变) ***/
|
||||||
|
export function addonWrap(
|
||||||
|
el: HTMLElement,
|
||||||
|
prefix?: string | HTMLElement,
|
||||||
|
suffix?: string | HTMLElement,
|
||||||
|
column = false, // 多行控件用纵向布局:prefix 在上、suffix 在下
|
||||||
|
): HTMLElement {
|
||||||
|
if (!prefix && !suffix) return el; // 卫语句:无附加不包裹
|
||||||
|
const addon = (v?: string | HTMLElement) => (v == null ? null : h('span', { class: 'ua-input-addon' }, v));
|
||||||
|
return h('div', { class: column ? 'ua-input-group ua-input-group-column' : 'ua-input-group' }, addon(prefix), el, addon(suffix));
|
||||||
|
}
|
||||||
@@ -156,6 +156,7 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
|||||||
|
|
||||||
const filterInput = textField({
|
const filterInput = textField({
|
||||||
placeholder: `筛选 ${filterKey}…`,
|
placeholder: `筛选 ${filterKey}…`,
|
||||||
|
prefix: '🔍',
|
||||||
disabled: props.loading, // 加载中禁止筛选,避免与在途结果竞争
|
disabled: props.loading, // 加载中禁止筛选,避免与在途结果竞争
|
||||||
onInput: (value) => {
|
onInput: (value) => {
|
||||||
table.dispatch({ type: 'setFilter', key: filterKey, value });
|
table.dispatch({ type: 'setFilter', key: filterKey, value });
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { h } from '@ui-arch/frame';
|
||||||
|
import type { DividerProps } from './types.js';
|
||||||
|
|
||||||
|
/*** 分隔线:水平/垂直两种方向,可带居中文案标签,dashed 切虚线 ***/
|
||||||
|
export function divider(props: DividerProps): HTMLDivElement {
|
||||||
|
const cls = ['ua-divider', props.vertical ? 'ua-divider-vertical' : '', props.dashed ? 'ua-divider-dashed' : ''].filter(Boolean).join(' ');
|
||||||
|
const line = props.label && !props.vertical
|
||||||
|
? h('div', { class: cls, role: 'separator', 'aria-label': props.label }, h('span', { class: 'ua-divider-label' }, props.label))
|
||||||
|
: h('div', { class: cls, role: 'separator', 'aria-label': props.label }); // 垂直或无标签:纯线条
|
||||||
|
return line as HTMLDivElement;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { h } from '@ui-arch/frame';
|
||||||
|
import type { EmptyProps } from './types.js';
|
||||||
|
|
||||||
|
/*** 空状态:居中占位提示,title/description 可选,action 放操作区(常见为按钮) ***/
|
||||||
|
export function empty(props: EmptyProps): HTMLDivElement {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ class: 'ua-empty', role: 'status' }, // role=status 由读屏播报
|
||||||
|
h('div', { class: 'ua-empty-icon', 'aria-hidden': 'true' }, '⌀'), // 装饰符号,读屏跳过
|
||||||
|
props.title ? h('div', { class: 'ua-empty-title' }, props.title) : null,
|
||||||
|
props.description ? h('div', { class: 'ua-empty-desc' }, props.description) : null,
|
||||||
|
props.action ? h('div', { class: 'ua-empty-action' }, props.action) : null,
|
||||||
|
) as HTMLDivElement;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { h } from '@ui-arch/frame';
|
||||||
|
import type { ImageProps } from './types.js';
|
||||||
|
|
||||||
|
/*** 图片:缩略图展示,点击 / 回车打开遮罩大图预览(Esc 或点击遮罩关闭),样式只消费 --ua-* 令牌 ***/
|
||||||
|
export function image(props: ImageProps): HTMLElement {
|
||||||
|
const style = props.width ? `width:${props.width}px` : '';
|
||||||
|
return h('img', {
|
||||||
|
class: 'ua-image',
|
||||||
|
src: props.src,
|
||||||
|
alt: props.alt ?? '',
|
||||||
|
style,
|
||||||
|
tabindex: '0', // 可聚焦,键盘用户回车预览
|
||||||
|
role: 'button',
|
||||||
|
'aria-label': '预览图片',
|
||||||
|
onclick: () => openViewer(props.src, props.alt),
|
||||||
|
onkeydown: (e: KeyboardEvent) => e.key === 'Enter' && openViewer(props.src, props.alt),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** 打开全屏遮罩预览层;关闭时自清理、解绑键盘并归还焦点 ***/
|
||||||
|
function openViewer(src: string, alt?: string): void {
|
||||||
|
const prev = document.activeElement as HTMLElement | null; // 焦点归还目标
|
||||||
|
const mask = h(
|
||||||
|
'div',
|
||||||
|
{ class: 'ua-image-mask', role: 'dialog', 'aria-modal': 'true', 'aria-label': '图片预览' },
|
||||||
|
h('img', { class: 'ua-image-full', src, alt: alt ?? '' }),
|
||||||
|
);
|
||||||
|
const close = () => {
|
||||||
|
mask.remove(); // 卫语句式自清理:遮罩移除 + 键盘解绑 + 焦点归还
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
prev?.focus?.();
|
||||||
|
};
|
||||||
|
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && close();
|
||||||
|
mask.addEventListener('click', close);
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
document.body.appendChild(mask);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ export { card } from './card.js';
|
|||||||
export { badge } from './badge.js';
|
export { badge } from './badge.js';
|
||||||
export { tag } from './tag.js';
|
export { tag } from './tag.js';
|
||||||
export { avatar } from './avatar.js';
|
export { avatar } from './avatar.js';
|
||||||
|
export { image } from './image.js';
|
||||||
export { alert } from './alert.js';
|
export { alert } from './alert.js';
|
||||||
export { toast, showToast } from './toast.js';
|
export { toast, showToast } from './toast.js';
|
||||||
export { tooltip } from './tooltip.js';
|
export { tooltip } from './tooltip.js';
|
||||||
@@ -26,6 +27,8 @@ export { progress } from './progress.js';
|
|||||||
export { tabs } from './tabs.js';
|
export { tabs } from './tabs.js';
|
||||||
export { stepper } from './stepper.js';
|
export { stepper } from './stepper.js';
|
||||||
export { rate } from './rate.js';
|
export { rate } from './rate.js';
|
||||||
|
export { empty } from './empty.js';
|
||||||
|
export { divider } from './divider.js';
|
||||||
export { modal } from './modal.js';
|
export { modal } from './modal.js';
|
||||||
export { drawer } from './drawer.js';
|
export { drawer } from './drawer.js';
|
||||||
export { form, formItem } from './form.js';
|
export { form, formItem } from './form.js';
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { effect, h } from '@ui-arch/frame';
|
import { effect, h } from '@ui-arch/frame';
|
||||||
import { button } from './button.js';
|
import { button } from './button.js';
|
||||||
|
import { addonWrap } from './addons.js';
|
||||||
import type { InputNumberProps } from './types.js';
|
import type { InputNumberProps } from './types.js';
|
||||||
|
|
||||||
/*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节 ***/
|
/*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节 ***/
|
||||||
export function inputNumber(props: InputNumberProps): HTMLDivElement {
|
export function inputNumber(props: InputNumberProps): HTMLElement {
|
||||||
const min = props.min ?? -Infinity; // 无下限缺省
|
const min = props.min ?? -Infinity; // 无下限缺省
|
||||||
const max = props.max ?? Infinity; // 无上限缺省
|
const max = props.max ?? Infinity; // 无上限缺省
|
||||||
const step = props.step ?? 1; // 步长缺省 1
|
const step = props.step ?? 1; // 步长缺省 1
|
||||||
@@ -22,5 +23,5 @@ export function inputNumber(props: InputNumberProps): HTMLDivElement {
|
|||||||
const inc = button({ label: '+', variant: 'ghost', disabled: props.disabled, onClick: () => emit(props.value() + step) });
|
const inc = button({ label: '+', variant: 'ghost', disabled: props.disabled, onClick: () => emit(props.value() + step) });
|
||||||
dec.setAttribute('aria-label', '减少');
|
dec.setAttribute('aria-label', '减少');
|
||||||
inc.setAttribute('aria-label', '增加');
|
inc.setAttribute('aria-label', '增加');
|
||||||
return h('div', { class: 'ua-input-number', role: 'group' }, dec, input, inc) as HTMLDivElement;
|
return addonWrap(h('div', { class: 'ua-input-number', role: 'group' }, dec, input, inc) as HTMLDivElement, props.prefix, props.suffix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { effect, h } from '@ui-arch/frame';
|
import { effect, h } from '@ui-arch/frame';
|
||||||
|
import { addonWrap } from './addons.js';
|
||||||
import type { TextFieldProps } from './types.js';
|
import type { TextFieldProps } from './types.js';
|
||||||
|
|
||||||
/*** 文本输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调 ***/
|
/*** 文本输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包附加组 ***/
|
||||||
export function textField(props: TextFieldProps): HTMLInputElement {
|
export function textField(props: TextFieldProps): HTMLElement {
|
||||||
const el = h('input', { class: 'ua-input', placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式
|
const el = h('input', { class: 'ua-input', placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
const signal = props.value;
|
const signal = props.value;
|
||||||
@@ -12,5 +13,5 @@ export function textField(props: TextFieldProps): HTMLInputElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value));
|
if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value));
|
||||||
return el;
|
return addonWrap(el, props.prefix, props.suffix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { effect, h } from '@ui-arch/frame';
|
import { effect, h } from '@ui-arch/frame';
|
||||||
|
import { addonWrap } from './addons.js';
|
||||||
import type { TextareaProps } from './types.js';
|
import type { TextareaProps } from './types.js';
|
||||||
|
|
||||||
/*** 多行输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调 ***/
|
/*** 多行输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包纵向附加组 ***/
|
||||||
export function textarea(props: TextareaProps): HTMLTextAreaElement {
|
export function textarea(props: TextareaProps): HTMLElement {
|
||||||
const el = h('textarea', { class: 'ua-textarea', rows: props.rows ?? 3, placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLTextAreaElement; // false/undefined 移除属性,Signal 走响应式
|
const el = h('textarea', { class: 'ua-textarea', rows: props.rows ?? 3, placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLTextAreaElement; // false/undefined 移除属性,Signal 走响应式
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
const signal = props.value;
|
const signal = props.value;
|
||||||
@@ -12,5 +13,5 @@ export function textarea(props: TextareaProps): HTMLTextAreaElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value));
|
if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value));
|
||||||
return el;
|
return addonWrap(el, props.prefix, props.suffix, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export interface TextFieldProps {
|
|||||||
value?: Signal<string>; // 双向绑定的信号
|
value?: Signal<string>; // 双向绑定的信号
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
||||||
onInput?: (value: string) => void; // 输入回调
|
onInput?: (value: string) => void; // 输入回调
|
||||||
|
prefix?: string | HTMLElement; // 输入框内侧前置附加块(如图标、货币符);缺省不渲染
|
||||||
|
suffix?: string | HTMLElement; // 输入框内侧后置附加块(如单位);缺省不渲染
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 卡片入参 ***/
|
/*** 卡片入参 ***/
|
||||||
@@ -163,6 +165,8 @@ export interface TextareaProps {
|
|||||||
value?: Signal<string>; // 双向绑定的信号
|
value?: Signal<string>; // 双向绑定的信号
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
||||||
onInput?: (value: string) => void; // 输入回调
|
onInput?: (value: string) => void; // 输入回调
|
||||||
|
prefix?: string | HTMLElement; // 上方附加块(如标题、计数说明);缺省不渲染
|
||||||
|
suffix?: string | HTMLElement; // 下方附加块(如字数提示);缺省不渲染
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 数字输入入参 ***/
|
/*** 数字输入入参 ***/
|
||||||
@@ -173,6 +177,8 @@ export interface InputNumberProps {
|
|||||||
step?: number; // 步长;缺省 1
|
step?: number; // 步长;缺省 1
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
||||||
onChange?: (value: number) => void; // 值变化回调(已夹取到合法区间)
|
onChange?: (value: number) => void; // 值变化回调(已夹取到合法区间)
|
||||||
|
prefix?: string | HTMLElement; // −/+ 按钮前的附加块(如货币符);缺省不渲染
|
||||||
|
suffix?: string | HTMLElement; // +/+ 按钮后的附加块(如单位);缺省不渲染
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 步骤条条目 ***/
|
/*** 步骤条条目 ***/
|
||||||
@@ -197,6 +203,20 @@ export interface RateProps {
|
|||||||
onChange?: (value: number) => void; // 评分变化回调
|
onChange?: (value: number) => void; // 评分变化回调
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 空状态入参 ***/
|
||||||
|
export interface EmptyProps {
|
||||||
|
title?: string; // 主标题;缺省不渲染
|
||||||
|
description?: string; // 补充说明;缺省不渲染
|
||||||
|
action?: unknown; // 操作区内容(常见为按钮);缺省不渲染
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** 分隔线入参 ***/
|
||||||
|
export interface DividerProps {
|
||||||
|
label?: string; // 居中文案标签;缺省纯线条
|
||||||
|
vertical?: boolean; // 是否垂直分隔线(用于行内工具栏);缺省水平
|
||||||
|
dashed?: boolean; // 是否虚线;缺省实线
|
||||||
|
}
|
||||||
|
|
||||||
/*** 徽标入参 ***/
|
/*** 徽标入参 ***/
|
||||||
export interface BadgeProps {
|
export interface BadgeProps {
|
||||||
label: string; // 徽标文案
|
label: string; // 徽标文案
|
||||||
@@ -219,6 +239,13 @@ export interface TagProps {
|
|||||||
onClose?: () => void; // 关闭回调(调用方自行从数据源移除该项)
|
onClose?: () => void; // 关闭回调(调用方自行从数据源移除该项)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 图片入参 ***/
|
||||||
|
export interface ImageProps {
|
||||||
|
src: string; // 图片地址(缩略图与大图共用)
|
||||||
|
alt?: string; // 替代文本;缺省空串
|
||||||
|
width?: number; // 缩略图宽度像素,高度自适应;缺省走样式默认
|
||||||
|
}
|
||||||
|
|
||||||
/*** 提示条入参 ***/
|
/*** 提示条入参 ***/
|
||||||
export interface AlertProps {
|
export interface AlertProps {
|
||||||
message: string; // 提示内容
|
message: string; // 提示内容
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@ui-arch/ui",
|
"name": "@ui-arch/ui",
|
||||||
"type": "skin-dom",
|
"type": "skin-dom",
|
||||||
"description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
|
"description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
|
||||||
"tags": [
|
"tags": [
|
||||||
"components",
|
"components",
|
||||||
"dom",
|
"dom",
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
"entry": "src/index.ts",
|
"entry": "src/index.ts",
|
||||||
"exports": [
|
"exports": [
|
||||||
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
||||||
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块",
|
||||||
"inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取",
|
"inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块",
|
||||||
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块",
|
||||||
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
||||||
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
@@ -40,11 +40,14 @@
|
|||||||
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
|
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
|
||||||
"stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳",
|
"stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳",
|
||||||
"rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配",
|
"rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配",
|
||||||
|
"empty(props):空状态占位,title/description 可选,action 放操作区",
|
||||||
|
"divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线",
|
||||||
"modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区",
|
"modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区",
|
||||||
"drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭",
|
"drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭",
|
||||||
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
|
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
|
||||||
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
|
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
|
||||||
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core"
|
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core",
|
||||||
|
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽"
|
||||||
],
|
],
|
||||||
"keyTypes": [
|
"keyTypes": [
|
||||||
"ButtonProps",
|
"ButtonProps",
|
||||||
@@ -101,6 +104,37 @@
|
|||||||
"读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM"
|
"读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM"
|
||||||
],
|
],
|
||||||
"components": [
|
"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": "缩略图宽度像素,高度自适应;缺省走样式默认"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
|
||||||
|
"steps": [
|
||||||
|
"基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜",
|
||||||
|
"点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图",
|
||||||
|
"宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width",
|
||||||
|
"alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "button",
|
"id": "button",
|
||||||
"title": "按钮 Button",
|
"title": "按钮 Button",
|
||||||
@@ -162,13 +196,24 @@
|
|||||||
"t": "(value: string) => void",
|
"t": "(value: string) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "输入回调,每次按键触发"
|
"d": "输入回调,每次按键触发"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"先用 signal('') 创建字符串信号",
|
"先用 signal('') 创建字符串信号",
|
||||||
"传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显",
|
"传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显",
|
||||||
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,13 +253,24 @@
|
|||||||
"t": "(value: number) => void",
|
"t": "(value: number) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "值变化回调(已夹取到合法区间)"
|
"d": "值变化回调(已夹取到合法区间)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
||||||
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
||||||
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用"
|
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -248,13 +304,24 @@
|
|||||||
"t": "(value: string) => void",
|
"t": "(value: string) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "输入回调"
|
"d": "输入回调"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string | HTMLElement",
|
||||||
|
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
||||||
"steps": [
|
"steps": [
|
||||||
"创建信号并传入 value",
|
"创建信号并传入 value",
|
||||||
"用 rows 控制初始高度,用户可纵向拉伸",
|
"用 rows 控制初始高度,用户可纵向拉伸",
|
||||||
"字数统计等派生逻辑直接在响应式文本里读 bio().length"
|
"字数统计等派生逻辑直接在响应式文本里读 bio().length",
|
||||||
|
"需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1052,6 +1119,62 @@
|
|||||||
"max 可调整星标总数;disabled 支持布尔或信号禁用"
|
"max 可调整星标总数;disabled 支持布尔或信号禁用"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "empty",
|
||||||
|
"title": "空状态 Empty",
|
||||||
|
"demo": "sec-empty",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "title",
|
||||||
|
"t": "string",
|
||||||
|
"d": "主标题;缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "description",
|
||||||
|
"t": "string",
|
||||||
|
"d": "补充说明;缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "action",
|
||||||
|
"t": "unknown",
|
||||||
|
"d": "操作区内容(常见为按钮);缺省不渲染"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "empty({ title: '暂无数据', description: '调整筛选条件后再试', action: button({ label: '重置筛选', variant: 'ghost' }) })",
|
||||||
|
"steps": [
|
||||||
|
"在列表 / 表格 / 筛选结果为空时渲染 empty 占位",
|
||||||
|
"title 一句话说明状态,description 补充原因或建议",
|
||||||
|
"action 传入 DOM(如 ghost 按钮)引导用户下一步操作"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "pagination",
|
"id": "pagination",
|
||||||
"title": "分页器 Pagination",
|
"title": "分页器 Pagination",
|
||||||
|
|||||||
@@ -814,6 +814,75 @@ button.ua-step-dot:hover {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 空状态 —— */
|
||||||
|
.ua-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ua-space-2);
|
||||||
|
padding: var(--ua-space-4);
|
||||||
|
min-height: 120px; /* 撑出足够留白感 */
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ua-empty-icon {
|
||||||
|
font-size: 34px;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--ua-color-border); /* 装饰符号弱化为边框色 */
|
||||||
|
}
|
||||||
|
.ua-empty-title {
|
||||||
|
font-size: var(--ua-font-size-md);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ua-color-text);
|
||||||
|
}
|
||||||
|
.ua-empty-desc {
|
||||||
|
font-size: var(--ua-font-size-sm);
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
}
|
||||||
|
.ua-empty-action {
|
||||||
|
margin-top: var(--ua-space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* —— 分隔线 —— */
|
||||||
|
.ua-divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ua-space-3);
|
||||||
|
margin: var(--ua-space-3) 0;
|
||||||
|
}
|
||||||
|
.ua-divider::before,
|
||||||
|
.ua-divider::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--ua-color-border);
|
||||||
|
}
|
||||||
|
.ua-divider-dashed::before,
|
||||||
|
.ua-divider-dashed::after {
|
||||||
|
background: none;
|
||||||
|
border-top: 1px dashed var(--ua-color-border);
|
||||||
|
}
|
||||||
|
.ua-divider-label {
|
||||||
|
font-size: var(--ua-font-size-sm);
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.ua-divider-vertical {
|
||||||
|
display: inline-block;
|
||||||
|
align-self: stretch;
|
||||||
|
width: 1px;
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 0 var(--ua-space-2);
|
||||||
|
}
|
||||||
|
.ua-divider-vertical::before,
|
||||||
|
.ua-divider-vertical::after {
|
||||||
|
content: none; /* 垂直线:单线无伪元素 */
|
||||||
|
}
|
||||||
|
.ua-divider-vertical.ua-divider-dashed {
|
||||||
|
background: none;
|
||||||
|
border-left: 1px dashed var(--ua-color-border); /* 垂直虚线走边框 */
|
||||||
|
}
|
||||||
|
|
||||||
/* —— 徽标 —— */
|
/* —— 徽标 —— */
|
||||||
.ua-badge {
|
.ua-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -1496,3 +1565,85 @@ button.ua-step-dot:hover {
|
|||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- image 图片(缩略图 + 遮罩大图预览) ---- */
|
||||||
|
.ua-image {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: var(--ua-radius-md);
|
||||||
|
cursor: zoom-in;
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.ua-image:hover,
|
||||||
|
.ua-image:focus-visible {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.ua-image-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.6); /* 遮罩透明度叠加,非实体色 */
|
||||||
|
cursor: zoom-out;
|
||||||
|
animation: ua-image-in 0.18s ease;
|
||||||
|
}
|
||||||
|
.ua-image-full {
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
border-radius: var(--ua-radius-md);
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
@keyframes ua-image-in {
|
||||||
|
from { opacity: 0; transform: scale(0.96); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ua-image,
|
||||||
|
.ua-image-mask {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 输入附加组(prefix/suffix) ---- */
|
||||||
|
.ua-input-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--ua-color-border);
|
||||||
|
border-radius: var(--ua-radius-md);
|
||||||
|
background: var(--ua-color-bg);
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
.ua-input-group:focus-within {
|
||||||
|
border-color: var(--ua-color-accent);
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 18%, transparent);
|
||||||
|
}
|
||||||
|
.ua-input-group .ua-input,
|
||||||
|
.ua-input-group .ua-textarea {
|
||||||
|
border: none; /* 组已带边框,内层去边防双线 */
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 160px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.ua-input-group .ua-input:focus,
|
||||||
|
.ua-input-group .ua-textarea:focus {
|
||||||
|
box-shadow: none; /* 聚焦光环由组统一承载 */
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.ua-input-group-column {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.ua-input-addon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
background: color-mix(in srgb, var(--ua-color-text) 5%, transparent);
|
||||||
|
font-size: var(--ua-font-size-md);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ function collectManifests() {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 内容无变化则跳过写入(幂等,行尾归一化防 CRLF 误判;norm 可剔除时间戳后再比对),避免弄脏工作区 ***/
|
||||||
|
function writeIfChanged(file, content, norm = (s) => s) {
|
||||||
|
if (fs.existsSync(file) && norm(fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n')) === norm(content)) return false;
|
||||||
|
fs.writeFileSync(file, content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*** 聚合清单,生成 .ua/index.json 与 llms.txt ***/
|
/*** 聚合清单,生成 .ua/index.json 与 llms.txt ***/
|
||||||
function cmdIndex() {
|
function cmdIndex() {
|
||||||
const packages = collectManifests();
|
const packages = collectManifests();
|
||||||
@@ -66,7 +73,11 @@ function cmdIndex() {
|
|||||||
repoRoot: '.',
|
repoRoot: '.',
|
||||||
packages,
|
packages,
|
||||||
};
|
};
|
||||||
fs.writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2) + '\n');
|
// 幂等:剔除 generatedAt 后内容一致即不写入,连续 check 不弄脏工作区
|
||||||
|
const json = JSON.stringify(index, null, 2) + '\n';
|
||||||
|
const stripTs = (s) => s.replace(/"generatedAt": "[^"]*"/, '"generatedAt": ""');
|
||||||
|
const changed = !fs.existsSync(INDEX_FILE) || stripTs(fs.readFileSync(INDEX_FILE, 'utf8').replace(/\r\n/g, '\n')) !== stripTs(json);
|
||||||
|
if (changed) fs.writeFileSync(INDEX_FILE, json);
|
||||||
|
|
||||||
const lines = [
|
const lines = [
|
||||||
'# @ui-arch 私人组件库索引',
|
'# @ui-arch 私人组件库索引',
|
||||||
@@ -87,8 +98,8 @@ function cmdIndex() {
|
|||||||
for (const c of p.conventions ?? []) lines.push(`- 约定: ${c}`);
|
for (const c of p.conventions ?? []) lines.push(`- 约定: ${c}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
}
|
}
|
||||||
fs.writeFileSync(LLMS_FILE, lines.join('\n'));
|
writeIfChanged(LLMS_FILE, lines.join('\n'));
|
||||||
console.log(`indexed ${packages.length} package(s) -> .ua/index.json, llms.txt`);
|
console.log(`indexed ${packages.length} package(s) -> .ua/index.json, llms.txt${changed ? '' : ' (unchanged, skip)'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 读取注册表索引;缺失则报错退出 ***/
|
/*** 读取注册表索引;缺失则报错退出 ***/
|
||||||
@@ -345,7 +356,9 @@ function cmdDocs() {
|
|||||||
fs.writeFileSync(path.join(outDir, 'index.html'), renderDocs(index, tokens));
|
fs.writeFileSync(path.join(outDir, 'index.html'), renderDocs(index, tokens));
|
||||||
const demoSrc = path.join(ROOT, 'apps', 'demo', 'dist', 'demo.html'); // 演示页拷入文档站供 iframe
|
const demoSrc = path.join(ROOT, 'apps', 'demo', 'dist', 'demo.html'); // 演示页拷入文档站供 iframe
|
||||||
if (fs.existsSync(demoSrc)) fs.copyFileSync(demoSrc, path.join(outDir, 'demo.html'));
|
if (fs.existsSync(demoSrc)) fs.copyFileSync(demoSrc, path.join(outDir, 'demo.html'));
|
||||||
fs.writeFileSync(LLMS_FULL, renderAgentDocs(index, tokens));
|
// llms-full 头部时间戳仅生成时刻之差视为无变化(与 index 幂等化同方案)
|
||||||
|
const normTs = (s) => s.replace(/自动生成于 [^,]*/, '自动生成于 X');
|
||||||
|
writeIfChanged(LLMS_FULL, renderAgentDocs(index, tokens), normTs);
|
||||||
console.log(
|
console.log(
|
||||||
`docs generated -> apps/docs/index.html + demo.html, llms-full.txt (${index.packages.length} packages)`,
|
`docs generated -> apps/docs/index.html + demo.html, llms-full.txt (${index.packages.length} packages)`,
|
||||||
);
|
);
|
||||||
@@ -593,6 +606,35 @@ function checkReadme(pkgs) {
|
|||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 组件数漂移校验:AGENTS.md / ARCHITECTURE.md 中的「N 个组件」必须与 skin-dom 清单实际总数一致 ***/
|
||||||
|
function checkCountDocs(actual) {
|
||||||
|
const issues = [];
|
||||||
|
const re = /(\d+)\s*个(?:\s*DOM\s*)?组件/;
|
||||||
|
for (const name of ['AGENTS.md', 'ARCHITECTURE.md', 'README.md']) {
|
||||||
|
const file = path.join(ROOT, name);
|
||||||
|
if (!fs.existsSync(file)) continue; // 卫语句:文档缺失不校验
|
||||||
|
fs.readFileSync(file, 'utf8').split(/\r?\n/).forEach((line, i) => {
|
||||||
|
const m = line.match(re);
|
||||||
|
if (m && Number(m[1]) !== actual) {
|
||||||
|
issues.push(`${name}:${i + 1} 组件数「${m[1]}」与清单实际 ${actual} 不一致,请同步`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** llms-full.txt 同步校验:内联重渲染 Agent 文档(时间戳归一化)与落盘产物比对,防 manifest 改后忘跑 docs ***/
|
||||||
|
function checkLlmsFull(pkgs, issues) {
|
||||||
|
if (!fs.existsSync(LLMS_FULL)) return; // 卫语句:产物缺失不算漂移(可能是新仓库)
|
||||||
|
const tokensPath = path.join(ROOT, 'packages', 'design', 'tokens', 'tokens.json');
|
||||||
|
const tokens = fs.existsSync(tokensPath) ? JSON.parse(fs.readFileSync(tokensPath, 'utf8')) : null;
|
||||||
|
const norm = (s) => s.replace(/\r\n/g, '\n').replace(/自动生成于 [^,]*/, '自动生成于 X');
|
||||||
|
const expected = renderAgentDocs({ generatedAt: '', packages: pkgs }, tokens);
|
||||||
|
if (norm(fs.readFileSync(LLMS_FULL, 'utf8')) !== norm(expected)) {
|
||||||
|
issues.push('llms-full.txt 与清单不一致,请运行 npm run docs 重新生成');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
||||||
function cmdVerify() {
|
function cmdVerify() {
|
||||||
const issues = [];
|
const issues = [];
|
||||||
@@ -628,7 +670,12 @@ function cmdVerify() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const skinTotal = pkgs // skin-dom 清单组件总数,供文档数字漂移校验
|
||||||
|
.filter((p) => p.type === 'skin-dom')
|
||||||
|
.flatMap((p) => p.docs?.components ?? []).length;
|
||||||
|
issues.push(...checkCountDocs(skinTotal));
|
||||||
issues.push(...checkReadme(pkgs));
|
issues.push(...checkReadme(pkgs));
|
||||||
|
checkLlmsFull(pkgs, issues);
|
||||||
|
|
||||||
if (issues.length) {
|
if (issues.length) {
|
||||||
for (const it of issues) console.error('verify: ' + it);
|
for (const it of issues) console.error('verify: ' + it);
|
||||||
@@ -659,7 +706,8 @@ function cmdCommitLint(args) {
|
|||||||
if (!TYPES.includes(type)) problems.push(`type「${type}」不在枚举:${TYPES.join('/')}`);
|
if (!TYPES.includes(type)) problems.push(`type「${type}」不在枚举:${TYPES.join('/')}`);
|
||||||
// iter 提交 scope 必须是 backlog 条目 id
|
// iter 提交 scope 必须是 backlog 条目 id
|
||||||
if (type === 'iter' && !/^[a-z]\d{2,}$/.test(scope ?? '')) problems.push('iter 提交的 scope 必须是 backlog 条目 id(如 b02/e03)');
|
if (type === 'iter' && !/^[a-z]\d{2,}$/.test(scope ?? '')) problems.push('iter 提交的 scope 必须是 backlog 条目 id(如 b02/e03)');
|
||||||
// 非 iter 提交 scope 仅允许小写字母与连字符
|
// 非 iter 提交 scope 必填(杜绝 chore:/docs: 裸 type 放行),且仅允许小写字母与连字符
|
||||||
|
if (type !== 'iter' && !scope) problems.push(`type「${type}」必须带 scope,如 chore(backlog):`);
|
||||||
if (type !== 'iter' && scope && !/^[a-z][a-z-]*$/.test(scope)) problems.push(`scope「${scope}」仅允许小写字母与连字符`);
|
if (type !== 'iter' && scope && !/^[a-z][a-z-]*$/.test(scope)) problems.push(`scope「${scope}」仅允许小写字母与连字符`);
|
||||||
// subject 长度与标点
|
// subject 长度与标点
|
||||||
if ([...subject].length > 50) problems.push(`subject 超过 50 字符(当前 ${[...subject].length})`);
|
if ([...subject].length > 50) problems.push(`subject 超过 50 字符(当前 ${[...subject].length})`);
|
||||||
@@ -672,6 +720,38 @@ function cmdCommitLint(args) {
|
|||||||
console.log(`commit-lint OK (${type}${scope ? `(${scope})` : ''})`);
|
console.log(`commit-lint OK (${type}${scope ? `(${scope})` : ''})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 迭代留痕校验:.ua/iterations.log 每行 JSON 字段与格式必须符合 ITERATION.md 规范 ***/
|
||||||
|
function cmdLintIterations() {
|
||||||
|
const file = path.join(INDEX_DIR, 'iterations.log');
|
||||||
|
if (!fs.existsSync(file)) return console.log('lint-iterations OK (no log)');
|
||||||
|
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter((l) => l.trim());
|
||||||
|
const issues = [];
|
||||||
|
lines.forEach((line, i) => {
|
||||||
|
const tag = `iterations.log:${i + 1}`;
|
||||||
|
let o;
|
||||||
|
try { o = JSON.parse(line); } catch { issues.push(`${tag} 不是合法 JSON`); return; }
|
||||||
|
const keys = Object.keys(o);
|
||||||
|
if (keys.length !== 5 || !['ts', 'id', 'result', 'commit', 'summary'].every((k) => keys.includes(k))) {
|
||||||
|
issues.push(`${tag} 字段必须恰为 {ts,id,result,commit,summary}(当前 ${keys.join(',')})`);
|
||||||
|
}
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+08:00$/.test(String(o.ts ?? '')) || Number.isNaN(Date.parse(o.ts))) {
|
||||||
|
issues.push(`${tag} ts 须为可解析的本地 +08:00 ISO 时间`);
|
||||||
|
}
|
||||||
|
if (!/^[a-z]\d{2,}$/.test(String(o.id ?? ''))) issues.push(`${tag} id 须为 backlog 条目 id`);
|
||||||
|
if (o.result !== 'done' && o.result !== 'aborted') issues.push(`${tag} result 须为 done|aborted`);
|
||||||
|
const commit = String(o.commit ?? '');
|
||||||
|
if (o.result === 'done' && !/^[0-9a-f]{7,40}$/.test(commit)) issues.push(`${tag} done 条目 commit 须为 7-40 位哈希`);
|
||||||
|
if (o.result === 'aborted' && commit && !/^[0-9a-f]{7,40}$/.test(commit)) issues.push(`${tag} aborted 条目 commit 须为空或哈希`);
|
||||||
|
if (!String(o.summary ?? '').trim()) issues.push(`${tag} summary 不能为空`);
|
||||||
|
});
|
||||||
|
if (issues.length) {
|
||||||
|
for (const it of issues) console.error('lint-iterations: ' + it);
|
||||||
|
console.error(`${issues.length} violation(s)`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`lint-iterations OK (${lines.length} entries)`);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 入口 ----
|
// ---- 入口 ----
|
||||||
|
|
||||||
const [cmd, ...args] = process.argv.slice(2);
|
const [cmd, ...args] = process.argv.slice(2);
|
||||||
@@ -701,10 +781,13 @@ switch (cmd) {
|
|||||||
case 'commit-lint':
|
case 'commit-lint':
|
||||||
cmdCommitLint(args);
|
cmdCommitLint(args);
|
||||||
break;
|
break;
|
||||||
|
case 'lint-iterations':
|
||||||
|
cmdLintIterations();
|
||||||
|
break;
|
||||||
case 'mcp':
|
case 'mcp':
|
||||||
cmdMcp();
|
cmdMcp();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('usage: ua <index|list|info <name>|docs|style|verify|commit-lint|mcp>');
|
console.log('usage: ua <index|list|info <name>|docs|style|verify|commit-lint|lint-iterations|mcp>');
|
||||||
process.exit(cmd ? 1 : 0);
|
process.exit(cmd ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+53
@@ -0,0 +1,53 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.6.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
apps/demo:
|
||||||
|
dependencies:
|
||||||
|
'@ui-arch/frame':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/core/frame
|
||||||
|
'@ui-arch/ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/skin/ui
|
||||||
|
|
||||||
|
packages/core/frame: {}
|
||||||
|
|
||||||
|
packages/core/table-core: {}
|
||||||
|
|
||||||
|
packages/design/tokens: {}
|
||||||
|
|
||||||
|
packages/skin/ui:
|
||||||
|
dependencies:
|
||||||
|
'@ui-arch/frame':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../core/frame
|
||||||
|
'@ui-arch/table-core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../core/table-core
|
||||||
|
'@ui-arch/tokens':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../design/tokens
|
||||||
|
|
||||||
|
packages/tools/cli: {}
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
Reference in New Issue
Block a user