qwen-code/packages/web-shell/client/components/ToastHost.tsx
ermin.zem 5c82857fea
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
Add harness infrastructure for web-shell package (#6517)
* test(web-shell): add browser and lint harness

* test(web-shell): harden browser smoke harness

* fix(web-shell): guard mock daemon model state

* test(web-shell): remove unused scenario harness

* fix(web-shell): remove stale lint disables

* test(web-shell): make matchMedia stub writable

* fix(web-shell): exclude tests from package typecheck

* test(web-shell): tighten mock daemon route contract

* Update packages/web-shell/client/e2e/utils/mockDaemon.ts

Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>

* test(web-shell): clear stale SSE connections

* ci(web-shell): gate smoke on full CI profile

---------

Co-authored-by: ermin.zem <ermin.zem@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-09 08:11:58 +00:00

77 lines
1.7 KiB
TypeScript

import { useEffect } from 'react';
import { useI18n } from '../i18n';
import styles from './ToastHost.module.css';
export type ToastTone = 'info' | 'warning' | 'error' | 'success';
export interface WebShellToast {
id: string;
tone: ToastTone;
message: string;
}
interface ToastHostProps {
toasts: readonly WebShellToast[];
onDismiss: (id: string) => void;
autoDismissMs?: number;
}
export function ToastHost({
toasts,
onDismiss,
autoDismissMs = 5000,
}: ToastHostProps) {
if (toasts.length === 0) return null;
return (
<div
className={styles.host}
role="status"
aria-live="polite"
data-web-shell-toast-host
>
{toasts.map((toast) => (
<ToastItem
key={toast.id}
toast={toast}
onDismiss={onDismiss}
autoDismissMs={autoDismissMs}
/>
))}
</div>
);
}
function ToastItem({
toast,
onDismiss,
autoDismissMs,
}: {
toast: WebShellToast;
onDismiss: (id: string) => void;
autoDismissMs: number;
}) {
const { t } = useI18n();
useEffect(() => {
const timer = window.setTimeout(() => onDismiss(toast.id), autoDismissMs);
return () => window.clearTimeout(timer);
}, [autoDismissMs, onDismiss, toast.id]);
return (
<div
className={`${styles.toast} ${styles[toast.tone]}`}
data-web-shell-toast
data-tone={toast.tone}
>
<div className={styles.message}>{toast.message}</div>
<button
type="button"
className={styles.close}
onClick={() => onDismiss(toast.id)}
aria-label={t('toast.dismiss')}
title={t('toast.dismissShort')}
>
x
</button>
</div>
);
}