Files
UI/packages/skin/ui/src/notification.ts
T

57 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 };
}