iter(g07): 新增 timeline 时间线组件

This commit is contained in:
HIsheR
2026-10-26 10:17:38 +08:00
parent 511a3901a7
commit db96a30439
12 changed files with 278 additions and 12 deletions
+40 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-23T11:03:12.689Z",
"generatedAt": "2026-10-26T02:16:50.161Z",
"repoRoot": ".",
"packages": [
{
@@ -176,7 +176,8 @@
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core",
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽"
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点"
],
"keyTypes": [
"ButtonProps",
@@ -213,7 +214,9 @@
"SliderProps",
"TagProps",
"DrawerProps",
"AvatarProps"
"AvatarProps",
"TimelineItem",
"TimelineProps"
]
},
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
@@ -1579,6 +1582,40 @@
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)"
]
},
{
"id": "timeline",
"title": "时间线 Timeline",
"demo": "sec-timeline",
"props": [
{
"n": "items",
"t": "TimelineItem[]",
"r": 1,
"d": "条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant"
},
{
"n": "mode",
"t": "'left' | 'right'",
"d": "轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右",
"df": "left"
},
{
"n": "pending",
"t": "boolean",
"d": "末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false",
"df": "false"
}
],
"example": "timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })",
"steps": [
"事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后",
"variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)",
"time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析",
"流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失",
"需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像",
"content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合"
]
}
]
},
+1 -1
View File
@@ -21,7 +21,7 @@
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
- `packages/skin/ui`(@ui-arch/ui):34 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
- `packages/skin/ui`(@ui-arch/ui):35 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
+1 -1
View File
@@ -34,7 +34,7 @@ ui-arch-mvp/
│ │ ├── table-core/ # 表格状态内核
│ │ └── frame/ # 信号 + hyperscript 微框架
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
│ │ └── ui/ # 34 个组件 + ui.css
│ │ └── ui/ # 35 个组件 + ui.css
│ ├── design/ # 资产层(可发布)
│ │ └── tokens/ # --ua-* 设计令牌
│ └── tools/ # 工程层(private,不发布)
+4 -4
View File
@@ -11,13 +11,13 @@ npm run demo # 打开演示:apps/demo/dist/demo.html
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
```
## 组件(共 34 个)
## 组件(共 35 个)
<!-- ua:components -->
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
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 · timeline · toast · tooltip
<!-- /ua:components -->
分组:表单(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)。
分组:表单(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 / timeline)。
## 目录
@@ -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/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
| `packages/skin/ui` | @ui-arch/ui:34 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
| `packages/skin/ui` | @ui-arch/ui:35 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
+22
View File
@@ -42,6 +42,7 @@ import {
tag,
textarea,
textField,
timeline,
toast,
tooltip,
} from '@ui-arch/ui';
@@ -151,6 +152,9 @@ const menuPick = signal('');
// —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled ——
const locked = signal(true);
// —— 时间线:false 时末尾显示「进行中」虚线节点,true 表示流程已结束 ——
const flowDone = signal(false);
const content = h(
'div',
{ class: 'page' },
@@ -440,6 +444,24 @@ const content = h(
button({ label: '导出', variant: 'ghost' }),
),
),
section(
'sec-timeline',
'时间线',
'timeline:items 自左向右排事件流,variant 给节点语义色;pending 追加虚线「进行中」节点,点按钮切换。',
h(
'div',
{ class: 'row' },
button({ label: '流程继续/结束', variant: 'ghost', onClick: () => flowDone(!flowDone()) }),
),
() => timeline({
items: [
{ title: '提交审批', time: '09:12', content: '王小明提交了报销单 RB-2048', variant: 'success' },
{ title: '财务复核', time: '10:30', content: '金额与发票一致,待张工确认', variant: 'warning' },
{ title: '打款失败', time: '14:05', content: '收款账户信息有误,已退回修改', variant: 'danger' },
],
pending: !flowDone(),
}),
),
section(
'sec-menu',
'下拉菜单',
+27 -1
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-23T11:03:12.689Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T02:16:50.161Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -898,6 +898,31 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓
现场演示:apps/docs/demo.html#sec-table
### timeline — 时间线 Timeline
参数:
- items(必填):TimelineItem[] —— 条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant
- mode(默认 left):'left' | 'right' —— 轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右
- pending(默认 false):boolean —— 末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false
示例:
```
timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })
```
手把手:
1. 事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后
2. variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)
3. time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析
4. 流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失
5. 需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像
6. content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合
现场演示:apps/docs/demo.html#sec-timeline
API 速览:
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
@@ -935,6 +960,7 @@ API 速览:
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
一行用法:
+1
View File
@@ -82,6 +82,7 @@
- formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄
- dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core
- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
+1
View File
@@ -29,6 +29,7 @@ export { stepper } from './stepper.js';
export { rate } from './rate.js';
export { empty } from './empty.js';
export { divider } from './divider.js';
export { timeline } from './timeline.js';
export { modal } from './modal.js';
export { drawer } from './drawer.js';
export { form, formItem } from './form.js';
+20
View File
@@ -0,0 +1,20 @@
import { h } from '@ui-arch/frame';
import type { TimelineItem, TimelineProps } from './types.js';
/*** 单个时间线节点:绝对定位的语义色圆点 + 标题 / 时间 / 正文,pending 节点为虚线空心圈 ***/
function node(item: TimelineItem, pending: boolean): HTMLElement {
const dot = h('span', { class: `ua-tl-dot ua-tl-dot-${pending ? 'pending' : item.variant ?? 'default'}` });
const head = h('div', { class: 'ua-tl-head' },
h('span', { class: 'ua-tl-title' }, item.title),
item.time ? h('span', { class: 'ua-tl-time' }, item.time) : null);
const body = item.content ? h('p', { class: 'ua-tl-content' }, item.content) : null;
return h('li', { class: `ua-tl-item${pending ? ' is-pending' : ''}` }, dot, h('div', { class: 'ua-tl-body' }, head, body));
}
/*** 时间线:纵向事件流,mode 决定轴线在左还是在右;pending 为真时末尾补一个进行中节点 ***/
export function timeline(props: TimelineProps): HTMLElement {
const rows: TimelineItem[] = [...props.items]; // 复制入参,避免改动调用方数组
if (props.pending) rows.push({ title: '进行中…' }); // 尾部虚节点表示流程尚未结束
const items = rows.map((row, i) => node(row, !!props.pending && i === rows.length - 1));
return h('ol', { class: `ua-tl ua-tl-${props.mode ?? 'left'}` }, ...items);
}
+15
View File
@@ -348,4 +348,19 @@ export interface FormProps {
onReset?: () => void; // 重置回调
}
/*** 时间线条目 ***/
export interface TimelineItem {
title: string; // 条目标题(事件名)
content?: string; // 条目正文补充;缺省不渲染
time?: string; // 时间文案(如 '09:30' / '10-23');缺省不渲染
variant?: 'default' | 'success' | 'warning' | 'danger'; // 节点语义色;缺省 default
}
/*** 时间线入参 ***/
export interface TimelineProps {
items: TimelineItem[]; // 条目列表,按时间顺序自上而下
mode?: 'left' | 'right'; // 轴线与节点所在侧;缺省 left(内容在右)
pending?: boolean; // 末尾追加「进行中」虚线节点,表示流程未结束;缺省 false
}
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方
+39 -2
View File
@@ -47,7 +47,8 @@
"form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit",
"formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄",
"dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core",
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽"
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点"
],
"keyTypes": [
"ButtonProps",
@@ -84,7 +85,9 @@
"SliderProps",
"TagProps",
"DrawerProps",
"AvatarProps"
"AvatarProps",
"TimelineItem",
"TimelineProps"
]
},
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
@@ -1450,6 +1453,40 @@
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)"
]
},
{
"id": "timeline",
"title": "时间线 Timeline",
"demo": "sec-timeline",
"props": [
{
"n": "items",
"t": "TimelineItem[]",
"r": 1,
"d": "条目列表,按时间顺序自上而下;每项含 title(必填)、content、time、variant"
},
{
"n": "mode",
"t": "'left' | 'right'",
"d": "轴线与节点所在侧;缺省 left(内容在右),right 时内容左对齐轴线居右",
"df": "left"
},
{
"n": "pending",
"t": "boolean",
"d": "末尾追加虚线「进行中…」节点,表示流程尚未结束;缺省 false",
"df": "false"
}
],
"example": "timeline({ items: [{ title: '提交审批', time: '09:12', variant: 'success' }, { title: '财务复核', time: '10:30', content: '等待张工处理' }], pending: true })",
"steps": [
"事件流按时间顺序排(组件不做排序):较早的条目放前面,最新的放最后",
"variant 只承载语义:success 完成、warning 待处理、danger 失败、default 普通记录(节点色走 --ua-* 令牌,随暗色主题变化)",
"time 是纯展示文案,格式自由('09:12' / '10-23 18:00'),组件不做日期解析",
"流程未结束时传 pending: true,末尾自动补一个虚线脉冲节点,完成后置回 false 即消失",
"需要轴线居右(如右侧栏时间流)时传 mode: 'right',条目与节点整体镜像",
"content 为纯文本;需要按钮等交互内容时改用 accordion / card 组合"
]
}
]
}
+107
View File
@@ -1647,3 +1647,110 @@ button.ua-step-dot:hover {
font-size: var(--ua-font-size-md);
user-select: none;
}
/* —— 时间线:纵向轴线 + 语义色节点,mode=right 时轴线居右 —— */
.ua-tl {
position: relative;
margin: 0;
padding: 0;
list-style: none;
}
.ua-tl::before {
content: '';
position: absolute;
left: 5px;
top: 6px;
bottom: 0;
width: 1px;
background: var(--ua-color-border);
}
.ua-tl-right::before {
left: auto;
right: 5px;
}
.ua-tl-item {
position: relative;
padding: 0 0 var(--ua-space-3) 22px;
animation: ua-tl-in 0.18s ease-out both;
}
.ua-tl-item:last-child {
padding-bottom: 0;
}
.ua-tl-right .ua-tl-item {
padding: 0 22px var(--ua-space-3) 0;
text-align: right;
}
.ua-tl-right .ua-tl-item:last-child {
padding-bottom: 0;
}
.ua-tl-dot {
position: absolute;
left: 0;
top: 4px;
width: 11px;
height: 11px;
border-radius: 50%;
background: var(--ua-color-text-muted);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 10%, transparent);
transition: transform 0.15s;
}
.ua-tl-right .ua-tl-dot {
left: auto;
right: 0;
}
.ua-tl-item:hover .ua-tl-dot {
transform: scale(1.15);
}
.ua-tl-dot-default { background: var(--ua-color-accent); }
.ua-tl-dot-success { background: var(--ua-color-success); }
.ua-tl-dot-warning { background: var(--ua-color-warning); }
.ua-tl-dot-danger { background: var(--ua-color-danger); }
.ua-tl-dot-pending {
background: var(--ua-color-bg);
border: 1px dashed var(--ua-color-accent);
animation: ua-tl-pulse 1.6s ease-in-out infinite;
}
.ua-tl-head {
display: flex;
gap: var(--ua-space-2);
align-items: baseline;
}
.ua-tl-right .ua-tl-head {
justify-content: flex-end;
}
.ua-tl-title {
color: var(--ua-color-text);
font-size: var(--ua-font-size-md);
font-weight: 600;
}
.ua-tl-item.is-pending .ua-tl-title {
color: var(--ua-color-text-muted);
font-weight: 500;
}
.ua-tl-time {
color: var(--ua-color-text-muted);
font-size: var(--ua-font-size-sm);
font-variant-numeric: tabular-nums;
}
.ua-tl-content {
margin: 2px 0 0;
color: var(--ua-color-text-muted);
font-size: var(--ua-font-size-sm);
line-height: 1.6;
}
@keyframes ua-tl-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes ua-tl-pulse {
0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ua-color-accent) 18%, transparent); }
50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 4%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
.ua-tl-item,
.ua-tl-dot-pending,
.ua-tl-dot {
animation: none !important;
transition: none !important;
}
}