iter: b02 新增 toast 消息提示组件(四语义工厂 + 右上角堆叠 + 3s 自动消失)

This commit is contained in:
HIsheR
2026-10-23 15:50:20 +08:00
parent aaf6fdc5ac
commit ece38bcac9
11 changed files with 202 additions and 10 deletions
+1
View File
@@ -9,6 +9,7 @@ export { radioGroup } from './radioGroup.js';
export { card } from './card.js';
export { badge } from './badge.js';
export { alert } from './alert.js';
export { toast, showToast } from './toast.js';
export { tooltip } from './tooltip.js';
export { spinner } from './spinner.js';
export { progress } from './progress.js';
+37
View File
@@ -0,0 +1,37 @@
import { h } from '@ui-arch/frame';
import type { ToastProps } from './types.js';
let host: HTMLElement | null = null; // 全局右上角堆叠容器:首条消息出现时懒创建
let seq = 0; // 自增序号:为每条 toast 生成唯一 id,辅助无障碍定位
/*** 确保堆叠容器存在:fixed 挂在 body 末尾,后续消息依次向上堆叠 ***/
function ensureHost(): HTMLElement {
if (host) return host;
host = h('div', { class: 'ua-toast-host' });
document.body.appendChild(host);
return host;
}
/*** 弹出一条 toast:语义色由 variant 决定,duration 毫秒后自动淡出并移除节点 ***/
export function showToast(props: ToastProps): HTMLElement {
// —— 状态集中声明 ——
const item = h('div', { class: `ua-toast ua-toast-${props.variant ?? 'info'}`, role: 'status', id: `ua-toast-${++seq}` }, props.message);
const duration = props.duration ?? 3000; // 停留时长毫秒
ensureHost().appendChild(item);
requestAnimationFrame(() => item.classList.add('ua-open')); // 下一帧入场,保证过渡生效
window.setTimeout(() => {
item.classList.remove('ua-open'); // 先退场再摘除,退场动画与 reduced-motion 兜底都安全
item.addEventListener('transitionend', () => item.remove(), { once: true });
window.setTimeout(() => item.isConnected && item.remove(), 320); // transition 不触发时的兜底移除
}, duration);
return item;
}
/*** toast 语义快捷工厂:toast.info / success / warning / error(message, duration?) ***/
export const toast = {
info: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'info' }), // 中性信息
success: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'success' }), // 成功反馈
warning: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'warning' }), // 警告
error: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'danger' }), // 错误
};
+7
View File
@@ -92,6 +92,13 @@ export interface AlertProps {
variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info
}
/*** toast 消息提示入参 ***/
export interface ToastProps {
message: string; // 提示文案
duration?: number; // 自动消失毫秒;缺省 3000
variant?: 'info' | 'success' | 'warning' | 'danger'; // 语义色;缺省 info
}
/*** 加载指示器入参 ***/
export interface SpinnerProps {
size?: number; // 直径像素;缺省 18
+34 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@ui-arch/ui",
"type": "skin-dom",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / tooltip / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"description": "DOM 皮肤组件库:button / textField / textarea / checkbox / switch / select / radioGroup / card / badge / alert / toast / tooltip / spinner / progress / tabs / modal / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。",
"tags": [
"components",
"dom",
@@ -23,6 +23,7 @@
"card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger",
"alert(props):提示条,variant = info | success | warning | danger",
"toast.info/success/warning/error(message, duration?):消息提示工厂,右上角堆叠,默认 3s 自动消失,出入场微动画;showToast(props) 为完整入参版本",
"tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起",
"spinner(props):加载指示器,size 控制直径",
"progress(props):进度条,轨道 + 渐变流动填充,value 信号驱动 0-100,满格切 success 色",
@@ -44,6 +45,7 @@
"CardProps",
"BadgeProps",
"AlertProps",
"ToastProps",
"TooltipProps",
"SpinnerProps",
"ProgressProps",
@@ -63,7 +65,7 @@
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
],
"docs": {
"intro": "17 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"intro": "18 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
"steps": [
"安装依赖:pnpm add @ui-arch/ui @ui-arch/frame(workspace 内自动链接)",
"引入样式:在应用入口 import '@ui-arch/ui/ui.css',暗色主题在根元素设 data-theme='dark'",
@@ -358,6 +360,36 @@
"传入 message 即可,入场自带下滑动画"
]
},
{
"id": "toast",
"title": "消息提示 Toast",
"demo": "sec-toast",
"props": [
{
"n": "message",
"t": "string",
"r": 1,
"d": "提示文案"
},
{
"n": "duration",
"t": "number",
"d": "自动消失毫秒;缺省 3000"
},
{
"n": "variant",
"t": "'info' | 'success' | 'warning' | 'danger'",
"d": "语义色;缺省 info(toast.error 对应 danger)"
}
],
"example": "toast.success('保存成功'); toast.error('网络请求失败', 5000)",
"steps": [
"直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器",
"不传 duration 时默认停留 3 秒后自动淡出并移除节点",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入"
]
},
{
"id": "tooltip",
"title": "悬浮提示 Tooltip",
+43
View File
@@ -379,6 +379,48 @@
background: color-mix(in srgb, var(--ua-color-danger) 12%, transparent);
}
/* —— toast 消息提示:右上角堆叠,转瞬即逝 —— */
.ua-toast-host {
position: fixed;
top: var(--ua-space-4);
right: var(--ua-space-4);
z-index: 50;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--ua-space-2);
pointer-events: none;
}
.ua-toast {
padding: var(--ua-space-2) var(--ua-space-3);
border-radius: var(--ua-radius-md);
border: 1px solid;
font-size: var(--ua-font-size-md);
opacity: 0;
transform: translateX(12px);
transition: opacity 0.22s ease-out, transform 0.22s ease-out;
}
.ua-toast.ua-open {
opacity: 1;
transform: translateX(0);
}
.ua-toast-info {
border-color: var(--ua-color-accent);
background: color-mix(in srgb, var(--ua-color-accent) 8%, var(--ua-color-bg));
}
.ua-toast-success {
border-color: var(--ua-color-success);
background: color-mix(in srgb, var(--ua-color-success) 10%, var(--ua-color-bg));
}
.ua-toast-warning {
border-color: var(--ua-color-warning);
background: color-mix(in srgb, var(--ua-color-warning) 10%, var(--ua-color-bg));
}
.ua-toast-danger {
border-color: var(--ua-color-danger);
background: color-mix(in srgb, var(--ua-color-danger) 10%, var(--ua-color-bg));
}
/* —— 加载指示器 —— */
.ua-spinner {
display: inline-block;
@@ -664,6 +706,7 @@
.ua-progress-fill::after,
.ua-alert,
.ua-tip,
.ua-toast,
.ua-form-error,
.ua-modal,
.ua-modal-overlay {