iter(g35): 新增 notification 通知卡片组件

This commit is contained in:
HIsheR
2026-10-26 11:46:54 +08:00
parent 8711fae1af
commit 8b5f355d7e
13 changed files with 302 additions and 9 deletions
+1
View File
@@ -18,6 +18,7 @@ export { avatar } from './avatar.js';
export { image } from './image.js';
export { alert } from './alert.js';
export { toast, showToast } from './toast.js';
export { notification } from './notification.js';
export { tooltip } from './tooltip.js';
export { popover } from './popover.js';
export { flip } from './position.js';
+56
View File
@@ -0,0 +1,56 @@
import { h } from '@ui-arch/frame';
import type { NotificationProps } from './types.js';
let host: HTMLElement | null = null; // 右下角堆叠容器:首条通知出现时懒创建
const MAX_STACK = 3; // 同屏最多条数,超出挤掉最早一条
/*** 确保堆叠容器存在:fixed 挂 body 末尾,与右上角 toast 分区不打架 ***/
function ensureHost(): HTMLElement {
if (host) return host;
host = h('div', { class: 'ua-notice-host' });
document.body.appendChild(host);
return host;
}
/*** 退场并移除:摘 ua-open 触发过渡,transitionend 与 320ms 超时二选一兜底 ***/
function dismiss(item: HTMLElement): void {
item.classList.remove('ua-open');
item.addEventListener('transitionend', () => item.remove(), { once: true });
window.setTimeout(() => item.isConnected && item.remove(), 320); // reduced-motion 下不触发 transition
}
/*** 通知卡片:标题 + 描述 + 可选操作区,返回 { el, close } 句柄;duration 为 0 表示常驻 ***/
export function notification(props: NotificationProps): { el: HTMLElement; close: () => void } {
// —— 状态集中声明 ——
const duration = props.duration ?? 0; // 缺省常驻:通知通常需要用户读完
const closable = props.closable ?? true; // 缺省带关闭按钮
let timer = 0; // 自动关闭计时器句柄
const closeBtn = h('button', { type: 'button', class: 'ua-notice-close', 'aria-label': '关闭通知' }, '×') as HTMLButtonElement; // 关闭按钮,回调在下方绑定
const el = h(
'div',
{ class: `ua-notice ua-notice-${props.variant ?? 'info'}`, role: 'alert' },
h(
'div',
{ class: 'ua-notice-head' },
h('span', { class: 'ua-notice-dot', 'aria-hidden': 'true' }),
h('span', { class: 'ua-notice-title' }, props.title),
closable ? closeBtn : null,
),
props.description ? h('div', { class: 'ua-notice-desc' }, props.description) : null,
props.action ? h('div', { class: 'ua-notice-action' }, props.action) : null,
) as HTMLElement;
/*** 关闭:清计时器后走退场动画 ***/
const close = () => {
window.clearTimeout(timer);
dismiss(el);
};
closeBtn.addEventListener('click', () => close());
const stack = ensureHost();
if (stack.childElementCount >= MAX_STACK) stack.firstElementChild?.remove(); // 超出上限挤掉最早一条
stack.appendChild(el);
requestAnimationFrame(() => el.classList.add('ua-open')); // 下一帧入场,保证过渡生效
if (duration > 0) timer = window.setTimeout(() => dismiss(el), duration); // 常驻(0)不设定时器
return { el, close };
}
+10
View File
@@ -420,6 +420,16 @@ export interface ListProps {
onItemClick?: (item: ListItem, index: number) => void; // 条目点击回调;提供后条目可点击并带 hover 反馈
}
/*** 通知入参 ***/
export interface NotificationProps {
title: string; // 通知标题(必填)
description?: string; // 补充说明;缺省不渲染
variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info
duration?: number; // 自动关闭毫秒;缺省 0 = 常驻(通知通常要读完)
closable?: boolean; // 是否显示关闭按钮;缺省 true
action?: unknown; // 操作区内容(如按钮);缺省不渲染
}
/*** 文件选择入参 ***/
export interface UploadProps {
accept?: string; // 接受的类型(同原生 accept),如 'image/*,.pdf'