diff --git a/.ua/backlog.json b/.ua/backlog.json index dd0c29b..a3c7ae7 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1399,11 +1399,11 @@ }, { "id": "g130", - "title": "新增栅格布局 row / col", + "title": "新增栅格容器 grid", "motivation": "页面骨架只能靠 space 与手写 flex,24 栅格、列间距、响应式断点缺失,示例页布局各写一套", - "acceptance": "row 提供 gutter 列间距与 justify/align,col 提供 span/offset 与 sm/md 断点;窄屏自动堆叠;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", + "acceptance": "grid 支持 columns 固定列数与 min 走 auto-fit 自适应列宽、gutter 统一列间距、align 交叉轴对齐、dense 紧凑填充;窄屏自动降为单列;样式只消费 --ua-*;manifest/docs/README/demo/llms-full 同步;npm run check 全绿", "status": "open", - "notes": "自补:组件补充方向" + "notes": "自补:组件补充方向(原设想 row/col 24 栅格,实施改为单容器 grid —— auto-fit 已覆盖响应式,跨列需求交给子项自身宽度,少一层抽象)" }, { "id": "g131", diff --git a/.ua/index.json b/.ua/index.json index ba70278..b733db5 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T07:17:51.824Z", + "generatedAt": "2026-10-26T07:19:51.268Z", "repoRoot": ".", "packages": [ { @@ -204,7 +204,8 @@ "anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器", "popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」", "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange", - "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮" + "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮", + "grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列" ], "keyTypes": [ "ButtonProps", @@ -3161,6 +3162,51 @@ "content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)", "段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙" ] + }, + { + "id": "grid", + "title": "栅格容器 Grid", + "demo": "sec-grid", + "props": [ + { + "n": "columns", + "t": "number", + "df": "3", + "d": "固定列数(按等宽铺开);窄屏(≤640px)由样式表统一降为单列" + }, + { + "n": "min", + "t": "string", + "d": "自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,列数随可用宽度自然增减,此时忽略 columns" + }, + { + "n": "gutter", + "t": "SpaceSize", + "df": "md", + "d": "列间距:sm/md/lg 走令牌,数字按像素" + }, + { + "n": "align", + "t": "'start' | 'center' | 'end' | 'stretch'", + "df": "stretch", + "d": "交叉轴对齐:同行子项高度不一时决定各自贴哪边或拉满" + }, + { + "n": "dense", + "t": "boolean", + "df": "false", + "d": "紧凑填充:小项回填前面留下的空洞(瀑布式排布)" + } + ], + "example": "grid({ columns: 3, gutter: 'md' }, card({ title: '实例数' }, statistic({ value: total })), card({ title: '错误率' }, statistic({ value: errRate }))); grid({ min: '240px', align: 'start' }, ...items)", + "steps": [ + "页面骨架用 grid({ columns: n }) 铺等宽卡片:子项宽度由列模板算,别在子项上写 width,窄屏会自动变成单列", + "卡片宽度要求「不窄于 X」时用 min(如 '240px'):auto-fit 会按可用宽度决定列数,比手写断点稳,此时不要再传 columns", + "列间距统一走 gutter(sm/md/lg 令牌或像素),不要在子项上写 margin——相邻 margin 会叠加成双倍间距", + "同行卡片高度参差时:要等高(默认 stretch),要各自顶端对齐传 align: 'start'", + "子项必须是单个元素(card / div / 自定义块),grid 只负责排布且已对子项设 min-width: 0,长文本不会撑破列宽", + "只要横向一排展示且元素不等宽,用 space 更轻;grid 面向二维对齐" + ] } ] }, diff --git a/AGENTS.md b/AGENTS.md index c5c42eb..2ac15b7 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):61 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):62 个 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 64762f3..069d0da 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 61 个组件 + ui.css +│ │ └── ui/ # 62 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index 45f64c6..288859c 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 可查漂移) ``` -## 组件(共 61 个) +## 组件(共 62 个) -accordion · affix · alert · anchor · autoComplete · avatar · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · popconfirm · progress · radioGroup · rate · result · segmented · select · skeleton · slider · space · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · typography · upload · watermark +accordion · affix · alert · anchor · autoComplete · avatar · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · grid · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · popconfirm · progress · radioGroup · rate · result · segmented · select · skeleton · slider · space · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · typography · 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 / popconfirm / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / anchor)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel / calendar / typography)、其他(space / 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 / popconfirm / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / anchor)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel / calendar / typography)、其他(grid / space / divider / splitter / backTop / affix / tour / watermark)。 ## 可访问性约定 @@ -32,7 +32,7 @@ accordion · affix · alert · anchor · autoComplete · avatar · backTop · ba |---|---| | `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:61 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:62 个 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 2001257..e1735f8 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -35,6 +35,7 @@ import { form, formItem, inputNumber, + grid, list, carousel, tree, @@ -1289,6 +1290,21 @@ const content = h( ), ), ), + section( + 'sec-grid', + '栅格容器', + 'grid 铺页面骨架:columns 给固定列数,min 走 auto-fit 让列数随可用宽度自然增减(比手写断点稳),列间距统一走 gutter 不产生双倍 margin,窄屏一律降为单列。', + grid({ columns: 3, gutter: 'md' }, + card({ title: '实例数' }, statistic({ value: signal(128), suffix: '台' })), + card({ title: '错误率' }, statistic({ value: signal(0.42), suffix: '%', precision: 2 })), + card({ title: '平均延迟' }, statistic({ value: signal(38), suffix: 'ms' })), + ), + h('div', { class: 'out' }, '↓ min: 220px 的 auto-fit:拉窄窗口可见列数自动减少'), + grid({ min: '220px', align: 'start' }, + ...['ap-east-1', 'ap-south-1', 'eu-west-1', 'us-east-1'].map((zone) => + card({ title: zone }, typography({ content: '健康检查通过', type: 'success', variant: 'paragraph' }))), + ), + ), ); // 引导漫游实例:根节点需挂到 body(fixed 定位,不参与布局) diff --git a/llms-full.txt b/llms-full.txt index f9a749c..a83a057 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T07:17:51.824Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T07:19:51.268Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1702,6 +1702,33 @@ typography({ variant: 'title', level: 2, content: '部署说明' }); typography( 现场演示:apps/docs/demo.html#sec-typography +### grid — 栅格容器 Grid + +参数: + +- columns(默认 3):number —— 固定列数(按等宽铺开);窄屏(≤640px)由样式表统一降为单列 +- min:string —— 自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,列数随可用宽度自然增减,此时忽略 columns +- gutter(默认 md):SpaceSize —— 列间距:sm/md/lg 走令牌,数字按像素 +- align(默认 stretch):'start' | 'center' | 'end' | 'stretch' —— 交叉轴对齐:同行子项高度不一时决定各自贴哪边或拉满 +- dense(默认 false):boolean —— 紧凑填充:小项回填前面留下的空洞(瀑布式排布) + +示例: + +``` +grid({ columns: 3, gutter: 'md' }, card({ title: '实例数' }, statistic({ value: total })), card({ title: '错误率' }, statistic({ value: errRate }))); grid({ min: '240px', align: 'start' }, ...items) +``` + +手把手: + +1. 页面骨架用 grid({ columns: n }) 铺等宽卡片:子项宽度由列模板算,别在子项上写 width,窄屏会自动变成单列 +2. 卡片宽度要求「不窄于 X」时用 min(如 '240px'):auto-fit 会按可用宽度决定列数,比手写断点稳,此时不要再传 columns +3. 列间距统一走 gutter(sm/md/lg 令牌或像素),不要在子项上写 margin——相邻 margin 会叠加成双倍间距 +4. 同行卡片高度参差时:要等高(默认 stretch),要各自顶端对齐传 align: 'start' +5. 子项必须是单个元素(card / div / 自定义块),grid 只负责排布且已对子项设 min-width: 0,长文本不会撑破列宽 +6. 只要横向一排展示且元素不等宽,用 space 更轻;grid 面向二维对齐 + +现场演示:apps/docs/demo.html#sec-grid + API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) @@ -1767,6 +1794,7 @@ API 速览: - popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」 - calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange - typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮 +- grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列 一行用法: diff --git a/llms.txt b/llms.txt index 8f502c2..8f18d08 100644 --- a/llms.txt +++ b/llms.txt @@ -110,6 +110,7 @@ - popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」 - calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange - typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮 + - grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列 - 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')` - 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号 - 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调 diff --git a/packages/skin/ui/src/grid.ts b/packages/skin/ui/src/grid.ts new file mode 100644 index 0000000..6612b4a --- /dev/null +++ b/packages/skin/ui/src/grid.ts @@ -0,0 +1,21 @@ +import { h } from '@ui-arch/frame'; +import type { GridProps } from './types.js'; + +// 间距档位 → 令牌(数字档位不入表,直接拼像素) +const GUTTER: Record = { sm: 'var(--ua-space-2)', md: 'var(--ua-space-3)', lg: 'var(--ua-space-4)' }; +// 交叉轴对齐 → grid align-items 取值 +const ALIGN: Record = { start: 'start', center: 'center', end: 'end', stretch: 'stretch' }; + +/*** 栅格容器:columns 固定列数、min 走 auto-fit 自适应列宽,gutter 统一列间距;列模板写进 --ua-grid-cols,窄屏由样式表降为单列 ***/ +export function grid(props: GridProps, ...children: unknown[]): HTMLElement { + const gutter = typeof props.gutter === 'number' ? `${props.gutter}px` : GUTTER[props.gutter ?? 'md']; // 数字档位按像素透传 + const template = props.min + ? `repeat(auto-fit, minmax(${props.min}, 1fr))` // 自适应:列数由可用宽度决定 + : `repeat(${props.columns ?? 3}, minmax(0, 1fr))`; // 固定列数:minmax(0,1fr) 防止长内容撑破 + const el = h('div', { + class: ['ua-grid', props.dense ? 'is-dense' : '', props.align ? `is-align-${props.align}` : ''].filter(Boolean).join(' '), + style: `gap:${gutter};align-items:${ALIGN[props.align ?? 'stretch']};`, + }, ...children); + el.style.setProperty('--ua-grid-cols', template); // 列模板交给 CSS 变量,媒体查询可覆盖 + return el; +} diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 02e5bd8..06b3e11 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -61,3 +61,4 @@ export { anchor } from './anchor.js'; export { popconfirm } from './popconfirm.js'; export { calendar } from './calendar.js'; export { typography } from './typography.js'; +export { grid } from './grid.js'; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 7bbc472..57501ee 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -757,4 +757,13 @@ export interface TypographyProps { copyable?: boolean; // 附复制按钮:点后写剪贴板并短暂回显结果;缺省 false } +/*** 栅格容器入参 ***/ +export interface GridProps { + columns?: number; // 固定列数;缺省 3(窄屏一律降为单列) + min?: string; // 自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,忽略 columns + gutter?: SpaceSize; // 列间距:sm/md/lg 走令牌,数字按像素;缺省 md + align?: 'start' | 'center' | 'end' | 'stretch'; // 交叉轴对齐(行高不一致时定子项位置);缺省 stretch + dense?: boolean; // 紧凑填充:小项回填空洞(瀑布式排布);缺省 false +} + export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 8b1fb4c..1594c2b 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -75,7 +75,8 @@ "anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器", "popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」", "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange", - "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮" + "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮", + "grid(props, ...children):栅格容器,columns 固定列数或 min 走 auto-fit 自适应列宽,gutter 统一列间距,窄屏自动降为单列" ], "keyTypes": [ "ButtonProps", @@ -3032,6 +3033,51 @@ "content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)", "段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙" ] + }, + { + "id": "grid", + "title": "栅格容器 Grid", + "demo": "sec-grid", + "props": [ + { + "n": "columns", + "t": "number", + "df": "3", + "d": "固定列数(按等宽铺开);窄屏(≤640px)由样式表统一降为单列" + }, + { + "n": "min", + "t": "string", + "d": "自适应最小列宽(如 '220px'):给了就按 auto-fit 铺列,列数随可用宽度自然增减,此时忽略 columns" + }, + { + "n": "gutter", + "t": "SpaceSize", + "df": "md", + "d": "列间距:sm/md/lg 走令牌,数字按像素" + }, + { + "n": "align", + "t": "'start' | 'center' | 'end' | 'stretch'", + "df": "stretch", + "d": "交叉轴对齐:同行子项高度不一时决定各自贴哪边或拉满" + }, + { + "n": "dense", + "t": "boolean", + "df": "false", + "d": "紧凑填充:小项回填前面留下的空洞(瀑布式排布)" + } + ], + "example": "grid({ columns: 3, gutter: 'md' }, card({ title: '实例数' }, statistic({ value: total })), card({ title: '错误率' }, statistic({ value: errRate }))); grid({ min: '240px', align: 'start' }, ...items)", + "steps": [ + "页面骨架用 grid({ columns: n }) 铺等宽卡片:子项宽度由列模板算,别在子项上写 width,窄屏会自动变成单列", + "卡片宽度要求「不窄于 X」时用 min(如 '240px'):auto-fit 会按可用宽度决定列数,比手写断点稳,此时不要再传 columns", + "列间距统一走 gutter(sm/md/lg 令牌或像素),不要在子项上写 margin——相邻 margin 会叠加成双倍间距", + "同行卡片高度参差时:要等高(默认 stretch),要各自顶端对齐传 align: 'start'", + "子项必须是单个元素(card / div / 自定义块),grid 只负责排布且已对子项设 min-width: 0,长文本不会撑破列宽", + "只要横向一排展示且元素不等宽,用 space 更轻;grid 面向二维对齐" + ] } ] } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index eb3b548..320821e 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -4045,3 +4045,24 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } transition: none; } } + +/* —— 栅格容器:列模板由内联变量给,窄屏统一降为单列 —— */ +.ua-grid { + display: grid; + grid-template-columns: var(--ua-grid-cols, repeat(3, minmax(0, 1fr))); + align-items: stretch; +} +.ua-grid > * { + min-width: 0; /* 长文本 / 表格不撑破列宽 */ +} +.ua-grid.is-dense { + grid-auto-flow: dense; +} +.ua-grid.is-align-start { align-items: start; } +.ua-grid.is-align-center { align-items: center; } +.ua-grid.is-align-end { align-items: end; } +@media (max-width: 640px) { + .ua-grid { + grid-template-columns: 1fr; + } +}