iter(g24): 新增 list 列表组件(含空状态回落与可点击条目)

This commit is contained in:
HIsheR
2026-10-26 11:33:27 +08:00
parent 2b65922c7e
commit e6b4a84085
13 changed files with 297 additions and 9 deletions
+1
View File
@@ -31,6 +31,7 @@ export { stepper } from './stepper.js';
export { rate } from './rate.js';
export { empty } from './empty.js';
export { divider } from './divider.js';
export { list } from './list.js';
export { descriptions } from './descriptions.js';
export { statistic } from './statistic.js';
export { timeline } from './timeline.js';
+36
View File
@@ -0,0 +1,36 @@
import { h } from '@ui-arch/frame';
import type { ListProps } from './types.js';
import { empty } from './empty.js';
/*** 列表:header/footer 外壳 + 逐条 items(avatar / title / description / extra),传 onItemClick 时条目可点击,无数据回落空状态 ***/
export function list(props: ListProps): HTMLElement {
// —— 状态集中声明 ——
const clickable = typeof props.onItemClick === 'function'; // 仅提供回调时才加可点击样式
const rootClass = props.bordered ? 'ua-list ua-list-bordered' : 'ua-list'; // 外边框由 bordered 决定
const root = h('div', { class: rootClass }) as HTMLElement; // 列表根容器
if (props.header) root.append(h('div', { class: 'ua-list-header' }, props.header));
if (!props.items.length) {
root.append(empty({ title: props.emptyText ?? '暂无数据' })); // 空列表复用空状态组件
return root;
}
const body = h('div', { class: 'ua-list-body', role: 'list' }) as HTMLElement; // 条目容器,供读屏识别列表语义
props.items.forEach((item, index) => {
const row = h(
'div',
{ class: clickable ? 'ua-list-item ua-list-clickable' : 'ua-list-item', role: 'listitem' },
item.avatar ? h('div', { class: 'ua-list-avatar' }, item.avatar) : null, // 左侧头像/图标,缺省不留位
h(
'div',
{ class: 'ua-list-main' },
h('div', { class: 'ua-list-title' }, item.title),
item.description ? h('div', { class: 'ua-list-desc' }, item.description) : null,
),
item.extra ? h('div', { class: 'ua-list-extra' }, item.extra) : null, // 右侧附加(时间、标签)
);
if (clickable) row.addEventListener('click', () => props.onItemClick?.(item, index)); // 点击回传条目与下标
body.append(row);
});
root.append(body);
if (props.footer) root.append(h('div', { class: 'ua-list-footer' }, props.footer));
return root;
}
+18
View File
@@ -402,4 +402,22 @@ export interface StatisticProps {
trend?: 'up' | 'down'; // 环比方向:up 红 / down 绿(A 股口径);缺省不渲染
}
/*** 列表条目 ***/
export interface ListItem {
title: string; // 主标题(必填)
description?: string; // 次要说明;缺省不渲染
avatar?: unknown; // 左侧头像/图标;缺省不留位
extra?: unknown; // 右侧附加内容(时间、标签等);缺省不留位
}
/*** 列表入参 ***/
export interface ListProps {
items: ListItem[]; // 条目列表,按序渲染
header?: string; // 列表头标题;缺省不渲染
footer?: string; // 列表尾说明;缺省不渲染
bordered?: boolean; // 是否带外边框与分隔线;缺省 false
emptyText?: string; // 无数据占位文案;缺省「暂无数据」
onItemClick?: (item: ListItem, index: number) => void; // 条目点击回调;提供后条目可点击并带 hover 反馈
}
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方
+48 -1
View File
@@ -52,7 +52,8 @@
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点",
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列",
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值"
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值",
"list(props):列表,header/footer 外壳 + 条目 avatar/title/description/extra,onItemClick 让条目可点击,items 为空回落空状态"
],
"keyTypes": [
"ButtonProps",
@@ -1633,6 +1634,52 @@
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
"用户开启「减弱动效」时组件直接落终值,无需调用方判断"
]
},
{
"id": "list",
"title": "列表 List",
"demo": "sec-list",
"props": [
{
"n": "items",
"t": "ListItem[]",
"r": 1,
"d": "条目列表,每项 { title, description?, avatar?, extra? }"
},
{
"n": "header",
"t": "string",
"d": "列表头标题;缺省不渲染"
},
{
"n": "footer",
"t": "string",
"d": "列表尾说明;缺省不渲染"
},
{
"n": "bordered",
"t": "boolean",
"d": "是否带外边框与分隔线;缺省 false"
},
{
"n": "emptyText",
"t": "string",
"d": "无数据占位文案;缺省「暂无数据」"
},
{
"n": "onItemClick",
"t": "(item: ListItem, index: number) => void",
"k": 1,
"d": "条目点击回调;提供后条目可点击并带 hover 反馈"
}
],
"example": "list({ header: '待办', bordered: true, items: [{ title: '报销单 RB-2048', description: '等待复核', avatar: avatar({ name: '王' }) }], onItemClick: (item) => console.log(item.title) })",
"steps": [
"准备 items 数组,每项至少给 title,description 作次要说明",
"avatar 传头像组件(如 avatar({ name }))、extra 传右侧附加(如 tag(...)),不传该侧不留位",
"需要点击时传 onItemClick(item, index),条目自动加 hover 底色;bordered: true 给外边框与分隔线",
"items 为空数组时自动渲染 empty 占位,文案可用 emptyText 覆盖"
]
}
]
}
+75
View File
@@ -1996,3 +1996,78 @@ button.ua-step-dot:hover {
transition: none !important;
}
}
/* —— 列表 —— */
.ua-list {
display: flex;
flex-direction: column;
border-radius: var(--ua-radius-md);
overflow: hidden;
}
.ua-list-bordered {
border: 1px solid var(--ua-color-border);
background: var(--ua-color-surface);
}
.ua-list-header,
.ua-list-footer {
padding: var(--ua-space-2) var(--ua-space-3);
font-size: var(--ua-font-size-sm);
color: var(--ua-color-text-muted);
}
.ua-list-header {
font-weight: 600;
color: var(--ua-color-text);
border-bottom: 1px solid var(--ua-color-border);
}
.ua-list-footer {
border-top: 1px solid var(--ua-color-border);
}
.ua-list-body {
display: flex;
flex-direction: column;
}
.ua-list-item {
display: flex;
align-items: center;
gap: var(--ua-space-2);
padding: var(--ua-space-2) var(--ua-space-3);
transition: background 0.15s ease;
}
.ua-list-item + .ua-list-item {
border-top: 1px solid var(--ua-color-border);
}
.ua-list-clickable {
cursor: pointer;
}
.ua-list-clickable:hover {
background: color-mix(in srgb, var(--ua-color-accent) 8%, transparent); /* 低视觉比重的悬停底色 */
}
.ua-list-avatar {
flex: none;
display: flex;
}
.ua-list-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.ua-list-title {
font-size: var(--ua-font-size-md);
color: var(--ua-color-text);
}
.ua-list-desc {
font-size: var(--ua-font-size-sm);
color: var(--ua-color-text-muted);
}
.ua-list-extra {
flex: none;
font-size: var(--ua-font-size-sm);
color: var(--ua-color-text-muted);
}
@media (prefers-reduced-motion: reduce) {
.ua-list-item {
transition: none;
}
}