import { Controller } from '@hotwired/stimulus'; /** * Workflow Progress Bar Controller * * Handles animated progress bar with color transitions and status updates. * * Usage: *
*
*/ export default class extends Controller { static values = { percentage: { type: Number, default: 0 }, status: { type: String, default: 'empty' }, color: { type: String, default: 'secondary' }, animated: { type: Boolean, default: true } } static targets = ['bar', 'badge', 'statusText', 'nextSteps'] connect() { this.updateProgress(); } percentageValueChanged() { this.updateProgress(); } statusValueChanged() { this.updateStatusDisplay(); } colorValueChanged() { this.updateBarColor(); } updateProgress() { if (!this.hasBarTarget) return; const percentage = this.percentageValue; if (this.animatedValue) { // Smooth animation this.animateProgressBar(percentage); } else { // Instant update this.barTarget.style.width = `${percentage}%`; this.barTarget.setAttribute('aria-valuenow', percentage); } // Update accessibility this.updateAriaLabel(); } animateProgressBar(targetPercentage) { const currentPercentage = parseInt(this.barTarget.style.width) || 0; const duration = 600; // ms const steps = 30; const increment = (targetPercentage - currentPercentage) / steps; const stepDuration = duration / steps; let currentStep = 0; const animate = () => { if (currentStep >= steps) { this.barTarget.style.width = `${targetPercentage}%`; this.barTarget.setAttribute('aria-valuenow', targetPercentage); return; } const newPercentage = currentPercentage + (increment * currentStep); this.barTarget.style.width = `${newPercentage}%`; this.barTarget.setAttribute('aria-valuenow', Math.round(newPercentage)); currentStep++; requestAnimationFrame(() => { setTimeout(animate, stepDuration); }); }; animate(); } updateBarColor() { if (!this.hasBarTarget) return; const colorClasses = [ 'bg-secondary', 'bg-info', 'bg-primary', 'bg-success', 'bg-warning', 'bg-danger' ]; // Remove all color classes colorClasses.forEach(cls => this.barTarget.classList.remove(cls)); // Add new color class this.barTarget.classList.add(`bg-${this.colorValue}`); } updateStatusDisplay() { if (this.hasBadgeTarget) { const statusMessages = this.getStatusMessage(this.statusValue); this.badgeTarget.textContent = statusMessages.short; } if (this.hasStatusTextTarget) { const statusMessages = this.getStatusMessage(this.statusValue); this.statusTextTarget.textContent = statusMessages.long; } } updateAriaLabel() { if (!this.hasBarTarget) return; const percentage = this.percentageValue; const statusMessages = this.getStatusMessage(this.statusValue); const label = `${statusMessages.short}: ${percentage}% complete`; this.barTarget.setAttribute('aria-label', label); } getStatusMessage(status) { const messages = { 'empty': { short: 'Not started', long: 'Reading list not started yet' }, 'draft': { short: 'Draft created', long: 'Draft created, add content to continue' }, 'has_metadata': { short: 'Title and summary added', long: 'Metadata complete, add articles next' }, 'has_articles': { short: 'Articles added', long: 'Articles added, checking requirements' }, 'ready_for_review': { short: 'Ready to publish', long: 'Your reading list is ready to publish' }, 'publishing': { short: 'Publishing...', long: 'Publishing to Nostr, please wait' }, 'published': { short: 'Published', long: 'Successfully published to Nostr' }, 'editing': { short: 'Editing', long: 'Editing published reading list' } }; return messages[status] || messages['empty']; } // Public methods that can be called from other controllers setPercentage(percentage) { this.percentageValue = percentage; } setStatus(status) { this.statusValue = status; } setColor(color) { this.colorValue = color; } pulse() { if (!this.hasBarTarget) return; this.barTarget.classList.add('workflow-progress-pulse'); setTimeout(() => { this.barTarget.classList.remove('workflow-progress-pulse'); }, 1000); } celebrate() { if (!this.hasBarTarget) return; // Add celebration animation when reaching 100% if (this.percentageValue === 100) { this.barTarget.classList.add('workflow-progress-celebrate'); setTimeout(() => { this.barTarget.classList.remove('workflow-progress-celebrate'); }, 2000); } } }