Spaces:
Running
Running
| class StatusBar extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| this.currentTimeout = null; | |
| } | |
| connectedCallback() { | |
| this.render(); | |
| } | |
| render() { | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| position: fixed; | |
| top: 0; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 1000; | |
| pointer-events: none; | |
| } | |
| .status-bar { | |
| min-width: 300px; | |
| max-width: 600px; | |
| padding: 0.75rem 1.5rem; | |
| margin-top: 1rem; | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 1rem; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | |
| backdrop-filter: blur(10px); | |
| opacity: 0; | |
| transform: translateY(-20px); | |
| transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
| pointer-events: auto; | |
| } | |
| .status-bar.visible { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| .status-bar.success { | |
| background: linear-gradient(135deg, #059669, #047857); | |
| color: white; | |
| } | |
| .status-bar.error { | |
| background: linear-gradient(135deg, #dc2626, #b91c1c); | |
| color: white; | |
| } | |
| .status-bar.loading { | |
| background: linear-gradient(135deg, #2563eb, #1d4ed8); | |
| color: white; | |
| } | |
| .status-message { | |
| flex: 1; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .status-icon { | |
| flex-shrink: 0; | |
| } | |
| .status-action { | |
| background: rgba(255, 255, 255, 0.15); | |
| border: 1px solid rgba(255, 255, 255, 0.25); | |
| color: white; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .status-action:hover { | |
| background: rgba(255, 255, 255, 0.25); | |
| border-color: rgba(255, 255, 255, 0.35); | |
| } | |
| .loading-spinner { | |
| width: 16px; | |
| height: 16px; | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: white; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| @media (max-width: 640px) { | |
| :host { | |
| left: 1rem; | |
| right: 1rem; | |
| transform: none; | |
| } | |
| .status-bar { | |
| min-width: auto; | |
| max-width: none; | |
| } | |
| } | |
| </style> | |
| <div id="statusBar" class="status-bar"> | |
| <div class="status-message"> | |
| <span class="status-icon"></span> | |
| <span class="status-text"></span> | |
| </div> | |
| <button class="status-action hidden" id="statusAction"></button> | |
| </div> | |
| `; | |
| } | |
| show(type, message, duration = 3000, actionText = null, actionCallback = null) { | |
| const statusBar = this.shadowRoot.getElementById('statusBar'); | |
| const statusText = this.shadowRoot.querySelector('.status-text'); | |
| const statusIcon = this.shadowRoot.querySelector('.status-icon'); | |
| const statusAction = this.shadowRoot.getElementById('statusAction'); | |
| // Clear any existing timeout | |
| if (this.currentTimeout) { | |
| clearTimeout(this.currentTimeout); | |
| } | |
| // Reset classes | |
| statusBar.className = 'status-bar'; | |
| // Set type-specific content | |
| switch (type) { | |
| case 'success': | |
| statusBar.classList.add('success'); | |
| statusIcon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>'; | |
| break; | |
| case 'error': | |
| statusBar.classList.add('error'); | |
| statusIcon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>'; | |
| break; | |
| case 'loading': | |
| statusBar.classList.add('loading'); | |
| statusIcon.innerHTML = '<div class="loading-spinner"></div>'; | |
| break; | |
| } | |
| // Set message | |
| statusText.textContent = message; | |
| // Handle action button | |
| if (actionText && actionCallback) { | |
| statusAction.textContent = actionText; | |
| statusAction.classList.remove('hidden'); | |
| statusAction.onclick = actionCallback; | |
| } else { | |
| statusAction.classList.add('hidden'); | |
| statusAction.onclick = null; | |
| } | |
| // Show status bar | |
| setTimeout(() => { | |
| statusBar.classList.add('visible'); | |
| }, 10); | |
| // Auto-hide after duration (except for loading) | |
| if (type !== 'loading' && duration > 0) { | |
| this.currentTimeout = setTimeout(() => { | |
| this.hide(); | |
| }, duration); | |
| } | |
| } | |
| hide() { | |
| const statusBar = this.shadowRoot.getElementById('statusBar'); | |
| statusBar.classList.remove('visible'); | |
| // Clear timeout | |
| if (this.currentTimeout) { | |
| clearTimeout(this.currentTimeout); | |
| this.currentTimeout = null; | |
| } | |
| } | |
| // Method to show success message | |
| success(message, duration = 3000) { | |
| this.show('success', message, duration); | |
| } | |
| // Method to show error message with retry | |
| error(message, retryCallback = null) { | |
| this.show('error', message, 5000, 'Retry', retryCallback); | |
| } | |
| // Method to show loading | |
| loading(message = 'Connecting...') { | |
| this.show('loading', message, 0); | |
| } | |
| } | |
| customElements.define('status-bar', StatusBar); |