iter(g09): 新增 descriptions 描述列表组件

This commit is contained in:
HIsheR
2026-10-26 10:23:22 +08:00
parent 086831d474
commit d8d45fcac0
12 changed files with 268 additions and 12 deletions
+21
View File
@@ -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);
}
+1
View File
@@ -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';
+16
View File
@@ -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 }; // 再导出常用内核类型,方便调用方
+50 -2
View File
@@ -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"
]
}
]
}
+64
View File
@@ -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);
}
}