bedee3ba8d
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
import { useToastState, type ToastItem } from "./toastStore";
|
||
|
||
const iconMap: Record<string, string> = {
|
||
success: "✓",
|
||
error: "✕",
|
||
info: "ℹ",
|
||
};
|
||
|
||
function ToastItemView({ item, onDismiss }: { item: ToastItem; onDismiss: (id: number) => void }) {
|
||
return (
|
||
<div className={`app-toast app-toast--${item.type}`} role="alert">
|
||
<span className="app-toast__icon">{iconMap[item.type]}</span>
|
||
<span className="app-toast__msg">{item.message}</span>
|
||
{item.onRetry && (
|
||
<button type="button" className="app-toast__retry" onClick={item.onRetry}>
|
||
重试
|
||
</button>
|
||
)}
|
||
<button type="button" className="app-toast__close" onClick={() => onDismiss(item.id)} aria-label="关闭">
|
||
×
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function ToastContainer() {
|
||
const { items, dismiss } = useToastState();
|
||
if (!items.length) return null;
|
||
|
||
return (
|
||
<div className="app-toast-container" aria-live="polite">
|
||
{items.map((item) => (
|
||
<ToastItemView key={item.id} item={item} onDismiss={dismiss} />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|