Compare commits

5 Commits

Author SHA1 Message Date
HIsheR ece38bcac9 iter: b02 新增 toast 消息提示组件(四语义工厂 + 右上角堆叠 + 3s 自动消失) 2026-10-23 15:50:20 +08:00
HIsheR aaf6fdc5ac iter: 协议升级为连续迭代+心跳监护模式 2026-10-23 15:40:51 +08:00
HIsheR a57577a995 iter: b01 新增 tooltip 悬浮提示组件
- packages/skin/ui/src/tooltip.ts:包裹式 API tooltip(props, trigger),hover/focus 150ms 延迟出场,移出/失焦/Esc 收起
- types.ts 增 TooltipProps;index.ts 导出;ui.css 增气泡样式(纯 --ua-* 令牌 + 出入场过渡 + reduced-motion 兜底)
- ua.manifest.json / llms-full.txt / .ua/index.json / llms.txt 同步;demo 增 sec-tooltip 区块
- backlog b01 置 done,iterations.log 留痕
2026-10-23 15:34:23 +08:00
HIsheR e9165ead18 iter: b01 前置修复——style 检查先剥离字面量,消除 URL 中 // 误判 2026-10-23 15:32:53 +08:00
HIsheR 17a4fb171b iter: 新增 Gitea REST API 包装脚本 2026-10-23 15:27:02 +08:00
18 changed files with 459 additions and 18 deletions
+2
View File
@@ -5,3 +5,5 @@ dist/
apps/docs/*.html apps/docs/*.html
.workbuddy/ .workbuddy/
.gitea-token .gitea-token
.ua/loop.lock
.ua/heartbeat*
+4 -4
View File
@@ -6,16 +6,16 @@
"title": "新增 tooltip 组件", "title": "新增 tooltip 组件",
"motivation": "皮肤包 16 组件缺少悬浮提示,表单与工具栏场景常用;可复用自绘弹层与微动画既有模式", "motivation": "皮肤包 16 组件缺少悬浮提示,表单与工具栏场景常用;可复用自绘弹层与微动画既有模式",
"acceptance": "packages/skin/ui 新增 tooltip.ts:content/message 简单 API,hover/focus 触发,延迟约 150ms 出场,带出入场微动画与 prefers-reduced-motion 兜底,只消费 --ua-* 令牌;导出并注册进 ua.manifest.json,文档站 props 表与 llms-full.txt 同步更新;npm run check 全绿", "acceptance": "packages/skin/ui 新增 tooltip.ts:content/message 简单 API,hover/focus 触发,延迟约 150ms 出场,带出入场微动画与 prefers-reduced-motion 兜底,只消费 --ua-* 令牌;导出并注册进 ua.manifest.json,文档站 props 表与 llms-full.txt 同步更新;npm run check 全绿",
"status": "open", "status": "done",
"notes": "" "notes": "API 定为 tooltip(props, trigger) 包裹式;delay 默认 150ms,Esc 立即收起,气泡 pointer-events:none 防抖,aria-describedby + role=tooltip 保证读屏可读;demo 新增 sec-tooltip 区块。顺带修复 ua style 对 https:// 的误判(先剥离字面量再定位 //)"
}, },
{ {
"id": "b02", "id": "b02",
"title": "新增 toast 消息提示组件", "title": "新增 toast 消息提示组件",
"motivation": "alert 是常驻块级元素,缺少轻量转瞬即逝的全局反馈;操作成功/失败场景刚需", "motivation": "alert 是常驻块级元素,缺少轻量转瞬即逝的全局反馈;操作成功/失败场景刚需",
"acceptance": "新增 toast.ts:toast.success/error/warning/info 四个工厂函数,入参 {message, duration?},默认 3s 自动消失,右上角堆叠,语义色复用四档 variant,出入场微动画 + prefers-reduced-motion 兜底;导出、manifest、文档、llms-full.txt 同步;npm run check 全绿", "acceptance": "新增 toast.ts:toast.success/error/warning/info 四个工厂函数,入参 {message, duration?},默认 3s 自动消失,右上角堆叠,语义色复用四档 variant,出入场微动画 + prefers-reduced-motion 兜底;导出、manifest、文档、llms-full.txt 同步;npm run check 全绿",
"status": "open", "status": "done",
"notes": "" "notes": "对外暴露 toast.info/success/warning/error(message, duration?) 四工厂 + showToast(props) 完整入参版;全局堆叠容器懒创建,role=status 无障碍;退场用 transitionend 移除并带 320ms 兜底(覆盖 reduced-motion 下 transition 不触发的情况);样式复用 alert 语义色 color-mix 模式,reduced-motion 列表已补 .ua-toast;demo 新增 sec-toast 区块"
}, },
{ {
"id": "b03", "id": "b03",
+75 -3
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "ui-arch-registry-index/0.1", "$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-23T06:57:51.085Z", "generatedAt": "2026-10-23T07:49:42.422Z",
"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 / checkbox / switch / select / radioGroup / card / badge / alert / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"tags": [ "tags": [
"components", "components",
"dom", "dom",
@@ -152,6 +152,8 @@
"card(props, ...children):卡片容器", "card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger", "badge(props):徽标,variant = default | success | warning | danger",
"alert(props):提示条,variant = info | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger",
"toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失,出入场微动画;showToast(props) 为完整入参版本",
"tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起",
"spinner(props):加载指示器,size 控制直径", "spinner(props):加载指示器,size 控制直径",
"progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色", "progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色",
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
@@ -172,6 +174,8 @@
"CardProps", "CardProps",
"BadgeProps", "BadgeProps",
"AlertProps", "AlertProps",
"ToastProps",
"TooltipProps",
"SpinnerProps", "SpinnerProps",
"ProgressProps", "ProgressProps",
"TabsProps", "TabsProps",
@@ -190,7 +194,7 @@
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
], ],
"docs": { "docs": {
"intro": "16 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", "intro": "18 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"steps": [ "steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)", "安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'", "引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
@@ -485,6 +489,74 @@
"传入 message 即可,入场自带下滑动画" "传入 message 即可,入场自带下滑动画"
] ]
}, },
{
"id": "toast",
"title": "消息提示 Toast",
"demo": "sec-toast",
"props": [
{
"n": "message",
"t": "string",
"r": 1,
"d": "提示文案"
},
{
"n": "duration",
"t": "number",
"d": "自动消失毫秒;缺省 3000"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info(toast.error 对应 danger)"
}
],
"example": "toast.success('保存成功'); toast.error('网络请求失败', 5000)",
"steps": [
"直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器",
"不传 duration 时默认停留 3 秒后自动淡出并移除节点",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入"
]
},
{
"id": "tooltip",
"title": "悬浮提示 Tooltip",
"demo": "sec-tooltip",
"props": [
{
"n": "content",
"t": "string",
"r": 1,
"d": "提示文案(纯文本)"
},
{
"n": "placement",
"t": "'top' | 'bottom'",
"d": "气泡方位;缺省 top(触发器上方)"
},
{
"n": "delay",
"t": "number",
"d": "出场延迟毫秒;缺省 150,传 0 立即显示"
},
{
"n": "trigger",
"t": "HTMLElement",
"r": 1,
"d": "第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)"
}
],
"example": "tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))",
"steps": [
"先创建触发器元素,如 const btn = button({ label: '保存' })",
"用 tooltip(props, trigger) 包一层:返回带气泡的包裹元素,原触发器仍是独立节点",
"鼠标移入或键盘聚焦约 150ms 后出场,移出 / 失焦立即收起;按 Esc 可立刻关闭",
"气泡默认在上方,空间不足或需要避让时传 placement: 'bottom'",
"气泡 pointer-events: none 不抢指针;判读依赖 aria-describedby + role='tooltip',读屏可读",
"只做展示:不要在气泡里放可点击元素,需要交互请用 modal 或自绘弹层"
]
},
{ {
"id": "spinner", "id": "spinner",
"title": "加载指示器 Spinner", "title": "加载指示器 Spinner",
+2
View File
@@ -0,0 +1,2 @@
{"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 全绿"}
{"time":"2026-10-23T15:50:03+08:00","id":"b02","result":"done","summary":"新增 toast 消息提示组件:四语义工厂 + showToast,右上角堆叠,默认 3s 自动消失,微动画 + reduced-motion 兜底;manifest/docs/llms 同步,check 全绿"}
+1 -1
View File
@@ -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):16 个 DOM 组件,样式只消费令牌 - `packages/skin/ui`(@ui-arch/ui):17 个 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
+1 -1
View File
@@ -34,7 +34,7 @@ ui-arch-mvp/
│ │ ├── table-core/ # 表格状态内核 │ │ ├── table-core/ # 表格状态内核
│ │ └── frame/ # 信号 + hyperscript 微框架 │ │ └── frame/ # 信号 + hyperscript 微框架
│ ├── skin/ # 皮肤层(DOM 组件,可发布) │ ├── skin/ # 皮肤层(DOM 组件,可发布)
│ │ └── ui/ # 15 个组件 + ui.css │ │ └── ui/ # 17 个组件 + ui.css
│ ├── design/ # 资产层(可发布) │ ├── design/ # 资产层(可发布)
│ │ └── tokens/ # --ua-* 设计令牌 │ │ └── tokens/ # --ua-* 设计令牌
│ └── tools/ # 工程层(private,不发布) │ └── tools/ # 工程层(private,不发布)
+10 -3
View File
@@ -1,7 +1,16 @@
# 无人值守自我迭代协议(ITERATION.md) ## 无人值守自我迭代协议(ITERATION.md)
> 本协议由自动化 Agent 执行,也可供人工参考。任何迭代必须遵守 `CODING.md`、`AGENTS.md`、`ARCHITECTURE.md` 三份规范,门禁 `npm run check` 不通过的一切产出作废。 > 本协议由自动化 Agent 执行,也可供人工参考。任何迭代必须遵守 `CODING.md`、`AGENTS.md`、`ARCHITECTURE.md` 三份规范,门禁 `npm run check` 不通过的一切产出作废。
## 运行模式:连续迭代 + 心跳监护
调度器(每小时唤起)**不是**迭代器,只做两件事:健康检查 + 中断重启。真正的迭代是单次唤起内的**连续循环**——反复执行《每轮迭代流程》,直到 backlog 清空或时间预算耗尽,中间不停歇。
- **并发锁**:`.ua/loop.lock`(含时间戳,不入库)。唤起时若锁存在且心跳在 15 分钟内 → 已有实例在跑,直接退出;否则写锁开始循环。每轮开始前刷新锁内时间戳作心跳。
- **时间预算**:单次唤起最多连续迭代约 50 分钟,到点在完成当前轮后停止(防止与下次唤起重叠)。
- **熔断**:同一 backlog 条目连续 2 轮失败 → 停止循环,锁照常释放,等待下次唤起。
- **重启语义**:锁心跳超过 15 分钟视为循环中断(崩溃/超时),调度器删旧锁、重新点火。
## 分支流水线 ## 分支流水线
``` ```
@@ -16,8 +25,6 @@ qa/probe ──门禁通过──▶ qa/stable ──人工审阅──▶ main
## 每轮迭代流程(自动化 Agent 执行) ## 每轮迭代流程(自动化 Agent 执行)
> 所有远程推送一律使用认证包装脚本:`node scripts/gitea.mjs push <remote> <分支>`(令牌读自本地 `.gitea-token`,不入库;凭据管理器不可用)。
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。
+27
View File
@@ -17,6 +17,8 @@ import {
tabs, tabs,
textarea, textarea,
textField, textField,
toast,
tooltip,
} from '@ui-arch/ui'; } from '@ui-arch/ui';
/*** 生成演示区块;id 供文档站 iframe 锚点定位 ***/ /*** 生成演示区块;id 供文档站 iframe 锚点定位 ***/
@@ -112,6 +114,31 @@ const app = h(
h('div', { class: 'row' }, spinner(), spinner({ size: 26 })), h('div', { class: 'row' }, spinner(), spinner({ size: 26 })),
progress({ value: percent }), progress({ value: percent }),
), ),
section(
'sec-tooltip',
'悬浮提示',
'tooltip 包裹触发器:hover / 聚焦延迟 150ms 出场,移出、失焦或 Esc 收起,气泡不抢指针。',
h(
'div',
{ class: 'row' },
tooltip({ content: '保存后立即生效' }, button({ label: '保存' })),
tooltip({ content: '该操作不可撤销', placement: 'bottom' }, button({ label: '删除', variant: 'danger' })),
tooltip({ content: '聚焦输入框同样会显示' }, textField({ placeholder: '聚焦我…' })),
),
),
section(
'sec-toast',
'消息提示',
'toast 右上角堆叠弹出,默认 3s 自动淡出移除;语义色四档,出入场微动画。',
h(
'div',
{ class: 'row' },
button({ label: '成功', onClick: () => toast.success('保存成功') }),
button({ label: '错误', variant: 'danger', onClick: () => toast.error('网络请求失败') }),
button({ label: '警告', onClick: () => toast.warning('磁盘空间不足') }),
button({ label: '信息(1.5s)', onClick: () => toast.info('已同步云端', 1500) }),
),
),
section( section(
'sec-formlab', 'sec-formlab',
'表单校验', '表单校验',
+53 -2
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版) # @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-23T06:57:51.085Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 自动生成于 2026-10-23T07:49:42.422Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手 ## 快速上手
@@ -40,7 +40,7 @@
## @ui-arch/ui(skin-dom)v0.1.0 ## @ui-arch/ui(skin-dom)v0.1.0
16 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 18 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。
### button — 按钮 Button ### button — 按钮 Button
@@ -260,6 +260,55 @@ alert({ message: '操作可能存在风险', variant: 'warning' })
现场演示:apps/docs/demo.html#sec-feedback 现场演示:apps/docs/demo.html#sec-feedback
### toast — 消息提示 Toast
参数:
- message(必填):string —— 提示文案
- duration:number —— 自动消失毫秒;缺省 3000
- variant:'info' | 'success' | 'warning' | 'danger' —— 语义色;缺省 info(toast.error 对应 danger)
示例:
```
toast.success('保存成功'); toast.error('网络请求失败', 5000)
```
手把手:
1. 直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器
2. 不传 duration 时默认停留 3 秒后自动淡出并移除节点
3. 多条消息在右上角自动纵向堆叠,互不遮挡
4. 需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入
现场演示:apps/docs/demo.html#sec-toast
### tooltip — 悬浮提示 Tooltip
参数:
- content(必填):string —— 提示文案(纯文本)
- placement:'top' | 'bottom' —— 气泡方位;缺省 top(触发器上方)
- delay:number —— 出场延迟毫秒;缺省 150,传 0 立即显示
- trigger(必填):HTMLElement —— 第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)
示例:
```
tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))
```
手把手:
1. 先创建触发器元素,如 const btn = button({ label: '保存' })
2. 用 tooltip(props, trigger) 包一层:返回带气泡的包裹元素,原触发器仍是独立节点
3. 鼠标移入或键盘聚焦约 150ms 后出场,移出 / 失焦立即收起;按 Esc 可立刻关闭
4. 气泡默认在上方,空间不足或需要避让时传 placement: 'bottom'
5. 气泡 pointer-events: none 不抢指针;判读依赖 aria-describedby + role='tooltip',读屏可读
6. 只做展示:不要在气泡里放可点击元素,需要交互请用 modal 或自绘弹层
现场演示:apps/docs/demo.html#sec-tooltip
### spinner — 加载指示器 Spinner ### spinner — 加载指示器 Spinner
参数: 参数:
@@ -418,6 +467,8 @@ API 速览:
- card(props, ...children):卡片容器 - card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger - badge(props):徽标,variant = default | success | warning | danger
- alert(props):提示条,variant = info | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger
- toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失,出入场微动画;showToast(props) 为完整入参版本
- tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起
- spinner(props):加载指示器,size 控制直径 - spinner(props):加载指示器,size 控制直径
- progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色 - progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
+3 -1
View File
@@ -45,7 +45,7 @@
## @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 / checkbox / switch / select / radioGroup / card / badge / alert / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。 - 描述: DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。
- API: - API:
- button(props):按钮,variant = primary | ghost | danger - button(props):按钮,variant = primary | ghost | danger
- textField(props):文本输入,value 信号双向绑定 - textField(props):文本输入,value 信号双向绑定
@@ -57,6 +57,8 @@
- card(props, ...children):卡片容器 - card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger - badge(props):徽标,variant = default | success | warning | danger
- alert(props):提示条,variant = info | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger
- toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失,出入场微动画;showToast(props) 为完整入参版本
- tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起
- spinner(props):加载指示器,size 控制直径 - spinner(props):加载指示器,size 控制直径
- progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色 - progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色
- tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签
+2
View File
@@ -9,6 +9,8 @@ export { radioGroup } from './radioGroup.js';
export { card } from './card.js'; export { card } from './card.js';
export { badge } from './badge.js'; export { badge } from './badge.js';
export { alert } from './alert.js'; export { alert } from './alert.js';
export { toast, showToast } from './toast.js';
export { tooltip } from './tooltip.js';
export { spinner } from './spinner.js'; export { spinner } from './spinner.js';
export { progress } from './progress.js'; export { progress } from './progress.js';
export { tabs } from './tabs.js'; export { tabs } from './tabs.js';
+37
View File
@@ -0,0 +1,37 @@
import { h } from '@ui-arch/frame';
import type { ToastProps } from './types.js';
let host: HTMLElement | null = null; // 全局右上角堆叠容器:首条消息出现时懒创建
let seq = 0; // 自增序号:为每条 toast 生成唯一 id,辅助无障碍定位
/*** 确保堆叠容器存在:fixed 挂在 body 末尾,后续消息依次向上堆叠 ***/
function ensureHost(): HTMLElement {
if (host) return host;
host = h('div', { class: 'ua-toast-host' });
document.body.appendChild(host);
return host;
}
/*** 弹出一条 toast:语义色由 variant 决定,duration 毫秒后自动淡出并移除节点 ***/
export function showToast(props: ToastProps): HTMLElement {
// —— 状态集中声明 ——
const item = h('div', { class: `ua-toast ua-toast-${props.variant ?? 'info'}`, role: 'status', id: `ua-toast-${++seq}` }, props.message);
const duration = props.duration ?? 3000; // 停留时长毫秒
ensureHost().appendChild(item);
requestAnimationFrame(() => item.classList.add('ua-open')); // 下一帧入场,保证过渡生效
window.setTimeout(() => {
item.classList.remove('ua-open'); // 先退场再摘除,退场动画与 reduced-motion 兜底都安全
item.addEventListener('transitionend', () => item.remove(), { once: true });
window.setTimeout(() => item.isConnected && item.remove(), 320); // transition 不触发时的兜底移除
}, duration);
return item;
}
/*** toast 语义快捷工厂:toast.info / success / warning / error(message, duration?) ***/
export const toast = {
info: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'info' }), // 中性信息
success: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'success' }), // 成功反馈
warning: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'warning' }), // 警告
error: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'danger' }), // 错误
};
+52
View File
@@ -0,0 +1,52 @@
import { h, signal } from '@ui-arch/frame';
import type { TooltipProps } from './types.js';
let seq = 0; // 全局自增序号:保证每个气泡 id 唯一,供 aria-describedby 关联
/*** 悬浮提示:包裹触发器,hover / focus 延迟出场,移出 / 失焦 / Esc 收起,纯展示不拦截指针 ***/
export function tooltip(props: TooltipProps, trigger: HTMLElement): HTMLElement {
// —— 状态集中声明 ——
const open = signal(false); // 气泡显隐
const id = `ua-tip-${++seq}`; // 气泡唯一 id
const delay = props.delay ?? 150; // 出场延迟毫秒;<=0 视为立即显示
let timer: number | null = null; // 出场延迟计时器句柄
const show = () => {
if (timer != null) window.clearTimeout(timer); // 重复进入先清旧计时
if (delay <= 0) return open(true);
timer = window.setTimeout(() => {
timer = null;
open(true);
}, delay);
};
const hide = () => {
if (timer != null) { // 还没出场就离开:直接取消计时,避免闪一下
window.clearTimeout(timer);
timer = null;
}
open(false);
};
// —— 触发器与气泡 ——
trigger.setAttribute('aria-describedby', id); // 无障碍:读屏把提示文案关联到触发器
const bubble = h(
'span',
{
id,
class: () => `ua-tip ua-tip-${props.placement ?? 'top'}${open() ? ' ua-open' : ''}`,
role: 'tooltip',
'aria-hidden': () => String(!open()),
},
props.content,
);
const root = h('span', { class: 'ua-tip-wrap' }, trigger, bubble);
root.addEventListener('pointerenter', show); // 鼠标进入
root.addEventListener('pointerleave', hide); // 鼠标离开
root.addEventListener('focusin', show); // 键盘聚焦(focusin 会冒泡)
root.addEventListener('focusout', hide); // 失焦
root.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') hide(); // Esc 立即收起,不必等延迟
});
return root;
}
+14
View File
@@ -1,6 +1,13 @@
import type { ColumnDef, RowKey } from '@ui-arch/table-core'; import type { ColumnDef, RowKey } from '@ui-arch/table-core';
import type { Signal } from '@ui-arch/frame'; import type { Signal } from '@ui-arch/frame';
/*** 悬浮提示入参 ***/
export interface TooltipProps {
content: string; // 提示文案(纯文本)
placement?: 'top' | 'bottom'; // 气泡方位;缺省 top(触发器上方)
delay?: number; // 出场延迟毫秒;缺省 150,传 0 立即显示
}
/*** 按钮入参 ***/ /*** 按钮入参 ***/
export interface ButtonProps { export interface ButtonProps {
label: string; // 按钮文案 label: string; // 按钮文案
@@ -85,6 +92,13 @@ export interface AlertProps {
variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info
} }
/*** toast 消息提示入参 ***/
export interface ToastProps {
message: string; // 提示文案
duration?: number; // 自动消失毫秒;缺省 3000
variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info
}
/*** 加载指示器入参 ***/ /*** 加载指示器入参 ***/
export interface SpinnerProps { export interface SpinnerProps {
size?: number; // 直径像素;缺省 18 size?: number; // 直径像素;缺省 18
+74 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "@ui-arch/ui", "name": "@ui-arch/ui",
"type": "skin-dom", "type": "skin-dom",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"tags": [ "tags": [
"components", "components",
"dom", "dom",
@@ -23,6 +23,8 @@
"card(props, ...children):卡片容器", "card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger", "badge(props):徽标,variant = default | success | warning | danger",
"alert(props):提示条,variant = info | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger",
"toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失,出入场微动画;showToast(props) 为完整入参版本",
"tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起",
"spinner(props):加载指示器,size 控制直径", "spinner(props):加载指示器,size 控制直径",
"progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色", "progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色",
"tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签",
@@ -43,6 +45,8 @@
"CardProps", "CardProps",
"BadgeProps", "BadgeProps",
"AlertProps", "AlertProps",
"ToastProps",
"TooltipProps",
"SpinnerProps", "SpinnerProps",
"ProgressProps", "ProgressProps",
"TabsProps", "TabsProps",
@@ -61,7 +65,7 @@
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
], ],
"docs": { "docs": {
"intro": "16 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", "intro": "18 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"steps": [ "steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)", "安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'", "引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
@@ -356,6 +360,74 @@
"传入 message 即可,入场自带下滑动画" "传入 message 即可,入场自带下滑动画"
] ]
}, },
{
"id": "toast",
"title": "消息提示 Toast",
"demo": "sec-toast",
"props": [
{
"n": "message",
"t": "string",
"r": 1,
"d": "提示文案"
},
{
"n": "duration",
"t": "number",
"d": "自动消失毫秒;缺省 3000"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info(toast.error 对应 danger)"
}
],
"example": "toast.success('保存成功'); toast.error('网络请求失败', 5000)",
"steps": [
"直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器",
"不传 duration 时默认停留 3 秒后自动淡出并移除节点",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入"
]
},
{
"id": "tooltip",
"title": "悬浮提示 Tooltip",
"demo": "sec-tooltip",
"props": [
{
"n": "content",
"t": "string",
"r": 1,
"d": "提示文案(纯文本)"
},
{
"n": "placement",
"t": "'top' | 'bottom'",
"d": "气泡方位;缺省 top(触发器上方)"
},
{
"n": "delay",
"t": "number",
"d": "出场延迟毫秒;缺省 150,传 0 立即显示"
},
{
"n": "trigger",
"t": "HTMLElement",
"r": 1,
"d": "第二个参数:被包裹的触发器元素(按钮 / 输入框等任意 DOM)"
}
],
"example": "tooltip({ content: '保存后立即生效' }, button({ label: '保存', onClick: save }))",
"steps": [
"先创建触发器元素,如 const btn = button({ label: '保存' })",
"用 tooltip(props, trigger) 包一层:返回带气泡的包裹元素,原触发器仍是独立节点",
"鼠标移入或键盘聚焦约 150ms 后出场,移出 / 失焦立即收起;按 Esc 可立刻关闭",
"气泡默认在上方,空间不足或需要避让时传 placement: 'bottom'",
"气泡 pointer-events: none 不抢指针;判读依赖 aria-describedby + role='tooltip',读屏可读",
"只做展示:不要在气泡里放可点击元素,需要交互请用 modal 或自绘弹层"
]
},
{ {
"id": "spinner", "id": "spinner",
"title": "加载指示器 Spinner", "title": "加载指示器 Spinner",
+77
View File
@@ -379,6 +379,48 @@
background: color-mix(in srgb, var(--ua-color-danger) 12%, transparent); background: color-mix(in srgb, var(--ua-color-danger) 12%, transparent);
} }
/* —— toast 消息提示:右上角堆叠,转瞬即逝 —— */
.ua-toast-host {
position: fixed;
top: var(--ua-space-4);
right: var(--ua-space-4);
z-index: 50;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--ua-space-2);
pointer-events: none;
}
.ua-toast {
padding: var(--ua-space-2) var(--ua-space-3);
border-radius: var(--ua-radius-md);
border: 1px solid;
font-size: var(--ua-font-size-md);
opacity: 0;
transform: translateX(12px);
transition: opacity 0.22s ease-out, transform 0.22s ease-out;
}
.ua-toast.ua-open {
opacity: 1;
transform: translateX(0);
}
.ua-toast-info {
border-color: var(--ua-color-accent);
background: color-mix(in srgb, var(--ua-color-accent) 8%, var(--ua-color-bg));
}
.ua-toast-success {
border-color: var(--ua-color-success);
background: color-mix(in srgb, var(--ua-color-success) 10%, var(--ua-color-bg));
}
.ua-toast-warning {
border-color: var(--ua-color-warning);
background: color-mix(in srgb, var(--ua-color-warning) 10%, var(--ua-color-bg));
}
.ua-toast-danger {
border-color: var(--ua-color-danger);
background: color-mix(in srgb, var(--ua-color-danger) 10%, var(--ua-color-bg));
}
/* —— 加载指示器 —— */ /* —— 加载指示器 —— */
.ua-spinner { .ua-spinner {
display: inline-block; display: inline-block;
@@ -613,6 +655,39 @@
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 18%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 18%, transparent);
} }
/* —— 悬浮提示 —— */
.ua-tip-wrap {
position: relative;
display: inline-flex;
}
.ua-tip {
position: absolute;
left: 50%;
z-index: 30;
max-width: 220px;
padding: var(--ua-space-1) var(--ua-space-2);
border-radius: var(--ua-radius-sm);
background: var(--ua-color-text);
color: var(--ua-color-bg);
font-size: var(--ua-font-size-sm);
line-height: 1.5;
pointer-events: none;
opacity: 0;
transition: opacity 0.16s ease-out, transform 0.16s ease-out;
}
.ua-tip-top {
bottom: calc(100% + 6px);
transform: translateX(-50%) translateY(3px);
}
.ua-tip-bottom {
top: calc(100% + 6px);
transform: translateX(-50%) translateY(-3px);
}
.ua-tip.ua-open {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* —— 无障碍:用户偏好减少动效时关闭全部动画 —— */ /* —— 无障碍:用户偏好减少动效时关闭全部动画 —— */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.ua-btn, .ua-btn,
@@ -630,6 +705,8 @@
.ua-progress-fill, .ua-progress-fill,
.ua-progress-fill::after, .ua-progress-fill::after,
.ua-alert, .ua-alert,
.ua-tip,
.ua-toast,
.ua-form-error, .ua-form-error,
.ua-modal, .ua-modal,
.ua-modal-overlay { .ua-modal-overlay {
+6 -1
View File
@@ -491,6 +491,11 @@ function collectSourceFiles() {
return out; return out;
} }
/*** 把字符串 / 模板字面量整体替换为等长空格,便于按位置定位真实注释(避开 URL、正则里的 //) ***/
function blankStrings(line) {
return line.replace(/(['"`])(?:\\.|(?!\1)[^\\])*\1/g, (m) => ' '.repeat(m.length));
}
/*** 检查单文件:导出缺注释块、接口字段缺行尾注释、行注释非中文 ***/ /*** 检查单文件:导出缺注释块、接口字段缺行尾注释、行注释非中文 ***/
function styleCheckFile(file, src) { function styleCheckFile(file, src) {
const issues = []; const issues = [];
@@ -528,7 +533,7 @@ function styleCheckFile(file, src) {
} }
} }
const hash = raw.indexOf('//'); const hash = blankStrings(raw).indexOf('//'); // 注释定位:先剥离字面量,'https://' 不算注释
if (hash >= 0) { if (hash >= 0) {
const text = raw.slice(hash + 2); const text = raw.slice(hash + 2);
const isPragma = /^[\s*/]*(eslint|@ts-|prettier-ignore|istanbul|c8|node:)/i.test(text); const isPragma = /^[\s*/]*(eslint|@ts-|prettier-ignore|istanbul|c8|node:)/i.test(text);
+19
View File
@@ -0,0 +1,19 @@
/*** 作用:Gitea REST API 调用包装器,用法 node scripts/gitea-api.mjs <METHOD> <path不带前导斜杠> [jsonBody],令牌读自 .gitea-token ***/
import { readFileSync } from 'node:fs';
// 仓库与令牌
const base = 'https://gitea.yinyuanjingling.icu/api/v1';
const token = readFileSync(new URL('../.gitea-token', import.meta.url), 'utf8').trim();
const [method = 'GET', path = '', body = ''] = process.argv.slice(2);
// 卫语句:缺参数退出
if (!method || !path) { console.error('用法: node scripts/gitea-api.mjs <METHOD> <path> [jsonBody]'); process.exit(2); }
// 发起请求并输出结果(path 不带前导斜杠,规避 Git Bash MSYS 路径转换篡改)
const res = await fetch(`${base}/${path.replace(/^\//, '')}`, {
method: method.toUpperCase(),
headers: { 'Authorization': `token ${token}`, 'Content-Type': 'application/json' },
body: body || undefined,
});
const text = await res.text();
console.log(`HTTP ${res.status}\n${text}`);
process.exit(res.ok ? 0 : 1);