diff --git a/.ua/backlog.json b/.ua/backlog.json index abe34bf..62281dc 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -980,6 +980,14 @@ "acceptance": "npm run changelog 重新生成(226 提交),changelog:check 不再报漂移;npm run check 全绿", "status": "done", "notes": "自补:发版前刷新步骤,按 g64 约定的方式执行" + }, + { + "id": "g78", + "title": "新增 space 间距容器组件", + "motivation": "库里 56 个组件都在各自实现里写 margin/gap,「一排按钮统一间距」「工具栏两端撑开」这类布局诉求没有承载物,演示页与业务方只能重复拼 style", + "acceptance": "新增 space(props, ...children):direction / size(sm|md|lg 走令牌,数字按像素)/ align / justify / wrap / block;manifest 参数表 + 手把手步骤 + api.exports 登记;demo 有 sec-space 区块真实调用;README 计数与分组同步;npm run check 全绿", + "status": "open", + "notes": "自补:布局基础件缺失;间距一律走 --ua-space-* 令牌,几何用内联 style 不进皮肤样式表" } ] } diff --git a/.ua/index.json b/.ua/index.json index 88ba9a9..df0eff9 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T04:40:12.953Z", + "generatedAt": "2026-10-26T04:55:25.133Z", "repoRoot": ".", "packages": [ { @@ -172,6 +172,7 @@ "rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配", "empty(props):空状态占位,title/description 可选,action 放操作区", "divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线", + "space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调", "modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区", "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", @@ -1424,6 +1425,50 @@ "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感" ] }, + { + "id": "space", + "title": "间距容器 Space", + "demo": "sec-space", + "props": [ + { + "n": "direction", + "t": "'horizontal' | 'vertical'", + "d": "主轴方向;缺省 horizontal 横排" + }, + { + "n": "size", + "t": "'sm' | 'md' | 'lg' | number", + "d": "子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md" + }, + { + "n": "align", + "t": "'start' | 'center' | 'end' | 'baseline'", + "d": "交叉轴对齐;缺省 center,按钮与文字混排不错位" + }, + { + "n": "justify", + "t": "'start' | 'center' | 'end' | 'between'", + "d": "主轴分布;缺省 start,between 两端撑开" + }, + { + "n": "wrap", + "t": "boolean", + "d": "空间不足是否换行;缺省 false" + }, + { + "n": "block", + "t": "boolean", + "d": "是否占满父容器宽度;缺省按内容收缩" + } + ], + "example": "space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...)", + "steps": [ + "把一排需要统一间距的元素交给 space 托管,不再各自写 margin", + "调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字", + "竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐", + "工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap" + ] + }, { "id": "pagination", "title": "分页器 Pagination", diff --git a/AGENTS.md b/AGENTS.md index 47c9262..cda7eff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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):56 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):57 个 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 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2bb95c2..d914aaa 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 56 个组件 + ui.css +│ │ └── ui/ # 57 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index 2b4996a..84eaffe 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全 npm run changelog # 依据 git 提交史重写 CHANGELOG.md(发版前跑;changelog:check 可查漂移) ``` -## 组件(共 56 个) +## 组件(共 57 个) -accordion · affix · alert · autoComplete · avatar · backTop · badge · breadcrumb · button · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · progress · radioGroup · rate · result · segmented · select · skeleton · slider · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · upload · watermark +accordion · affix · alert · autoComplete · avatar · backTop · badge · breadcrumb · button · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · progress · radioGroup · rate · result · segmented · select · skeleton · slider · space · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · upload · watermark -分组:表单(button / textField / inputNumber / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / toast / notification / result / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel)、其他(divider / splitter / backTop / affix / tour / watermark)。 +分组:表单(button / textField / inputNumber / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / toast / notification / result / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel)、其他(space / divider / splitter / backTop / affix / tour / watermark)。 ## 可访问性约定 @@ -32,7 +32,7 @@ accordion · affix · alert · autoComplete · avatar · backTop · badge · bre |---|---| | `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:56 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:57 个 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 a0645a7..9e36c67 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -54,6 +54,7 @@ import { skeleton, splitter, slider, + space, spinner, stepper, statistic, @@ -524,6 +525,24 @@ const content = h( button({ label: '导出', variant: 'ghost' }), ), ), + section( + 'sec-space', + '间距容器', + 'space:用 flex gap 托管一排子元素的间距(不再各自写 margin),size 三档走令牌或直接给像素,direction 切竖排,justify + block 让工具栏两端撑开。', + space( + { justify: 'between', block: true }, + h('span', { class: 'out' }, '左侧标题'), + space({ size: 'sm' }, button({ label: '取消', variant: 'ghost' }), button({ label: '保存' })), + ), + space( + { size: 'lg', wrap: true }, + badge({ label: '默认' }), + badge({ label: '成功', variant: 'success' }), + badge({ label: '警告', variant: 'warning' }), + tag({ label: '#A1024', variant: 'info' }), + ), + space({ direction: 'vertical', align: 'start', size: 6 }, h('span', { class: 'out' }, '竖排第一行'), h('span', { class: 'out' }, '竖排第二行(6px 精确间距)')), + ), section( 'sec-statistic', '统计数值', diff --git a/llms-full.txt b/llms-full.txt index 66c37cb..8b61bee 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T04:40:12.953Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T04:55:25.133Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -776,6 +776,32 @@ divider({ label: '更多信息' }) / divider({ vertical: true }) 现场演示:apps/docs/demo.html#sec-divider +### space — 间距容器 Space + +参数: + +- direction:'horizontal' | 'vertical' —— 主轴方向;缺省 horizontal 横排 +- size:'sm' | 'md' | 'lg' | number —— 子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md +- align:'start' | 'center' | 'end' | 'baseline' —— 交叉轴对齐;缺省 center,按钮与文字混排不错位 +- justify:'start' | 'center' | 'end' | 'between' —— 主轴分布;缺省 start,between 两端撑开 +- wrap:boolean —— 空间不足是否换行;缺省 false +- block:boolean —— 是否占满父容器宽度;缺省按内容收缩 + +示例: + +``` +space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...) +``` + +手把手: + +1. 把一排需要统一间距的元素交给 space 托管,不再各自写 margin +2. 调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字 +3. 竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐 +4. 工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap + +现场演示:apps/docs/demo.html#sec-space + ### pagination — 分页器 Pagination 参数: @@ -1551,6 +1577,7 @@ API 速览: - rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配 - empty(props):空状态占位,title/description 可选,action 放操作区 - divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线 +- space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调 - 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 d3eef5c..97da9e4 100644 --- a/llms.txt +++ b/llms.txt @@ -77,6 +77,7 @@ - rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配 - empty(props):空状态占位,title/description 可选,action 放操作区 - divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线 + - space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调 - 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 b74ae9a..b2bf7c3 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -35,6 +35,7 @@ export { rate } from './rate.js'; export { empty } from './empty.js'; export { result } from './result.js'; export { divider } from './divider.js'; +export { space } from './space.js'; export { list } from './list.js'; export { carousel } from './carousel.js'; export { tree } from './tree.js'; diff --git a/packages/skin/ui/src/space.ts b/packages/skin/ui/src/space.ts new file mode 100644 index 0000000..3f8a4a2 --- /dev/null +++ b/packages/skin/ui/src/space.ts @@ -0,0 +1,19 @@ +import { h } from '@ui-arch/frame'; +import type { SpaceProps, SpaceSize } from './types.js'; + +// 间距档位 → 令牌(数字档位不入表,直接拼像素) +const GAP: Record, string> = { sm: 'var(--ua-space-2)', md: 'var(--ua-space-3)', lg: 'var(--ua-space-4)' }; +// 交叉轴对齐 → flex align-items 取值 +const ALIGN: Record = { start: 'flex-start', center: 'center', end: 'flex-end', baseline: 'baseline' }; +// 主轴分布 → flex justify-content 取值 +const JUSTIFY: Record = { start: 'flex-start', center: 'center', end: 'flex-end', between: 'space-between' }; + +/*** 间距容器:用 flex gap 统一一排子元素的间距,方向 / 档位 / 对齐 / 换行可调 ***/ +export function space(props: SpaceProps, ...children: unknown[]): HTMLDivElement { + const size = props.size ?? 'md'; // 缺省中档,与多数组件内边距对齐 + const gap = typeof size === 'number' ? `${size}px` : GAP[size]; // 数字档位按像素透传 + const cls = ['ua-space', props.direction === 'vertical' ? 'is-vertical' : '', props.block ? 'is-block' : ''].filter(Boolean).join(' '); + // 内联只放几何(间距/对齐/换行),颜色与描边一律留给令牌驱动的类名 + const geom = `gap:${gap};align-items:${ALIGN[props.align ?? 'center']};justify-content:${JUSTIFY[props.justify ?? 'start']};flex-wrap:${props.wrap ? 'wrap' : 'nowrap'}`; + return h('div', { class: cls, style: geom }, ...children) as HTMLDivElement; +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index aa83ab6..cac9eec 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -650,4 +650,17 @@ export interface MultiSelectProps { onChange?: (values: string[]) => void; // 变更回调(传副本) } +/*** 间距档位:令牌档位 sm/md/lg,或直接给像素数 ***/ +export type SpaceSize = 'sm' | 'md' | 'lg' | number; + +/*** 间距容器入参 ***/ +export interface SpaceProps { + direction?: 'horizontal' | 'vertical'; // 主轴方向;缺省 horizontal + size?: SpaceSize; // 子元素间距:sm/md/lg 走令牌,数字按像素;缺省 md + align?: 'start' | 'center' | 'end' | 'baseline'; // 交叉轴对齐;缺省 center,按钮与文字混排不会高低错位 + justify?: 'start' | 'center' | 'end' | 'between'; // 主轴分布;缺省 start,between 用于两端撑开 + wrap?: boolean; // 空间不足是否换行;缺省 false + block?: boolean; // 是否占满父容器宽度;缺省按内容收缩 +} + export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 496a8e1..5e9886c 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -43,6 +43,7 @@ "rate(props):评分星标,value 信号双向绑定,hover 预览移出还原,键盘方向键调节,max 可配", "empty(props):空状态占位,title/description 可选,action 放操作区", "divider(props):分隔线,水平/垂直两种方向,可带居中文案标签,dashed 切虚线", + "space(props):间距容器,flex gap 统一子元素间距,direction / size / align / justify / wrap / block 可调", "modal(props, ...children):弹窗,open 信号驱动显隐,Esc / 遮罩 / × 关闭,size 三档宽度,footer 提供底部操作区", "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", @@ -1295,6 +1296,50 @@ "工具栏内用 vertical: true 的行内竖线,dashed: true 切虚线弱化分隔感" ] }, + { + "id": "space", + "title": "间距容器 Space", + "demo": "sec-space", + "props": [ + { + "n": "direction", + "t": "'horizontal' | 'vertical'", + "d": "主轴方向;缺省 horizontal 横排" + }, + { + "n": "size", + "t": "'sm' | 'md' | 'lg' | number", + "d": "子元素间距:三档走 --ua-space-* 令牌,数字按像素;缺省 md" + }, + { + "n": "align", + "t": "'start' | 'center' | 'end' | 'baseline'", + "d": "交叉轴对齐;缺省 center,按钮与文字混排不错位" + }, + { + "n": "justify", + "t": "'start' | 'center' | 'end' | 'between'", + "d": "主轴分布;缺省 start,between 两端撑开" + }, + { + "n": "wrap", + "t": "boolean", + "d": "空间不足是否换行;缺省 false" + }, + { + "n": "block", + "t": "boolean", + "d": "是否占满父容器宽度;缺省按内容收缩" + } + ], + "example": "space({ size: 'lg' }, button({ label: '保存' }), button({ label: '取消', variant: 'ghost' })) / space({ direction: 'vertical', align: 'start' }, ...)", + "steps": [ + "把一排需要统一间距的元素交给 space 托管,不再各自写 margin", + "调间距:size 传 sm/md/lg 走令牌,需要精确像素时直接给数字", + "竖排用 direction: 'vertical',配 align: 'start' 让子元素左对齐", + "工具栏两端撑开用 justify: 'between' + block: true;窄屏放不下时加 wrap" + ] + }, { "id": "pagination", "title": "分页器 Pagination", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index cfa832e..7635108 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -3488,3 +3488,15 @@ button.ua-step-dot:hover { transform: none; } } + +/* —— 间距容器 —— */ +.ua-space { + display: flex; + min-width: 0; +} +.ua-space.is-vertical { + flex-direction: column; +} +.ua-space.is-block { + width: 100%; +}