Files
cv-site/doc/13-TOAST-NOTIFICATIONS.md
T
juanatsap 9c5f0b20d8 docs: Add comprehensive toast notification test and documentation
Test Addition:
- Created tests/mjs/29-pdf-toast-notifications.test.mjs (22 assertions)
- Validates toast elements, JavaScript API, animations
- Tests both PDF download scenarios (modal stays open vs. closes early)
- Verifies toast lifecycle: prepare → progress → success → dismiss
- Tests icon/title/message updates and progress bar animation

Test Coverage:
 Toast DOM elements exist
 JavaScript functions available (showPDFToast, hidePDFToast)
 Manual toast trigger and auto-hide
 PDF modal integration - Scenario A (stay in modal)
 PDF modal integration - Scenario B (close modal, toast appears)
 Toast content updates (📥)
 Progress bar animation
 CSS animations defined (toastSlideIn, toastLifecycle, progressShrink)

Documentation Addition:
- Created doc/13-TOAST-NOTIFICATIONS.md (comprehensive guide)
- Architecture and component structure
- User experience flows (2 scenarios documented)
- JavaScript API reference (showPDFToast, hidePDFToast, showError)
- CSS structure and animations
- Template structure with ARIA accessibility
- PDF download integration details
- Accessibility features and testing guide
- Customization guide and troubleshooting

Documentation Updates:
- Updated doc/README.md (added entry #13, total: 12 → 13 docs)
- Updated tests/README.md (test count: 27 → 28)
- Updated tests/TEST-SUMMARY.md (added toast notifications coverage)
- Updated tests/mjs/README.md (added test 29 description)

Test Count: 28 active tests
Doc Count: 13 core documents
Coverage: Complete UX feedback system with dual-mode operation
2025-11-20 13:05:46 +00:00

793 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Toast Notification System
**Comprehensive documentation for the CV project's toast notification system.**
---
## Table of Contents
1. [Overview](#overview)
2. [Architecture](#architecture)
3. [User Experience Flows](#user-experience-flows)
4. [Implementation Details](#implementation-details)
5. [JavaScript API](#javascript-api)
6. [CSS Structure](#css-structure)
7. [Template Structure](#template-structure)
8. [Integration with PDF Downloads](#integration-with-pdf-downloads)
9. [Accessibility](#accessibility)
10. [Testing](#testing)
11. [Customization Guide](#customization-guide)
12. [Troubleshooting](#troubleshooting)
---
## Overview
The toast notification system provides non-intrusive visual feedback for asynchronous operations, particularly PDF generation and downloads. It enhances user experience by:
- **Immediate Feedback**: Users know their action was registered
- **Progress Tracking**: Visual indication of operation duration
- **Completion Confirmation**: Clear success/error messaging
- **Non-Blocking UX**: Users can continue working while operations complete
### Key Features
✅ Multiple toast variants (success, error, info, warning)
✅ Animated slide-in/slide-out
✅ Progress bar with timing animation
✅ Auto-dismiss with configurable duration
✅ Manual close button
✅ Mobile responsive (bottom-center on small screens)
✅ Accessibility compliant (ARIA labels, reduced motion support)
✅ Bilingual support (English/Spanish)
---
## Architecture
### Component Structure
```
Toast System
├── CSS Layer (static/css/04-interactive/_toasts.css)
│ ├── Toast container positioning
│ ├── Variant styles (success, error, info, warning)
│ ├── Animations (slide, fade, progress)
│ └── Responsive breakpoints
├── HTML Templates (templates/partials/widgets/)
│ ├── error-toast.html (Error notifications)
│ └── pdf-toast.html (PDF download feedback)
├── JavaScript API (static/js/main.js)
│ ├── showPDFToast(options)
│ ├── hidePDFToast()
│ └── showError(message)
└── Integration Layer
└── PDF Modal (templates/partials/modals/pdf-modal.html)
└── downloadPDF() function
```
### Design Principles
1. **Progressive Enhancement**: Works without JavaScript (fallback to modal only)
2. **Separation of Concerns**: CSS handles animations, JS handles logic
3. **Reusability**: Generic toast system extensible for any notification type
4. **Performance**: Minimal DOM manipulation, CSS-driven animations
5. **Accessibility**: Screen reader announcements, keyboard navigation
---
## User Experience Flows
### Flow 1: PDF Download (Modal Stays Open)
```
User clicks "Download PDF"
Modal shows loading overlay
"⏳ Preparing PDF... (~4 seconds)"
User waits in modal
PDF downloads to browser
Modal auto-closes
DONE ✅
```
**When to use:** Default behavior for users who want to watch progress.
### Flow 2: PDF Download (User Closes Modal)
```
User clicks "Download PDF"
Modal shows loading overlay
User presses ESC or clicks X
Toast slides in (bottom-right)
"📥 Preparing PDF..."
"Generating Default CV (5 pages)... (~4s)"
[Progress bar animates]
After estimated time (~4s)
Toast updates
"✅ PDF Ready!"
"Check your downloads folder"
Toast auto-dismisses (3s)
DONE ✅
```
**When to use:** Power users who want to continue working while PDF generates.
---
## Implementation Details
### File Structure
```
cv/
├── static/css/04-interactive/
│ └── _toasts.css # Toast styles (242 lines)
├── templates/partials/widgets/
│ ├── error-toast.html # Error notification template
│ └── pdf-toast.html # PDF download notification template
├── templates/index.html # Includes toast templates
├── static/js/main.js # Toast functions
└── templates/partials/modals/
└── pdf-modal.html # Integrates toast with downloads
```
### CSS Organization
**File:** `static/css/04-interactive/_toasts.css`
**Sections:**
1. **Base Toast Container** - Positioning, layout, common styles
2. **Toast Variants** - Color schemes for different notification types
3. **Content Elements** - Icon, title, message, close button
4. **Progress Bar** - Visual countdown indicator
5. **Animations** - Slide-in, slide-out, lifecycle, progress
6. **Responsive Design** - Mobile breakpoints
7. **Accessibility** - Reduced motion preferences
8. **Print Styles** - Hide toasts in print media
---
## JavaScript API
### `showPDFToast(options)`
Display a PDF download toast notification.
**Parameters:**
```javascript
{
icon: string, // Emoji icon (📥, ✅, ⚠️)
title: string, // Toast title
message: string, // Toast message
duration: number, // Auto-hide duration in ms (default: 5000)
autoHide: boolean // Auto-dismiss after duration (default: true)
}
```
**Example:**
```javascript
window.showPDFToast({
icon: '📥',
title: 'Preparing PDF...',
message: 'Generating Default CV (5 pages)... (~4s)',
duration: 4000,
autoHide: false // Manually control dismissal
});
```
**Return:** None
**Side Effects:**
- Adds `.show` class to toast element
- Starts progress bar animation
- Schedules auto-hide timeout (if `autoHide: true`)
---
### `hidePDFToast()`
Immediately hide the PDF toast.
**Parameters:** None
**Example:**
```javascript
window.hidePDFToast();
```
**Return:** None
**Side Effects:**
- Removes `.show` class from toast element
- Cancels progress bar animation
---
### `showError(message)`
Display an error toast notification.
**Parameters:**
```javascript
message: string // Error message to display
```
**Example:**
```javascript
window.showError('Failed to load content. Please try again.');
```
**Return:** None
**Side Effects:**
- Shows error toast with red styling
- Auto-hides after 5 seconds (CSS animation)
---
## CSS Structure
### Toast Variants
#### Success Toast (Green)
```css
.success-toast {
background: linear-gradient(135deg,
rgba(40, 167, 69, 0.95) 0%,
rgba(25, 135, 84, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Error Toast (Red)
```css
.error-toast {
background: linear-gradient(135deg,
rgba(220, 53, 69, 0.95) 0%,
rgba(200, 35, 51, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Info Toast (Blue)
```css
.info-toast {
background: linear-gradient(135deg,
rgba(13, 110, 253, 0.95) 0%,
rgba(10, 88, 202, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Warning Toast (Yellow)
```css
.warning-toast {
background: linear-gradient(135deg,
rgba(255, 193, 7, 0.95) 0%,
rgba(255, 167, 38, 0.95) 100%);
color: #333;
border-left: 4px solid #333;
}
```
### Key Animations
#### Slide In
```css
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%) translateY(0);
}
to {
opacity: 1;
transform: translateX(0) translateY(0);
}
}
```
#### Lifecycle (Auto-hide)
```css
@keyframes toastLifecycle {
0% {
opacity: 1;
transform: translateX(0) translateY(0);
}
85% {
opacity: 1;
transform: translateX(0) translateY(0);
}
100% {
opacity: 0;
transform: translateX(100%) translateY(0);
}
}
```
#### Progress Bar
```css
@keyframes progressShrink {
from { width: 100%; }
to { width: 0%; }
}
```
---
## Template Structure
### PDF Toast Template
**File:** `templates/partials/widgets/pdf-toast.html`
```html
<div id="pdf-toast" class="success-toast no-print"
role="status" aria-live="polite" aria-atomic="true">
<!-- Icon -->
<span class="toast-icon" id="pdf-toast-icon">📥</span>
<!-- Content -->
<div class="toast-content">
<p class="toast-title" id="pdf-toast-title">Preparing PDF</p>
<p class="toast-message" id="pdf-toast-message"></p>
</div>
<!-- Close Button -->
<button aria-label="Close notification"
class="toast-close"
onclick="document.getElementById('pdf-toast').classList.remove('show')">
×
</button>
<!-- Progress Bar -->
<div class="toast-progress">
<div class="toast-progress-bar" id="pdf-toast-progress"></div>
</div>
</div>
```
### Key Elements
- **`role="status"`**: ARIA role for status messages
- **`aria-live="polite"`**: Screen reader announcement (non-interrupting)
- **`aria-atomic="true"`**: Read entire toast as single unit
- **`.no-print`**: Hidden in print media
- **`#pdf-toast-icon`**: Dynamically updated emoji
- **`#pdf-toast-title`**: Main heading
- **`#pdf-toast-message`**: Detailed message
- **`.toast-progress-bar`**: Visual countdown indicator
---
## Integration with PDF Downloads
### Download Flow Integration
**File:** `templates/partials/modals/pdf-modal.html`
```javascript
function downloadPDF() {
// ... setup code ...
// Track if modal is closed by user
let modalClosedByUser = false;
const onModalClose = () => {
modalClosedByUser = true;
// Show toast when user closes modal
if (window.showPDFToast) {
window.showPDFToast({
icon: '📥',
title: isSpanish ? 'Preparando PDF...' : 'Preparing PDF...',
message: `Generating ${formatName}... (~${estimatedTime}s)`,
duration: estimatedTime * 1000,
autoHide: false
});
}
};
// Listen for modal close
modal.addEventListener('close', onModalClose, { once: true });
// Trigger download
window.location.href = url;
// After estimated time: update toast or close modal
setTimeout(() => {
if (modalClosedByUser && window.showPDFToast) {
// Update toast to success
window.showPDFToast({
icon: '✅',
title: isSpanish ? '¡PDF Listo!' : 'PDF Ready!',
message: isSpanish
? 'Revisa tu carpeta de descargas'
: 'Check your downloads folder',
duration: 3000,
autoHide: true
});
} else {
// Close modal if still open
modal.close();
}
}, estimatedTime * 1000);
}
```
### Format-Specific Timing
| PDF Format | Pages | Estimated Time | Icon Progression |
|------------|-------|----------------|------------------|
| Short | 4 | 3 seconds | 📥 → ✅ |
| Default | 5 | 4 seconds | 📥 → ✅ |
| Extended | 9 | 8 seconds | 📥 → ✅ |
---
## Accessibility
### ARIA Attributes
```html
<div id="pdf-toast"
role="status" <!-- Status message role -->
aria-live="polite" <!-- Non-interrupting announcement -->
aria-atomic="true"> <!-- Read as single unit -->
```
### Screen Reader Behavior
1. **Toast Appears**: Screen reader announces title + message
2. **Toast Updates**: New content announced automatically
3. **Toast Dismisses**: Silent (user already informed)
### Keyboard Navigation
- **Close Button**: Focusable with `Tab`
- **Enter/Space**: Activate close button
- **ESC**: Closes parent modal (which may trigger toast)
### Reduced Motion
Users with motion sensitivity get simplified animations:
```css
@media (prefers-reduced-motion: reduce) {
.success-toast,
.error-toast,
.toast {
animation: none;
opacity: 1;
}
.toast-progress-bar {
animation: none;
}
}
```
**Effect:** Toast appears/disappears instantly without sliding/fading.
---
## Testing
### Automated Test
**File:** `tests/mjs/29-pdf-toast-notifications.test.mjs`
**Coverage:**
- ✅ Toast elements exist in DOM
- ✅ Toast initially hidden
- ✅ JavaScript functions available
- ✅ Manual toast trigger works
- ✅ Toast auto-hides after duration
- ✅ PDF modal integration (Scenario A: Stay in modal)
- ✅ PDF modal integration (Scenario B: Close modal early)
- ✅ CSS animations defined
- ✅ Icon/title/message updates
- ✅ Progress bar animation
**Run Test:**
```bash
bun tests/mjs/29-pdf-toast-notifications.test.mjs
```
### Manual Testing Checklist
**Scenario 1: Modal Overlay Only**
- [ ] Open PDF modal
- [ ] Select Default CV
- [ ] Click Download PDF
- [ ] Wait in modal without closing
- [ ] Verify overlay shows with spinner
- [ ] Verify modal closes after ~4 seconds
- [ ] Verify NO toast appears
**Scenario 2: Toast After Modal Close**
- [ ] Open PDF modal
- [ ] Select Extended CV (9 pages)
- [ ] Click Download PDF
- [ ] Immediately press ESC to close modal
- [ ] Verify toast appears bottom-right
- [ ] Verify icon is 📥
- [ ] Verify title says "Preparing PDF..."
- [ ] Verify message includes format name and time
- [ ] Verify progress bar animates (shrinks)
- [ ] Wait ~8 seconds
- [ ] Verify toast updates to ✅
- [ ] Verify title says "PDF Ready!"
- [ ] Verify toast auto-dismisses after ~3 seconds
**Scenario 3: Close Button**
- [ ] Trigger toast manually (browser console): `showPDFToast({icon: '🧪', title: 'Test', message: 'Test message'})`
- [ ] Click × close button
- [ ] Verify toast disappears immediately
**Scenario 4: Mobile Responsive**
- [ ] Resize browser to < 540px width
- [ ] Trigger toast
- [ ] Verify toast is full-width at bottom
- [ ] Verify content is readable
- [ ] Verify close button accessible
---
## Customization Guide
### Adding a New Toast Variant
**1. Define CSS Class** (`static/css/04-interactive/_toasts.css`):
```css
.custom-toast {
background: linear-gradient(135deg,
rgba(156, 39, 176, 0.95) 0%,
rgba(123, 31, 162, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
**2. Create HTML Template** (`templates/partials/widgets/custom-toast.html`):
```html
{{define "custom-toast"}}
<div id="custom-toast" class="custom-toast no-print"
role="status" aria-live="polite">
<span class="toast-icon" id="custom-toast-icon">🎨</span>
<div class="toast-content">
<p class="toast-title" id="custom-toast-title">Custom Title</p>
<p class="toast-message" id="custom-toast-message"></p>
</div>
<button class="toast-close">×</button>
</div>
{{end}}
```
**3. Add JavaScript Function** (`static/js/main.js`):
```javascript
window.showCustomToast = function(options = {}) {
const toast = document.getElementById('custom-toast');
const icon = document.getElementById('custom-toast-icon');
const title = document.getElementById('custom-toast-title');
const message = document.getElementById('custom-toast-message');
if (options.icon) icon.textContent = options.icon;
if (options.title) title.textContent = options.title;
if (options.message) message.textContent = options.message;
toast.classList.remove('show');
void toast.offsetWidth;
toast.classList.add('show');
if (options.autoHide !== false) {
const duration = options.duration || 5000;
setTimeout(() => toast.classList.remove('show'), duration);
}
};
```
**4. Include in Index** (`templates/index.html`):
```html
{{template "custom-toast" .}}
```
### Changing Animation Duration
**CSS** (`_toasts.css`):
```css
/* Change lifecycle duration (default: 5s) */
.toast.show {
animation: toastLifecycle 8s ease forwards; /* Now 8 seconds */
}
/* Change progress bar duration */
.toast-progress-bar {
animation: progressShrink 8s linear forwards; /* Match lifecycle */
}
```
**JavaScript** (when calling function):
```javascript
window.showPDFToast({
icon: '📥',
title: 'Preparing...',
message: 'This will take longer',
duration: 10000 // 10 seconds
});
```
---
## Troubleshooting
### Toast Not Appearing
**Symptom:** Calling `showPDFToast()` has no effect.
**Checks:**
1. Verify toast element exists: `document.getElementById('pdf-toast')`
2. Check console for JavaScript errors
3. Verify CSS is loaded: Inspect element and check for `.success-toast` styles
4. Confirm `toasts.css` imported in `main.css`
5. Check `index.html` includes `{{template "pdf-toast" .}}`
**Solution:**
```bash
# Verify CSS import
grep "toasts.css" static/css/main.css
# Verify template inclusion
grep "pdf-toast" templates/index.html
# Check browser console
# Should see no errors when calling showPDFToast()
```
---
### Toast Not Auto-Dismissing
**Symptom:** Toast appears but never disappears.
**Checks:**
1. Verify `autoHide` is not set to `false`
2. Check CSS animation: `toastLifecycle` should be defined
3. Inspect element: `animation` property should be set
4. Check for `prefers-reduced-motion` (disables animations)
**Solution:**
```javascript
// Explicitly set autoHide
window.showPDFToast({
icon: '✅',
title: 'Test',
message: 'Should auto-hide',
duration: 3000,
autoHide: true // Explicitly enable
});
// Or manually hide
setTimeout(() => window.hidePDFToast(), 3000);
```
---
### Progress Bar Not Animating
**Symptom:** Progress bar appears but doesn't shrink.
**Checks:**
1. Verify `progressShrink` animation is defined in CSS
2. Check animation is applied: Inspect `#pdf-toast-progress`
3. Confirm `prefers-reduced-motion` is not active
4. Check `duration` parameter matches animation duration
**Solution:**
```javascript
// Ensure progress bar animation resets
const progressBar = document.getElementById('pdf-toast-progress');
if (progressBar) {
progressBar.style.animation = 'none';
void progressBar.offsetWidth; // Trigger reflow
progressBar.style.animation = 'progressShrink 5000ms linear forwards';
}
```
---
### Toast Overlaps Content on Mobile
**Symptom:** Toast covers important content on small screens.
**Checks:**
1. Verify responsive CSS is loaded
2. Check viewport width: `< 540px` should trigger mobile styles
3. Inspect `z-index` conflicts
**Solution:**
```css
/* Adjust mobile positioning */
@media (max-width: 540px) {
.toast {
bottom: 1rem; /* Lower if needed */
right: 1rem;
left: 1rem;
z-index: 10000; /* Ensure on top */
}
}
```
---
### Multiple Toasts Stacking
**Symptom:** Calling `showPDFToast()` multiple times creates overlapping toasts.
**Root Cause:** Current system uses single toast element, so multiple calls update the same toast.
**Expected Behavior:** Later calls replace earlier toast content.
**If You Need Multiple Toasts:**
1. Clone toast element before showing:
```javascript
function showMultipleToasts(options) {
const original = document.getElementById('pdf-toast');
const clone = original.cloneNode(true);
clone.id = `toast-${Date.now()}`;
document.body.appendChild(clone);
// Update clone content and show
// ... similar logic to showPDFToast ...
}
```
2. Or use a toast queue system (advanced).
---
## Related Documentation
- [2. Modern Web Techniques](2-MODERN-WEB-TECHNIQUES.md) - HTMX patterns and hypermedia architecture
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization
- [3. API Reference](3-API.md) - Backend API endpoints including PDF export
- [11. PDF Export](11-PDF-EXPORT.md) - PDF generation with chromedp
---
**Last Updated**: 2025-11-20
**Version**: 1.0
**Status**: Production Ready
**Test Coverage**: 29-pdf-toast-notifications.test.mjs (22 assertions)