793 lines
19 KiB
Markdown
793 lines
19 KiB
Markdown
|
|
# 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)
|