diff --git a/.ua/backlog.json b/.ua/backlog.json index 65589cb..c3abfd0 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -120,6 +120,14 @@ "acceptance": "demo 页改为 shell 双栏布局(左 sticky 目录 + 右内容),目录项挂载后从 .sec 自动反推(不手写清单),IntersectionObserver 高亮当前区块,窄屏(≤820px)隐藏目录,reduced-motion 下关平滑滚动;npm run check 全绿、demo 可正常构建", "status": "done", "notes": "目录由 buildToc 在 mount 后扫描 section.sec 生成(id + h2 文案),新增区块自动入目录无需维护;rootMargin -10%/-80% 让高亮更跟手;.page 去掉 max-width 由 .shell 统一控宽 1160" + }, + { + "id": "d09", + "title": "新增滑块 slider 组件", + "motivation": "设置类场景(音量/阈值/预算)需要范围输入,皮肤包只有文本类输入;原生 range 自带键盘与触屏,接入成本低", + "acceptance": "新增 slider.ts:value 信号双向绑定(拖动写回 + 外部赋值同步),min/max/step 可配,disabled 支持布尔或信号,showValue 可关,onChange 回调;样式只消费 --ua-* 令牌(accent-color)且带聚焦光环与禁用态;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿", + "status": "done", + "notes": "组件不持有数值,一律委托调用方信号;effect 内等值不重写 input.value,避免拖动过程被打断;轨道配色用 accent-color 令牌化,无需自绘 thumb;demo 新增 sec-slider(音量 step 5 + 与 locked 信号联动的禁用滑块)" } ] } diff --git a/.ua/index.json b/.ua/index.json index f17ec1c..fba872c 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T08:52:28.012Z", + "generatedAt": "2026-10-23T08:53:00.281Z", "repoRoot": ".", "packages": [ { @@ -130,7 +130,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / 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 / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "tags": [ "components", "dom", @@ -145,6 +145,7 @@ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", + "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", @@ -199,7 +200,8 @@ "FormItemHandle", "FormProps", "DataTableProps", - "PopoverProps" + "PopoverProps", + "SliderProps" ] }, "usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')", @@ -209,7 +211,7 @@ "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" ], "docs": { - "intro": "24 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", + "intro": "25 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", "steps": [ "安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)", "引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'", @@ -328,6 +330,62 @@ "字数统计等派生逻辑直接在响应式文本里读 bio().length" ] }, + { + "id": "slider", + "title": "滑块 Slider", + "demo": "sec-slider", + "props": [ + { + "n": "value", + "t": "Signal", + "r": 1, + "d": "当前值信号,双向绑定:拖动写回,外部赋值同步位置" + }, + { + "n": "min", + "t": "number", + "d": "最小值;缺省 0", + "df": "0" + }, + { + "n": "max", + "t": "number", + "d": "最大值;缺省 100", + "df": "100" + }, + { + "n": "step", + "t": "number", + "d": "步长;缺省 1", + "df": "1" + }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步" + }, + { + "n": "showValue", + "t": "boolean", + "d": "是否右侧显示当前值;缺省 true", + "df": "true" + }, + { + "n": "onChange", + "t": "(value: number) => void", + "k": 1, + "d": "拖动回调" + } + ], + "example": "const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })", + "steps": [ + "创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定", + "用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数", + "拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理", + "只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal", + "禁用场景传 disabled(布尔或信号),置灰且不可拖" + ] + }, { "id": "checkbox", "title": "复选框 Checkbox", diff --git a/.ua/iterations.log b/.ua/iterations.log index b387e17..8bf26da 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -13,3 +13,4 @@ {"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 全绿"} {"time": "2026-10-23T16:51:48+08:00", "id": "d07", "result": "done", "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 全绿"} +{"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 全绿"} diff --git a/apps/demo/main.ts b/apps/demo/main.ts index a0f9bca..3c0c1f5 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -18,6 +18,7 @@ import { radioGroup, select, skeleton, + slider, spinner, switchButton, tabs, @@ -96,6 +97,9 @@ const formResult = signal(''); // —— 分页联动状态:dataTable 与独立 pagination 共用同一页码信号 —— const tablePage = signal(1); +// —— 滑块:音量与禁用态演示 —— +const volume = signal(40); + // —— 表格加载态:切换后数据行变骨架行,筛选与翻页临时禁用 —— const tableLoading = signal(false); @@ -155,6 +159,17 @@ const content = h( radioGroup({ options: [{ value: 'x', label: '静态禁用' }], value: size, disabled: true }), ), ), + section( + 'sec-slider', + '滑块', + 'slider:原生 range 承载交互(含键盘 ←→),value 信号双向绑定,右侧可显示当前值。', + h( + 'div', + { class: 'row' }, + slider({ value: volume, min: 0, max: 100, step: 5, onChange: (v) => toast.info(`音量 ${v}`) }), + slider({ value: volume, disabled: locked }), + ), + ), section( 'sec-feedback', '反馈与提示', diff --git a/llms-full.txt b/llms-full.txt index 150a540..a3d5acf 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T08:52:28.012Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T08:53:00.281Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -40,7 +40,7 @@ ## @ui-arch/ui(skin-dom)v0.1.0 -24 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 +25 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 ### button — 按钮 Button @@ -112,6 +112,34 @@ const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话 现场演示:apps/docs/demo.html#sec-form +### slider — 滑块 Slider + +参数: + +- value(必填):Signal —— 当前值信号,双向绑定:拖动写回,外部赋值同步位置 +- min(默认 0):number —— 最小值;缺省 0 +- max(默认 100):number —— 最大值;缺省 100 +- step(默认 1):number —— 步长;缺省 1 +- disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 +- showValue(默认 true):boolean —— 是否右侧显示当前值;缺省 true +- onChange(回调):(value: number) => void —— 拖动回调 + +示例: + +``` +const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) }) +``` + +手把手: + +1. 创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定 +2. 用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数 +3. 拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理 +4. 只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal +5. 禁用场景传 disabled(布尔或信号),置灰且不可拖 + +现场演示:apps/docs/demo.html#sec-slider + ### checkbox — 复选框 Checkbox 参数: @@ -624,6 +652,7 @@ API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 +- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 diff --git a/llms.txt b/llms.txt index f3ef621..354e3cb 100644 --- a/llms.txt +++ b/llms.txt @@ -45,11 +45,12 @@ ## @ui-arch/ui (v0.1.0) - 类型: skin-dom · 目录: packages/skin/ui -- 描述: DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / 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 / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。 - API: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 + - slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index a3cd5c3..183462b 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -2,6 +2,7 @@ export * from './types.js'; export { button } from './button.js'; export { textField } from './textField.js'; export { textarea } from './textarea.js'; +export { slider } from './slider.js'; export { checkbox } from './checkbox.js'; export { switchButton } from './switch.js'; export { select } from './select.js'; diff --git a/packages/skin/ui/src/slider.ts b/packages/skin/ui/src/slider.ts new file mode 100644 index 0000000..1994e51 --- /dev/null +++ b/packages/skin/ui/src/slider.ts @@ -0,0 +1,32 @@ +import { effect, h } from '@ui-arch/frame'; +import type { SliderProps } from './types.js'; + +/*** 滑块:原生 range 承载交互与键盘,value 信号双向绑定;轨道配色走 accent-color 令牌 ***/ +export function slider(props: SliderProps): HTMLElement { + const value = props.value; // 必需信号:组件不持有数值,一律委托调用方 + const input = h('input', { + type: 'range', + class: 'ua-slider-input', + min: props.min ?? 0, + max: props.max ?? 100, + step: props.step ?? 1, + disabled: props.disabled, // 布尔或信号禁用,h() 直接透传 + }) as HTMLInputElement; + + input.addEventListener('input', () => { + const next = Number(input.value); + value(next); // 用户拖动写回信号 + props.onChange?.(next); + }); + effect(() => { + const next = String(value()); + if (input.value !== next) input.value = next; // 外部赋值同步滑块位置(等值不重写,避免打断拖动) + }); + + return h( + 'div', + { class: 'ua-slider' }, + input, + props.showValue === false ? null : h('span', { class: 'ua-slider-value' }, () => String(value())), + ); +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 4064863..9daa17c 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -137,6 +137,17 @@ export interface AccordionProps { onChange?: (active: string) => void; // 展开变化回调:收起时传空串 } +/*** 滑块入参 ***/ +export interface SliderProps { + value: Signal; // 当前值信号,双向绑定(拖动写回、外部赋值同步) + min?: number; // 最小值;缺省 0 + max?: number; // 最大值;缺省 100 + step?: number; // 步长;缺省 1 + disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 + showValue?: boolean; // 是否右侧显示当前值;缺省 true + onChange?: (value: number) => void; // 拖动回调 +} + /*** 多行输入入参 ***/ export interface TextareaProps { placeholder?: string; // 占位文案 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 0714650..6015c7e 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1,7 +1,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / 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 / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "tags": [ "components", "dom", @@ -16,6 +16,7 @@ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", + "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", @@ -70,7 +71,8 @@ "FormItemHandle", "FormProps", "DataTableProps", - "PopoverProps" + "PopoverProps", + "SliderProps" ] }, "usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')", @@ -80,7 +82,7 @@ "语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色" ], "docs": { - "intro": "24 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", + "intro": "25 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。", "steps": [ "安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)", "引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'", @@ -199,6 +201,62 @@ "字数统计等派生逻辑直接在响应式文本里读 bio().length" ] }, + { + "id": "slider", + "title": "滑块 Slider", + "demo": "sec-slider", + "props": [ + { + "n": "value", + "t": "Signal", + "r": 1, + "d": "当前值信号,双向绑定:拖动写回,外部赋值同步位置" + }, + { + "n": "min", + "t": "number", + "d": "最小值;缺省 0", + "df": "0" + }, + { + "n": "max", + "t": "number", + "d": "最大值;缺省 100", + "df": "100" + }, + { + "n": "step", + "t": "number", + "d": "步长;缺省 1", + "df": "1" + }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步" + }, + { + "n": "showValue", + "t": "boolean", + "d": "是否右侧显示当前值;缺省 true", + "df": "true" + }, + { + "n": "onChange", + "t": "(value: number) => void", + "k": 1, + "d": "拖动回调" + } + ], + "example": "const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })", + "steps": [ + "创建数值信号(初值在 min/max 区间内),传入 value 完成双向绑定", + "用 min / max / step 限定范围与粒度,如 step: 5 只取 5 的倍数", + "拖动或按 ←→ 键改变值:原生 range 自带键盘与触屏交互,无需额外处理", + "只想看拖动条不显示数字时传 showValue: false;需要自定义展示可自行读 signal", + "禁用场景传 disabled(布尔或信号),置灰且不可拖" + ] + }, { "id": "checkbox", "title": "复选框 Checkbox", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 6b697fd..d5ff3d8 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -539,6 +539,39 @@ padding-bottom: 12px; } +/* —— 滑块 —— */ +.ua-slider { + display: inline-flex; + align-items: center; + gap: var(--ua-space-3); + min-width: 180px; +} +.ua-slider-input { + flex: 1; + height: 4px; + margin: 0; + border-radius: var(--ua-radius-sm); + background: color-mix(in srgb, var(--ua-color-border) 70%, transparent); + accent-color: var(--ua-color-accent); + cursor: pointer; + transition: opacity 0.15s; +} +.ua-slider-input:focus-visible { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 20%, transparent); +} +.ua-slider-input:disabled { + opacity: 0.45; + cursor: not-allowed; +} +.ua-slider-value { + min-width: 32px; + color: var(--ua-color-text-muted); + font-size: var(--ua-font-size-md); + font-variant-numeric: tabular-nums; + text-align: right; +} + /* —— 多行输入 —— */ .ua-textarea { width: 100%; @@ -1045,6 +1078,7 @@ .ua-acc-body, .ua-crumb-link, .ua-pop, + .ua-slider-input, .ua-switch-track, .ua-switch-thumb, .ua-tab,