94 lines
2.3 KiB
TypeScript
94 lines
2.3 KiB
TypeScript
class ToastService {
|
|
private container: HTMLElement;
|
|
private stylesInjected = false;
|
|
|
|
constructor() {
|
|
this.container = this.ensureContainer();
|
|
this.injectStyles();
|
|
}
|
|
|
|
info(message: string, timeoutMs: number = 5000) {
|
|
const el = this.createToast(message, 'info');
|
|
this.container.appendChild(el);
|
|
// Auto-dismiss after timeout
|
|
const timer = window.setTimeout(() => this.dismiss(el), timeoutMs);
|
|
// Dismiss on click immediately
|
|
el.addEventListener('click', () => {
|
|
window.clearTimeout(timer);
|
|
this.dismiss(el);
|
|
});
|
|
}
|
|
|
|
error(message: string) {
|
|
const el = this.createToast(message, 'error');
|
|
this.container.appendChild(el);
|
|
// Only dismiss on click
|
|
el.addEventListener('click', () => this.dismiss(el));
|
|
}
|
|
|
|
private dismiss(el: HTMLElement) {
|
|
if (!el.parentElement) return;
|
|
el.parentElement.removeChild(el);
|
|
}
|
|
|
|
private createToast(message: string, type: 'info' | 'error'): HTMLElement {
|
|
const div = document.createElement('div');
|
|
div.className = `toast ${type}`;
|
|
div.textContent = message;
|
|
div.setAttribute('role', 'status');
|
|
div.setAttribute('aria-live', 'polite');
|
|
return div;
|
|
}
|
|
|
|
private ensureContainer(): HTMLElement {
|
|
let container = document.getElementById('toast-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.id = 'toast-container';
|
|
document.body.appendChild(container);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
private injectStyles() {
|
|
if (this.stylesInjected) return;
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
#toast-container {
|
|
position: fixed;
|
|
top: 12px;
|
|
right: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
z-index: 9999;
|
|
}
|
|
.toast {
|
|
max-width: 320px;
|
|
padding: 10px 12px;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
font-family: sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.3;
|
|
}
|
|
.toast.info {
|
|
background-color: #d4edda; /* green-ish */
|
|
color: #155724;
|
|
border-left: 4px solid #28a745;
|
|
}
|
|
.toast.error {
|
|
background-color: #f8d7da; /* red-ish */
|
|
color: #721c24;
|
|
border-left: 4px solid #dc3545;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
this.stylesInjected = true;
|
|
}
|
|
}
|
|
|
|
export const toast = new ToastService();
|