diff --git a/.ua/backlog.json b/.ua/backlog.json index 87a5542..ee38d27 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -3131,6 +3131,15 @@ "motivation": "sortableList 是句柄式组件(返回 { el, move, order }),与 form / virtualList 同类,但 18 条配方里没有一条演示句柄式拼装——Agent 照配方抄会把返回值直接当节点 append,页面一片空白;「顺序只存一份」「move 与拖拽同一条提交路径」这些约定也没人写下来。", "acceptance": "① recipes.md 新增配方 19(编号连续、带代码块与要点),代码块里认出 sortableList / card / button / toast;② 要点覆盖句柄要 append(.el)、顺序唯一真相、move 与拖拽同路径、边缘不自动滚动、条目文案唯一;③ ua recipes 19 能取到该条,llms-full.txt 与 llms.txt 重生成;④ npm run verify / check 全绿。", "notes": "recipes.md 新增配方 19:sortableList + card + button + toast 的可排序检查单,要点写清句柄要 append(.el)、顺序唯一真相、move 与拖拽同路径、边缘不自动滚动、文案唯一;ua recipes 19 可取出,llms.txt / llms-full.txt 重生成" + }, + { + "id": "g305", + "domain": "new", + "status": "open", + "title": "新增瀑布流布局 masonry(89 个组件)", + "motivation": "库里有 grid(等宽等高的规则栅格)与 space(线性间距),但图片墙、评论流、看板这类「列高不等」的布局没有落点,业务侧只能自己写 column-width 与 break-inside,还要记住多列容器不能是 flex/grid 这个坑。", + "acceptance": "① masonry({ items, columns?, minColumnWidth?, gap? }) 走 CSS 多列:不传 columns 按 minColumnWidth 自适应,传了即固定列数;② 每项包 .ua-masonry-item 并 break-inside: avoid,卡片不在列边界拆断;③ gap 同时管列间距与行间距;④ 样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号;⑤ manifest/docs/README/demo/llms-full 同步,组件 88→89,npm run check 全绿。", + "notes": "" } ] } diff --git a/.ua/index.json b/.ua/index.json index ed737b6..1bccaf9 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T16:31:56.843Z", + "generatedAt": "2026-10-26T16:34:08.281Z", "repoRoot": ".", "packages": [ { @@ -231,7 +231,8 @@ "heatmap(props):日历热力图,按周列 × 星期行铺格子、四档浓度表示当天数量,悬停看明细", "progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘", "noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报", - "sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄" + "sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄", + "masonry(props):瀑布流多列布局,列宽自适应或固定列数,卡片不被拆到两列" ], "keyTypes": [ "ButtonProps", @@ -4696,6 +4697,45 @@ "外部要按代码调整顺序用句柄 move(from, to),下标越界或禁用时静默忽略;order() 以 DOM 为唯一真相回读当前顺序", "什么时候别用:条目超过一屏(拖到边缘不会自动滚动,改用筛选或上下按钮)、需要跨列表搬运(用两个实例并自己管状态)" ] + }, + { + "id": "masonry", + "title": "瀑布流 Masonry", + "demo": "sec-masonry", + "props": [ + { + "n": "items", + "t": "unknown[]", + "r": 1, + "d": "每个卡片内容(字符串 / DOM / 响应式值);数组顺序即铺放顺序,列高不等由内容自己决定" + }, + { + "n": "columns", + "t": "number", + "d": "固定列数;传了即不随容器宽度变化,与 minColumnWidth 互斥" + }, + { + "n": "minColumnWidth", + "t": "number", + "d": "自适应列宽下限(像素);缺省 240,容器越宽列数越多", + "df": "240" + }, + { + "n": "gap", + "t": "string", + "d": "列间距与行间距(CSS 长度或令牌值);缺省 var(--ua-space-3)", + "df": "var(--ua-space-3)" + } + ], + "example": "masonry({ minColumnWidth: 220, items: shots.map((s) => card({ title: s.title }, image({ src: s.url }))) })", + "steps": [ + "items 里直接放 card / image / 任意 DOM:每项由组件包一层 .ua-masonry-item,只有这一层带 break-inside: avoid,卡片才不会在列边界断成两截", + "不传 columns 就按 minColumnWidth 自适应:容器变宽自动加列、窄屏自动降到一列,不需要自己监听 window.resize", + "要固定几列(比如大屏看板恒 4 列)就传 columns,此时 minColumnWidth 不再生效——两者互斥,别同时传指望它俩协商", + "间距传一次就够:gap 同时给 column-gap 与每项的 margin-bottom,列间距和行间距天然一致;改间距只改这一处", + "列高不等才是瀑布流的价值所在(图片墙、评论流、看板):等高内容请用 grid,瀑布流会把等高卡片铺成奇怪的错位", + "什么时候别用:需要严格的行对齐(表格化数据)、需要按列独立滚动(用 splitter / 多个 virtualList),以及首屏内容需要按阅读顺序严格横向(多列布局是竖向填充,读屏顺序与视觉列序一致但不等同于行序)" + ] } ] }, diff --git a/AGENTS.md b/AGENTS.md index 81bb173..7077f01 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):88 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):89 个 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 0891b7c..9ca70eb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -39,7 +39,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 88 个组件 + ui.css +│ │ └── ui/ # 89 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index 57ae2fa..fc18f46 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 可查漂移) ``` -## 组件(共 88 个) +## 组件(共 89 个) -accordion · affix · alert · anchor · autoComplete · avatar · avatarGroup · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · codeBlock · colorPicker · commandPalette · copyButton · countdown · dataTable · datePicker · descriptions · diffView · divider · drawer · empty · floatButton · form · grid · heatmap · highlight · image · inputNumber · inputTag · jsonViewer · kbd · layout · link · list · loading · menu · modal · mentions · multiSelect · noticeBar · notification · pagination · pinInput · popover · popconfirm · progress · progressRing · radioGroup · rate · relativeTime · result · segmented · select · signaturePad · skeleton · slider · sortableList · space · sparkline · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · timePicker · toast · tooltip · tour · transfer · tree · treeSelect · typography · upload · virtualList · watermark +accordion · affix · alert · anchor · autoComplete · avatar · avatarGroup · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · codeBlock · colorPicker · commandPalette · copyButton · countdown · dataTable · datePicker · descriptions · diffView · divider · drawer · empty · floatButton · form · grid · heatmap · highlight · image · inputNumber · inputTag · jsonViewer · kbd · layout · link · list · loading · masonry · menu · modal · mentions · multiSelect · noticeBar · notification · pagination · pinInput · popover · popconfirm · progress · progressRing · radioGroup · rate · relativeTime · result · segmented · select · signaturePad · skeleton · slider · sortableList · space · sparkline · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · timePicker · toast · tooltip · tour · transfer · tree · treeSelect · typography · upload · virtualList · watermark -分组:表单(button / copyButton / signaturePad / textField / inputNumber / inputTag / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / mentions / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / timePicker / pinInput / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / loading / toast / notification / noticeBar / result / spinner / progress / progressRing / skeleton / badge / empty / statistic / countdown)、浮层(tooltip / popover / popconfirm / modal / drawer / menu / commandPalette)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / treeSelect / anchor)、数据展示(dataTable / jsonViewer / diffView / codeBlock / relativeTime / sparkline / heatmap / descriptions / card / list / sortableList / kbd / highlight / timeline / tag / avatar / avatarGroup / image / carousel / calendar / typography / virtualList)、其他(floatButton / grid / layout / link / space / divider / splitter / backTop / affix / tour / watermark)。 +分组:表单(button / copyButton / signaturePad / textField / inputNumber / inputTag / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / mentions / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / timePicker / pinInput / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / loading / toast / notification / noticeBar / result / spinner / progress / progressRing / skeleton / badge / empty / statistic / countdown)、浮层(tooltip / popover / popconfirm / modal / drawer / menu / commandPalette)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / treeSelect / anchor)、数据展示(dataTable / jsonViewer / diffView / codeBlock / relativeTime / sparkline / heatmap / descriptions / card / list / sortableList / kbd / highlight / timeline / tag / avatar / avatarGroup / image / carousel / calendar / typography / virtualList)、其他(floatButton / grid / layout / link / space / divider / splitter / backTop / affix / tour / masonry / watermark)。 ## 可访问性约定 @@ -34,7 +34,7 @@ accordion · affix · alert · anchor · autoComplete · avatar · avatarGroup |---|---| | `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:88 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:89 个 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 3c1d9ef..3f10861 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -62,6 +62,7 @@ import { upload, watermark, menu, + masonry, modal, mentions, multiSelect, @@ -1930,6 +1931,21 @@ content.append( h('p', { class: 'desc' }, () => `当前顺序:${sortableOrder().join(' → ')}`), ), ), + section( + 'sec-masonry', + '瀑布流', + 'masonry:CSS 多列布局,列宽按 minColumnWidth 自适应(窄屏自动降列)、也能固定列数;每项包一层才不被列边界拆断,间距一处传值同时管列间距与行间距。', + masonry({ + minColumnWidth: 200, + items: [ + card({ title: '图片墙 ①' }, h('p', { class: 'desc' }, '列高不同才是瀑布流的价值:内容自己决定卡片高度,浏览器负责找最短列。')), + card({ title: '图片墙 ②' }, h('p', { class: 'desc' }, '短卡片。')), + card({ title: '图片墙 ③' }, h('p', { class: 'desc' }, '这一张内容偏长,用来制造高度差:文字越多卡片越高,多列布局才会错落起来,而不是排成整齐的行。')), + card({ title: '图片墙 ④' }, h('p', { class: 'desc' }, '中等长度的一段说明,两行左右。')), + card({ title: '图片墙 ⑤' }, h('p', { class: 'desc' }, '再一条短的。')), + ], + }), + ), ); // 悬浮按钮:fixed 定位,挂 body 不参与页面布局 diff --git a/llms-full.txt b/llms-full.txt index 0486d5d..7ef7f22 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T16:31:56.843Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T16:34:08.281Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -2524,6 +2524,32 @@ container.append(sort.el); 现场演示:apps/docs/demo.html#sec-sortable +### masonry — 瀑布流 Masonry + +参数: + +- items(必填):unknown[] —— 每个卡片内容(字符串 / DOM / 响应式值);数组顺序即铺放顺序,列高不等由内容自己决定 +- columns:number —— 固定列数;传了即不随容器宽度变化,与 minColumnWidth 互斥 +- minColumnWidth(默认 240):number —— 自适应列宽下限(像素);缺省 240,容器越宽列数越多 +- gap(默认 var(--ua-space-3)):string —— 列间距与行间距(CSS 长度或令牌值);缺省 var(--ua-space-3) + +示例: + +``` +masonry({ minColumnWidth: 220, items: shots.map((s) => card({ title: s.title }, image({ src: s.url }))) }) +``` + +手把手: + +1. items 里直接放 card / image / 任意 DOM:每项由组件包一层 .ua-masonry-item,只有这一层带 break-inside: avoid,卡片才不会在列边界断成两截 +2. 不传 columns 就按 minColumnWidth 自适应:容器变宽自动加列、窄屏自动降到一列,不需要自己监听 window.resize +3. 要固定几列(比如大屏看板恒 4 列)就传 columns,此时 minColumnWidth 不再生效——两者互斥,别同时传指望它俩协商 +4. 间距传一次就够:gap 同时给 column-gap 与每项的 margin-bottom,列间距和行间距天然一致;改间距只改这一处 +5. 列高不等才是瀑布流的价值所在(图片墙、评论流、看板):等高内容请用 grid,瀑布流会把等高卡片铺成奇怪的错位 +6. 什么时候别用:需要严格的行对齐(表格化数据)、需要按列独立滚动(用 splitter / 多个 virtualList),以及首屏内容需要按阅读顺序严格横向(多列布局是竖向填充,读屏顺序与视觉列序一致但不等同于行序) + +现场演示:apps/docs/demo.html#sec-masonry + API 速览: - button(props):按钮,variant 三档视觉强度,disabled / loading 支持布尔或信号动态同步(loading 转圈并禁点),onClick 点击回调 @@ -2616,6 +2642,7 @@ API 速览: - progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘 - noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报 - sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄 +- masonry(props):瀑布流多列布局,列宽自适应或固定列数,卡片不被拆到两列 一行用法: diff --git a/llms.txt b/llms.txt index 5bd12e8..8bc73da 100644 --- a/llms.txt +++ b/llms.txt @@ -137,6 +137,7 @@ - progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘 - noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报 - sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄 + - masonry(props):瀑布流多列布局,列宽自适应或固定列数,卡片不被拆到两列 - 用法: `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/index.ts b/packages/skin/ui/src/index.ts index 59d7aff..14b9b12 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -88,3 +88,4 @@ export { heatmap } from './heatmap.js'; export { progressRing } from './progressRing.js'; export { noticeBar } from './noticeBar.js'; export { sortableList } from './sortableList.js'; +export { masonry } from './masonry.js'; diff --git a/packages/skin/ui/src/masonry.ts b/packages/skin/ui/src/masonry.ts new file mode 100644 index 0000000..e3d3b29 --- /dev/null +++ b/packages/skin/ui/src/masonry.ts @@ -0,0 +1,21 @@ +import { h } from '@ui-arch/frame'; +import type { MasonryProps } from './types.js'; + +/*** 瀑布流:CSS 多列布局,列数按容器宽度自适应或显式指定;每项单独包一层才能 break-inside 不拆断 ***/ +export function masonry(props: MasonryProps): HTMLElement { + // —— 状态集中声明 —— + const adaptive = !props.columns; // 未指定列数即自适应(column-width 让浏览器自己算列数) + const width = props.minColumnWidth ?? 240; // 自适应时的最小列宽(像素) + const gap = props.gap ?? 'var(--ua-space-3)'; // 列间距与行间距同源,避免两处各写一份 + const root = h('div', { class: 'ua-masonry' }) as HTMLElement; + root.style.columnGap = gap; + if (adaptive) root.style.columnWidth = `${width}px`; // 自适应:容器越宽列数越多,窄屏自动降到一列 + else root.style.columnCount = String(props.columns); // 显式列数:与 column-width 互斥,固定几列就几列 + + props.items.forEach((item) => { + const cell = h('div', { class: 'ua-masonry-item' }, item); + cell.style.marginBottom = gap; // 行间距与列间距同值,视觉上才是均匀网格 + root.append(cell); + }); + return root; +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 20fef17..ce8a32b 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -1146,3 +1146,11 @@ export interface SortableListHandle { move: (from: number, to: number) => void; // 程序化搬位:下标越界或禁用时静默忽略 order: () => string[]; // 读取当前顺序(以 DOM 为唯一真相) } + +/*** 瀑布流入参 ***/ +export interface MasonryProps { + items: unknown[]; // 每个卡片内容(字符串 / DOM / 响应式值),按数组顺序铺进最短列 + columns?: number; // 固定列数;传了即不随容器宽度变化,与 minColumnWidth 互斥 + minColumnWidth?: number; // 自适应列宽下限(像素);缺省 240,容器越宽列数越多 + gap?: string; // 列间距与行间距(CSS 长度或令牌值);缺省 var(--ua-space-3) +} diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index f62ba8b..781ea46 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -102,7 +102,8 @@ "heatmap(props):日历热力图,按周列 × 星期行铺格子、四档浓度表示当天数量,悬停看明细", "progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘", "noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报", - "sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄" + "sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄", + "masonry(props):瀑布流多列布局,列宽自适应或固定列数,卡片不被拆到两列" ], "keyTypes": [ "ButtonProps", @@ -4567,6 +4568,45 @@ "外部要按代码调整顺序用句柄 move(from, to),下标越界或禁用时静默忽略;order() 以 DOM 为唯一真相回读当前顺序", "什么时候别用:条目超过一屏(拖到边缘不会自动滚动,改用筛选或上下按钮)、需要跨列表搬运(用两个实例并自己管状态)" ] + }, + { + "id": "masonry", + "title": "瀑布流 Masonry", + "demo": "sec-masonry", + "props": [ + { + "n": "items", + "t": "unknown[]", + "r": 1, + "d": "每个卡片内容(字符串 / DOM / 响应式值);数组顺序即铺放顺序,列高不等由内容自己决定" + }, + { + "n": "columns", + "t": "number", + "d": "固定列数;传了即不随容器宽度变化,与 minColumnWidth 互斥" + }, + { + "n": "minColumnWidth", + "t": "number", + "d": "自适应列宽下限(像素);缺省 240,容器越宽列数越多", + "df": "240" + }, + { + "n": "gap", + "t": "string", + "d": "列间距与行间距(CSS 长度或令牌值);缺省 var(--ua-space-3)", + "df": "var(--ua-space-3)" + } + ], + "example": "masonry({ minColumnWidth: 220, items: shots.map((s) => card({ title: s.title }, image({ src: s.url }))) })", + "steps": [ + "items 里直接放 card / image / 任意 DOM:每项由组件包一层 .ua-masonry-item,只有这一层带 break-inside: avoid,卡片才不会在列边界断成两截", + "不传 columns 就按 minColumnWidth 自适应:容器变宽自动加列、窄屏自动降到一列,不需要自己监听 window.resize", + "要固定几列(比如大屏看板恒 4 列)就传 columns,此时 minColumnWidth 不再生效——两者互斥,别同时传指望它俩协商", + "间距传一次就够:gap 同时给 column-gap 与每项的 margin-bottom,列间距和行间距天然一致;改间距只改这一处", + "列高不等才是瀑布流的价值所在(图片墙、评论流、看板):等高内容请用 grid,瀑布流会把等高卡片铺成奇怪的错位", + "什么时候别用:需要严格的行对齐(表格化数据)、需要按列独立滚动(用 splitter / 多个 virtualList),以及首屏内容需要按阅读顺序严格横向(多列布局是竖向填充,读屏顺序与视觉列序一致但不等同于行序)" + ] } ] } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index acf8d86..898deab 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -6089,3 +6089,12 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } transition-property: color, background-color, border-color, box-shadow, opacity, fill, stroke; /* 只保留不产生空间位移的属性 */ } } + +/* —— 瀑布流:多列布局,列宽自适应或固定列数 —— */ +.ua-masonry { + display: block; /* 多列容器不能是 flex/grid,否则 column-width 失效 */ +} +.ua-masonry-item { + break-inside: avoid; /* 卡片不被拆到两列:图片墙里断半截最刺眼 */ + page-break-inside: avoid; /* 打印时同样不拆断 */ +}