feat: Enhance toast system with queuing and manual dismiss

Improved toast notification system with queue management:

**Features Added**:
1. **Toast Queuing**:
   - Multiple toasts no longer replace each other
   - Toasts are queued and displayed sequentially
   - Smooth transitions between queued messages
   - Prevents message loss during rapid operations

2. **Manual Dismissal**:
   - Click [×] button to dismiss toast immediately
   - Useful for long-duration error messages
   - Clears auto-dismiss timeout on manual close
   - Next queued toast appears immediately after dismiss

3. **Queue Management**:
   - Internal toastQueue[] array tracks pending messages
   - currentToast reference prevents overlapping displays
   - dismissToast() handles both auto and manual dismissal
   - Automatic dequeue when toast closes

**Implementation**:
- displayToast() separated from showToast() for queue handling
- timeoutId stored on toast element for cleanup
- Close button styled with terminal aesthetic ([×])
- 300ms fade-out animation preserved

**Benefits**:
✓ No lost messages during bulk operations
✓ Better UX - users can dismiss errors immediately
✓ Clean queue management prevents memory leaks
✓ Maintains terminal aesthetic with minimal close button

Example: Bulk assign 10 tickets with 2 failures now shows:
1. "Bulk assign: 8 succeeded, 2 failed" (toast 1)
2. Next operation's message queued (toast 2)
3. User can dismiss or wait for auto-dismiss

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 17:00:35 -05:00
parent 998b85e907
commit d86a60c609

View File

@@ -1,17 +1,26 @@
/** /**
* Terminal-style toast notification system * Terminal-style toast notification system with queuing
*/ */
// Toast queue management
let toastQueue = [];
let currentToast = null;
function showToast(message, type = 'info', duration = 3000) { function showToast(message, type = 'info', duration = 3000) {
// Remove any existing toasts // Queue if a toast is already showing
const existingToast = document.querySelector('.terminal-toast'); if (currentToast) {
if (existingToast) { toastQueue.push({ message, type, duration });
existingToast.remove(); return;
} }
displayToast(message, type, duration);
}
function displayToast(message, type, duration) {
// Create toast element // Create toast element
const toast = document.createElement('div'); const toast = document.createElement('div');
toast.className = `terminal-toast toast-${type}`; toast.className = `terminal-toast toast-${type}`;
currentToast = toast;
// Icon based on type // Icon based on type
const icons = { const icons = {
@@ -24,6 +33,7 @@ function showToast(message, type = 'info', duration = 3000) {
toast.innerHTML = ` toast.innerHTML = `
<span class="toast-icon">[${icons[type] || ''}]</span> <span class="toast-icon">[${icons[type] || ''}]</span>
<span class="toast-message">${message}</span> <span class="toast-message">${message}</span>
<span class="toast-close" style="margin-left: auto; cursor: pointer; opacity: 0.7; padding-left: 1rem;">[×]</span>
`; `;
// Add to document // Add to document
@@ -32,11 +42,36 @@ function showToast(message, type = 'info', duration = 3000) {
// Trigger animation // Trigger animation
setTimeout(() => toast.classList.add('show'), 10); setTimeout(() => toast.classList.add('show'), 10);
// Manual dismiss handler
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => dismissToast(toast));
// Auto-remove after duration // Auto-remove after duration
setTimeout(() => { const timeoutId = setTimeout(() => {
toast.classList.remove('show'); dismissToast(toast);
setTimeout(() => toast.remove(), 300);
}, duration); }, duration);
// Store timeout ID for manual dismiss
toast.timeoutId = timeoutId;
}
function dismissToast(toast) {
// Clear auto-dismiss timeout
if (toast.timeoutId) {
clearTimeout(toast.timeoutId);
}
toast.classList.remove('show');
setTimeout(() => {
toast.remove();
currentToast = null;
// Show next toast in queue
if (toastQueue.length > 0) {
const next = toastQueue.shift();
displayToast(next.message, next.type, next.duration);
}
}, 300);
} }
// Convenience functions // Convenience functions