From d8d45fcac01b26a9a228b484abfc0f6dfc62a8c8 Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 10:23:22 +0800 Subject: [PATCH] =?UTF-8?q?iter(g09):=20=E6=96=B0=E5=A2=9E=20descriptions?= =?UTF-8?q?=20=E6=8F=8F=E8=BF=B0=E5=88=97=E8=A1=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 54 +++++++++++++++++++++-- AGENTS.md | 2 +- ARCHITECTURE.md | 2 +- README.md | 8 ++-- apps/demo/main.ts | 29 +++++++++++++ llms-full.txt | 30 ++++++++++++- llms.txt | 1 + packages/skin/ui/src/descriptions.ts | 21 +++++++++ packages/skin/ui/src/index.ts | 1 + packages/skin/ui/src/types.ts | 16 +++++++ packages/skin/ui/ua.manifest.json | 52 +++++++++++++++++++++- packages/skin/ui/ui.css | 64 ++++++++++++++++++++++++++++ 12 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 packages/skin/ui/src/descriptions.ts diff --git a/.ua/index.json b/.ua/index.json index c920b4c..4447c41 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T02:20:07.266Z", + "generatedAt": "2026-10-26T02:22:42.729Z", "repoRoot": ".", "packages": [ { @@ -178,7 +178,8 @@ "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽", "timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点", - "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用" + "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用", + "descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列" ], "keyTypes": [ "ButtonProps", @@ -218,7 +219,9 @@ "AvatarProps", "TimelineItem", "TimelineProps", - "SegmentedProps" + "SegmentedProps", + "DescriptionItem", + "DescriptionsProps" ] }, "usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')", @@ -1663,6 +1666,51 @@ "滑块几何由组件测量按钮位置得到,不要在外部改按钮宽度 / 内边距,否则需自行重渲染", "需要带图标或更复杂布局的分段,改用 radioGroup + 自定义样式" ] + }, + { + "id": "descriptions", + "title": "描述列表 Descriptions", + "demo": "sec-descriptions", + "props": [ + { + "n": "items", + "t": "DescriptionItem[]", + "r": 1, + "d": "描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数)" + }, + { + "n": "column", + "t": "1 | 2 | 3 | 4", + "d": "桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列", + "df": "3" + }, + { + "n": "bordered", + "t": "boolean", + "d": "表格形态(带分隔线与字段名底纹);缺省 false 纯网格", + "df": "false" + }, + { + "n": "size", + "t": "'sm' | 'md'", + "d": "尺寸档位;缺省 md", + "df": "md" + }, + { + "n": "title", + "t": "string", + "d": "分组标题;缺省不渲染标题行" + } + ], + "example": "descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] })", + "steps": [ + "详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验)", + "column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行", + "value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容", + "bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌", + "窄屏自动降为单列,无需在调用方写媒体查询", + "纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value" + ] } ] }, diff --git a/AGENTS.md b/AGENTS.md index a740fa3..9c8a880 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):36 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):37 个 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 585ebc2..c7fe7be 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 36 个组件 + ui.css +│ │ └── ui/ # 37 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index 615c603..4f8378d 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 ``` -## 组件(共 36 个) +## 组件(共 37 个) -accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip +accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip -分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / tag / avatar / card)、其他(divider / timeline)。 +分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / descriptions / tag / avatar / card)、其他(divider / timeline)。 ## 目录 @@ -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:36 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:37 个 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 0b91136..49187b3 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -17,6 +17,7 @@ import { card, checkbox, dataTable, + descriptions, divider, drawer, empty, @@ -448,6 +449,34 @@ const content = h( button({ label: '导出', variant: 'ghost' }), ), ), + section( + 'sec-descriptions', + '描述列表', + 'descriptions:label : value 网格,column 定列数(窄屏自动单列);bordered 切表格形态,span 让长字段占整行。', + descriptions({ + title: '订单信息(纯网格 · 3 列)', + column: 3, + items: [ + { label: '订单号', value: 'RB-2048' }, + { label: '下单人', value: '王小明' }, + { label: '金额', value: '¥1,280.00' }, + { label: '状态', value: tag({ label: '待复核', variant: 'warning' }) }, + { label: '备注', value: '需要开专用发票,抬头与税号见附件。', span: 2 }, + ], + }), + divider({ dashed: true }), + descriptions({ + title: '物流信息(表格形态 · 2 列)', + column: 2, + bordered: true, + size: 'sm', + items: [ + { label: '承运商', value: '顺丰速运' }, + { label: '运单号', value: 'SF1234567890' }, + { label: '收件地址', value: '浙江省杭州市余杭区文一西路 969 号', span: 2 }, + ], + }), + ), section( 'sec-segmented', '分段控制器', diff --git a/llms-full.txt b/llms-full.txt index 99bcdf1..55ce016 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T02:20:07.266Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T02:22:42.729Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -950,6 +950,33 @@ segmented({ options: [{ value: 'day', label: '日' }, { value: 'week', label: ' 现场演示:apps/docs/demo.html#sec-segmented +### descriptions — 描述列表 Descriptions + +参数: + +- items(必填):DescriptionItem[] —— 描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数) +- column(默认 3):1 | 2 | 3 | 4 —— 桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列 +- bordered(默认 false):boolean —— 表格形态(带分隔线与字段名底纹);缺省 false 纯网格 +- size(默认 md):'sm' | 'md' —— 尺寸档位;缺省 md +- title:string —— 分组标题;缺省不渲染标题行 + +示例: + +``` +descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] }) +``` + +手把手: + +1. 详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验) +2. column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行 +3. value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容 +4. bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌 +5. 窄屏自动降为单列,无需在调用方写媒体查询 +6. 纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value + +现场演示:apps/docs/demo.html#sec-descriptions + API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 @@ -989,6 +1016,7 @@ API 速览: - image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 - timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点 - segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用 +- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列 一行用法: diff --git a/llms.txt b/llms.txt index ce7fd46..af73065 100644 --- a/llms.txt +++ b/llms.txt @@ -84,6 +84,7 @@ - image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 - timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点 - segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用 + - descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列 - 用法: `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/descriptions.ts b/packages/skin/ui/src/descriptions.ts new file mode 100644 index 0000000..1a1f083 --- /dev/null +++ b/packages/skin/ui/src/descriptions.ts @@ -0,0 +1,21 @@ +import { h } from '@ui-arch/frame'; +import type { DescriptionItem, DescriptionsProps } from './types.js'; + +/*** 单个描述项:字段名 + 字段值,span 控制跨列(超出列数时按整行处理) ***/ +function cell(item: DescriptionItem, column: number): HTMLElement { + const cellEl = h('div', { class: 'ua-desc-item' }, + h('div', { class: 'ua-desc-label' }, item.label), + h('div', { class: 'ua-desc-value' }, item.value)); + cellEl.style.gridColumn = `span ${Math.min(item.span ?? 1, column)}`; // 跨列不越界 + return cellEl; +} + +/*** 描述列表:label:value 两栏网格,column 定桌面列数,窄屏由样式表降到单列;bordered 走表格形态 ***/ +export function descriptions(props: DescriptionsProps): HTMLElement { + const column = props.column ?? 3; + const grid = h('div', { class: `ua-desc ua-desc-${props.size ?? 'md'}${props.bordered ? ' is-bordered' : ''}` }, + ...props.items.map((item) => cell(item, column))); + grid.style.setProperty('--ua-desc-col', String(column)); // 列数交给 CSS 变量,媒体查询可在窄屏覆盖 + if (!props.title) return grid; + return h('div', { class: 'ua-desc-wrap' }, h('div', { class: 'ua-desc-title' }, props.title), grid); +} diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 53b2f81..7d1ea15 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -30,6 +30,7 @@ export { stepper } from './stepper.js'; export { rate } from './rate.js'; export { empty } from './empty.js'; export { divider } from './divider.js'; +export { descriptions } from './descriptions.js'; export { timeline } from './timeline.js'; export { modal } from './modal.js'; export { drawer } from './drawer.js'; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index e8341b7..29a473a 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -372,4 +372,20 @@ export interface TimelineProps { pending?: boolean; // 末尾追加「进行中」虚线节点,表示流程未结束;缺省 false } +/*** 描述项:详情页的「字段名 : 字段值」一行 ***/ +export interface DescriptionItem { + label: string; // 字段名(左栏) + value: unknown; // 字段值(右栏):字符串 / DOM 节点 / 响应式函数 + span?: number; // 占列数;缺省 1,超过 column 时按整行占满 +} + +/*** 描述列表入参 ***/ +export interface DescriptionsProps { + items: DescriptionItem[]; // 描述项列表,按序铺进网格 + column?: 1 | 2 | 3 | 4; // 桌面列数;缺省 3(窄屏自动降为单列) + bordered?: boolean; // 表格形态(带分隔线与底纹);缺省 false(纯网格) + size?: 'sm' | 'md'; // 尺寸档位;缺省 md + title?: string; // 分组标题;缺省不渲染 +} + export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 5f75420..1b2ea8c 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -49,7 +49,8 @@ "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽", "timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点", - "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用" + "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用", + "descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列" ], "keyTypes": [ "ButtonProps", @@ -89,7 +90,9 @@ "AvatarProps", "TimelineItem", "TimelineProps", - "SegmentedProps" + "SegmentedProps", + "DescriptionItem", + "DescriptionsProps" ] }, "usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')", @@ -1534,6 +1537,51 @@ "滑块几何由组件测量按钮位置得到,不要在外部改按钮宽度 / 内边距,否则需自行重渲染", "需要带图标或更复杂布局的分段,改用 radioGroup + 自定义样式" ] + }, + { + "id": "descriptions", + "title": "描述列表 Descriptions", + "demo": "sec-descriptions", + "props": [ + { + "n": "items", + "t": "DescriptionItem[]", + "r": 1, + "d": "描述项列表:每项含 label(字段名)、value(字符串 / DOM / 响应式函数)、span(跨列数)" + }, + { + "n": "column", + "t": "1 | 2 | 3 | 4", + "d": "桌面列数;缺省 3,窄屏(≤640px)由样式表降为单列", + "df": "3" + }, + { + "n": "bordered", + "t": "boolean", + "d": "表格形态(带分隔线与字段名底纹);缺省 false 纯网格", + "df": "false" + }, + { + "n": "size", + "t": "'sm' | 'md'", + "d": "尺寸档位;缺省 md", + "df": "md" + }, + { + "n": "title", + "t": "string", + "d": "分组标题;缺省不渲染标题行" + } + ], + "example": "descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label: '订单号', value: 'RB-2048' }, { label: '金额', value: '¥1,280.00' }, { label: '备注', value: '加急', span: 2 }] })", + "steps": [ + "详情页 / 抽屉里展示只读字段用它,不要用 form(form 承载输入与校验)", + "column 按信息密度定:字段多且短用 3-4 列,字段长(地址、备注)用 2 列并配 span 占整行", + "value 支持任意 DOM:放 tag、badge、button 都可以;要给值做响应式更新就传函数 () => 内容", + "bordered: true 是表格形态(适合打印与密集信息),纯网格形态更轻,适合卡片内嵌", + "窄屏自动降为单列,无需在调用方写媒体查询", + "纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value" + ] } ] } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 882aec2..0cb354a 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -1816,3 +1816,67 @@ button.ua-step-dot:hover { transition: none !important; } } + +/* —— 描述列表:label : value 网格,列数走 --ua-desc-col,窄屏自动单列 —— */ +.ua-desc { + display: grid; + grid-template-columns: repeat(var(--ua-desc-col, 3), minmax(0, 1fr)); + gap: var(--ua-space-2) var(--ua-space-3); +} +.ua-desc-wrap { + display: flex; + flex-direction: column; + gap: var(--ua-space-2); +} +.ua-desc-title { + color: var(--ua-color-text); + font-size: var(--ua-font-size-md); + font-weight: 600; +} +.ua-desc-item { + display: flex; + gap: var(--ua-space-2); + min-width: 0; + padding: 4px 0; +} +.ua-desc-label { + flex: 0 0 auto; + color: var(--ua-color-text-muted); + font-size: var(--ua-font-size-md); +} +.ua-desc-value { + min-width: 0; + color: var(--ua-color-text); + font-size: var(--ua-font-size-md); + overflow-wrap: anywhere; +} +.ua-desc-sm .ua-desc-label, +.ua-desc-sm .ua-desc-value { + font-size: var(--ua-font-size-sm); +} +.ua-desc.is-bordered { + gap: 0; + border: 1px solid var(--ua-color-border); + border-radius: var(--ua-radius-md); + overflow: hidden; +} +.ua-desc.is-bordered .ua-desc-item { + padding: 0; + border-right: 1px solid var(--ua-color-border); + border-bottom: 1px solid var(--ua-color-border); +} +.ua-desc.is-bordered .ua-desc-label { + padding: 6px 10px; + background: color-mix(in srgb, var(--ua-color-text) 4%, transparent); + border-right: 1px solid var(--ua-color-border); + white-space: nowrap; +} +.ua-desc.is-bordered .ua-desc-value { + padding: 6px 10px; + flex: 1; +} +@media (max-width: 640px) { + .ua-desc { + grid-template-columns: minmax(0, 1fr); + } +}