diff --git a/.ua/backlog.json b/.ua/backlog.json index 11db6a8..685f81f 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -292,6 +292,14 @@ "acceptance": "packages/skin/ui 新增 stepper.ts:stepper({steps, active, clickable?, onChange?}),done/current/upcoming 三态视觉,clickable 时已完成步骤可点回跳并写回 active 信号,aria-current=step;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿", "status": "done", "notes": "stepper(props):active 信号变化整条重渲染(effect 首跑赋值 + replaceWith,条目少开销可忽略);done 显示 ✓、current 光环聚焦、upcoming 弱化,aria-current=step;clickable 时已完成步骤渲染为 button 回跳写回信号;连接线用 .ua-step + .ua-step::before 横向短线;demo 新增 sec-stepper(上一步/下一步按钮 computed 禁用联动);README 同步 30 组件" + }, + { + "id": "e03", + "title": "新增 rate 评分组件", + "motivation": "评价、打分场景常用,现有组件无覆盖;星标交互可完整复用信号双向绑定与微动画审美约定,单文件可控", + "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", + "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 组件" } ] } \ No newline at end of file diff --git a/.ua/index.json b/.ua/index.json index 9e76385..20f12a2 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T09:57:15.241Z", + "generatedAt": "2026-10-23T10:03:21.198Z", "repoRoot": ".", "packages": [ { @@ -168,6 +168,7 @@ "progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", "stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳", + "rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配", "modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区", "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", @@ -1144,6 +1145,42 @@ "clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态" ] }, + { + "id": "rate", + "title": "评分 Rate", + "demo": "sec-rate", + "props": [ + { + "n": "value", + "t": "Signal", + "r": 1, + "d": "当前评分(1 基整数值)信号,双向绑定:点选/键盘写回、外部赋值同步" + }, + { + "n": "max", + "t": "number", + "d": "星标总数;缺省 5", + "df": "5" + }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步" + }, + { + "n": "onChange", + "t": "(value: number) => void", + "k": 1, + "d": "评分变化回调" + } + ], + "example": "const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) })", + "steps": [ + "用 signal(0) 创建评分信号并传入 value;0 表示未评分", + "点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原", + "max 可调整星标总数;disabled 支持布尔或信号禁用" + ] + }, { "id": "pagination", "title": "分页器 Pagination", diff --git a/.ua/iterations.log b/.ua/iterations.log index 08214ba..bf5b735 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -36,3 +36,4 @@ {"ts": "2026-10-23T09:12:49+00:00", "round": 29, "id": "d29", "result": "done", "commit": "", "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: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":"4f7d1e8","summary":"新增 rate 评分组件:星标点选 + hover 预览 + 键盘方向键调节,信号双向绑定,门禁全绿"} diff --git a/README.md b/README.md index 521838c..2e3d61e 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ npm run demo # 打开演示:apps/demo/dist/demo.html npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt ``` -## 组件(共 30 个) +## 组件(共 31 个) -accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · drawer · form · inputNumber · menu · modal · pagination · popover · progress · radioGroup · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · toast · tooltip +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 -分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / 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)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / tag / avatar / card)。 ## 目录 @@ -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:30 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:31 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | | `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) | | `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server | | `apps/demo` | 交互演示(esbuild 打包成单文件) | diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 3346ae7..3356865 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -19,6 +19,7 @@ import { popover, progress, radioGroup, + rate, select, showToast, skeleton, @@ -119,6 +120,9 @@ const qty = signal(1); // —— 步骤条:下单流程演示,按钮推进、点已完成步骤回跳 —— const stepIdx = signal(0); +// —— 评分:满意度演示,与只读实例共用同一信号 —— +const score = signal(0); + // —— 受控选中:dataTable 内部勾选实时写回,外部可清空 —— const picked = signal([]); @@ -363,6 +367,18 @@ const content = h( button({ label: '下一步', disabled: computed(() => stepIdx() === 2), onClick: () => stepIdx(Math.min(2, stepIdx() + 1)) }), ), ), + section( + 'sec-rate', + '评分', + 'rate:点选星标打分,悬停预览移出还原,键盘 →/↑ 加分 ←/↓ 减分,disabled 只读展示。', + h( + 'div', + { class: 'row' }, + rate({ value: score, onChange: (v) => toast.info(`评分 ${v} 星`) }), + h('span', { class: 'out' }, () => `当前:${score() || '未评分'}`), + ), + h('div', { class: 'row' }, rate({ value: score, disabled: true })), + ), section( 'sec-menu', '下拉菜单', diff --git a/llms-full.txt b/llms-full.txt index 86a6aa8..c7151bf 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T09:57:15.241Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T10:03:21.198Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -651,6 +651,29 @@ const step = signal(1); stepper({ steps: [{ label: '下单' }, { label: '支付' 现场演示:apps/docs/demo.html#sec-stepper +### rate — 评分 Rate + +参数: + +- value(必填):Signal —— 当前评分(1 基整数值)信号,双向绑定:点选/键盘写回、外部赋值同步 +- max(默认 5):number —— 星标总数;缺省 5 +- disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 +- onChange(回调):(value: number) => void —— 评分变化回调 + +示例: + +``` +const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) }) +``` + +手把手: + +1. 用 signal(0) 创建评分信号并传入 value;0 表示未评分 +2. 点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原 +3. max 可调整星标总数;disabled 支持布尔或信号禁用 + +现场演示:apps/docs/demo.html#sec-rate + ### pagination — 分页器 Pagination 参数: @@ -827,6 +850,7 @@ API 速览: - progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 - stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳 +- rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配 - modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区 - drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭 - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit diff --git a/llms.txt b/llms.txt index 4de7eef..0256dfc 100644 --- a/llms.txt +++ b/llms.txt @@ -73,6 +73,7 @@ - progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 - stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳 + - rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配 - modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区 - drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭 - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 26f6f05..38586a2 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -25,6 +25,7 @@ export { spinner } from './spinner.js'; export { progress } from './progress.js'; export { tabs } from './tabs.js'; export { stepper } from './stepper.js'; +export { rate } from './rate.js'; export { modal } from './modal.js'; export { drawer } from './drawer.js'; export { form, formItem } from './form.js'; diff --git a/packages/skin/ui/src/rate.ts b/packages/skin/ui/src/rate.ts new file mode 100644 index 0000000..5619af6 --- /dev/null +++ b/packages/skin/ui/src/rate.ts @@ -0,0 +1,34 @@ +import { effect, h, signal } from '@ui-arch/frame'; +import type { RateProps } from './types.js'; + +/*** 评分:星标点选,hover 预览移出还原,键盘 ←→/↑↓ 调节,value 信号双向绑定 ***/ +export function rate(props: RateProps): HTMLDivElement { + const value = props.value; + const max = props.max ?? 5; // 星标总数缺省 5 + const hover = signal(0); // 预览索引(1 基,0 表示无预览) + const emit = (v: number): void => { const n = Math.min(max, Math.max(1, v)); value(n); if (props.onChange) props.onChange(n); }; // 写回信号并回调 + const render = (): HTMLElement => { + const shown = hover() || value(); // 预览优先,无预览显示当前值 + const stars = []; + for (let i = 1; i <= max; i++) { + stars.push(h('button', { + class: `ua-rate-star${i <= shown ? ' is-on' : ''}`, + type: 'button', + disabled: props.disabled, + 'aria-label': `${i} 星`, + onClick: () => emit(i), + onMouseEnter: () => hover(i), + }, '★')); + } + return h('div', { class: 'ua-rate', role: 'slider', 'aria-label': '评分', 'aria-valuemin': 1, 'aria-valuemax': max, 'aria-valuenow': value(), onMouseLeave: () => hover(0) }, ...stars); + }; + let el!: HTMLElement; // 当前渲染产物(effect 首跑赋值) + effect(() => { const next = render(); if (el) el.replaceWith(next); el = next; }); // value/hover 变化整条重渲染(星标少,开销可忽略) + const keyNav = (e: KeyboardEvent): void => { + if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' && e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return; // 卫语句:只拦截方向键 + e.preventDefault(); + emit(value() + (e.key === 'ArrowRight' || e.key === 'ArrowUp' ? 1 : -1)); // →/↑ 加一分,←/↓ 减一分 + }; + el.addEventListener('keydown', keyNav); + return el as HTMLDivElement; +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index dec1149..9a2f02b 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -189,6 +189,14 @@ export interface StepperProps { onChange?: (index: number) => void; // 步骤切换回调(点击回跳触发) } +/*** 评分入参 ***/ +export interface RateProps { + value: Signal; // 当前评分(1 基整数值),双向绑定(点击/键盘写回、外部赋值同步) + max?: number; // 星标总数;缺省 5 + disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 + onChange?: (value: number) => void; // 评分变化回调 +} + /*** 徽标入参 ***/ export interface BadgeProps { label: string; // 徽标文案 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 0d81cc8..7221eb1 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -39,6 +39,7 @@ "progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", "stepper(props):步骤条,done/current/upcoming 三态,active 信号双向绑定,clickable 时已完成步骤可回跳", + "rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配", "modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区", "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", @@ -1015,6 +1016,42 @@ "clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态" ] }, + { + "id": "rate", + "title": "评分 Rate", + "demo": "sec-rate", + "props": [ + { + "n": "value", + "t": "Signal", + "r": 1, + "d": "当前评分(1 基整数值)信号,双向绑定:点选/键盘写回、外部赋值同步" + }, + { + "n": "max", + "t": "number", + "d": "星标总数;缺省 5", + "df": "5" + }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步" + }, + { + "n": "onChange", + "t": "(value: number) => void", + "k": 1, + "d": "评分变化回调" + } + ], + "example": "const score = signal(0); rate({ value: score, max: 5, onChange: (v) => console.log(v) })", + "steps": [ + "用 signal(0) 创建评分信号并传入 value;0 表示未评分", + "点击星标或聚焦后按 →/↑ 加一分、←/↓ 减一分;悬停高亮预览、移出还原", + "max 可调整星标总数;disabled 支持布尔或信号禁用" + ] + }, { "id": "pagination", "title": "分页器 Pagination", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 6ea763f..45c9aec 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -783,6 +783,37 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); } +/* —— 评分 —— */ +.ua-rate { + display: inline-flex; + gap: 2px; +} +.ua-rate-star { + padding: 0; + border: none; + background: none; + color: var(--ua-color-border); /* 未选中:弱化为边框色 */ + font-size: 22px; + line-height: 1; + cursor: pointer; + transition: color 0.15s, transform 0.12s; +} +.ua-rate-star:hover { + transform: scale(1.15); /* 悬停微放大 */ +} +.ua-rate-star.is-on { + color: var(--ua-color-warning); /* 选中:警示黄即星标金 */ +} +.ua-rate-star:disabled { + cursor: not-allowed; + opacity: 0.45; +} +.ua-rate-star:focus-visible { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-warning) 25%, transparent); + border-radius: 4px; +} + /* —— 徽标 —— */ .ua-badge { display: inline-block; @@ -1459,7 +1490,8 @@ button.ua-step-dot:hover { .ua-modal-overlay, .ua-drawer, .ua-drawer-overlay, - .ua-step-dot { + .ua-step-dot, + .ua-rate-star { animation: none !important; transition: none !important; }