import { DeesElement, css, cssManager, customElement, html, property, type TemplateResult, } from '@design.estate/dees-element'; import { mobileComponentStyles } from '../../00componentstyles.js'; import '../dees-mobile-icon/dees-mobile-icon.js'; import { demoFunc } from './dees-mobile-toast.demo.js'; export type ToastType = 'success' | 'error' | 'info' | 'warning'; declare global { interface HTMLElementTagNameMap { 'dees-mobile-toast': DeesMobileToast; } } @customElement('dees-mobile-toast') export class DeesMobileToast extends DeesElement { public static demo = demoFunc; @property({ type: String }) accessor message: string = ''; @property({ type: String }) accessor type: ToastType = 'info'; @property({ type: Number }) accessor duration: number = 0; // 0 means use default private timeoutId?: number; public static styles = [ cssManager.defaultStyles, mobileComponentStyles, css` :host { display: block; position: fixed; /* Mobile-first defaults */ bottom: 1rem; left: 1rem; right: 1rem; transform: none; z-index: var(--dees-z-notification, 900); animation: slideUp 200ms var(--dees-spring); } /* Desktop enhancements */ @media (min-width: 641px) { :host { bottom: 2rem; left: 50%; right: auto; transform: translateX(-50%); } } /* Mobile-first animations */ @keyframes slideUp { from { transform: translateY(100%) scale(0.95); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } } @keyframes slideDown { from { transform: translateY(0) scale(1); opacity: 1; } to { transform: translateY(100%) scale(0.95); opacity: 0; } } /* Desktop-specific animations that include X translation */ @media (min-width: 641px) { @keyframes slideUp { from { transform: translate(-50%, 100%) scale(0.95); opacity: 0; } to { transform: translate(-50%, 0) scale(1); opacity: 1; } } @keyframes slideDown { from { transform: translate(-50%, 0) scale(1); opacity: 1; } to { transform: translate(-50%, 100%) scale(0.95); opacity: 0; } } } :host(.closing) { animation: slideDown 200ms var(--dees-ease-in); } .toast { display: flex; align-items: center; gap: 0.75rem; padding: 1rem 1.5rem; border-radius: var(--dees-radius); box-shadow: var(--dees-shadow-lg); /* Mobile-first defaults */ width: 100%; min-width: auto; max-width: none; position: relative; overflow: hidden; } /* Desktop enhancements */ @media (min-width: 641px) { .toast { width: auto; min-width: 300px; max-width: 500px; } } /* Type-specific styles */ .toast.success { background: var(--dees-card); color: var(--dees-foreground); border: 1px solid var(--dees-border); } .toast.error { background: var(--dees-danger); color: white; border: 1px solid var(--dees-danger); } .toast.warning { background: var(--dees-warning); color: white; border: 1px solid var(--dees-warning); } .toast.info { background: var(--dees-primary); color: white; border: 1px solid var(--dees-primary); } .icon { flex-shrink: 0; width: 1.25rem; height: 1.25rem; position: relative; z-index: 1; } .icon.success { color: var(--dees-success); } .icon.error, .icon.warning, .icon.info { color: currentColor; } .message { flex: 1; font-size: 0.875rem; font-weight: 500; position: relative; z-index: 1; } .close { flex-shrink: 0; width: 2rem; height: 2rem; padding: 0.375rem; margin: -0.375rem; background: none; border: none; cursor: pointer; transition: all var(--dees-transition-fast); opacity: 0.8; position: relative; z-index: 2; pointer-events: auto; display: flex; align-items: center; justify-content: center; } .close.success { color: var(--dees-muted-foreground); } .close.error, .close.warning, .close.info { color: currentColor; } .close:hover { opacity: 1; transform: scale(1.1); } /* Progress bar */ .progress-bar { position: absolute; bottom: 0; left: 0; width: 100%; height: 3px; background: currentColor; opacity: 0.3; transform-origin: left; animation: progress linear forwards; pointer-events: none; transform: scaleX(1); } .toast.success .progress-bar { background: var(--dees-success); } .toast.error .progress-bar, .toast.warning .progress-bar, .toast.info .progress-bar { background: rgba(255, 255, 255, 0.5); } @keyframes progress { from { transform: scaleX(1); } to { transform: scaleX(0); } } `, ]; private get defaultDuration(): number { switch (this.type) { case 'success': return 3000; case 'error': return 5000; case 'warning': return 4000; case 'info': return 4000; } } async connectedCallback() { await super.connectedCallback(); // Auto-dismiss after duration const duration = this.duration || this.defaultDuration; this.timeoutId = window.setTimeout(() => { this.handleClose(); }, duration); } async disconnectedCallback() { await super.disconnectedCallback(); // Clear the timeout when the element is removed if (this.timeoutId) { clearTimeout(this.timeoutId); } } private handleClose() { // Cancel the auto-dismiss timer if (this.timeoutId) { clearTimeout(this.timeoutId); this.timeoutId = undefined; } // Prevent double-triggering if (this.classList.contains('closing')) return; // Add closing animation this.classList.add('closing'); // Wait for closing animation to complete setTimeout(() => { this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true, })); }, 200); } private getIcon(): string { switch (this.type) { case 'success': return 'check-circle'; case 'error': return 'alert-circle'; case 'warning': return 'alert-triangle'; case 'info': return 'info'; } } public render(): TemplateResult { const duration = this.duration || this.defaultDuration; return html`