sentinel-hawk / components /toast-notification.js
hakandinger's picture
Accounts Screen Prompt
58c73e7 verified
class ToastNotification extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._timeout = null;
}
static get observedAttributes() {
return ['type', 'title', 'message', 'duration', 'visible'];
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
disconnectedCallback() {
if (this._timeout) {
clearTimeout(this._timeout);
}
}
show() {
this.setAttribute('visible', 'true');
this.render();
const duration = parseInt(this.getAttribute('duration')) || 5000;
if (this._timeout) {
clearTimeout(this._timeout);
}
this._timeout = setTimeout(() => {
this.hide();
}, duration);
}
hide() {
this.setAttribute('visible', 'false');
this.render();
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
}
render() {
const type = this.getAttribute('type') || 'success';
const title = this.getAttribute('title') || 'Notification';
const message = this.getAttribute('message') || '';
const visible = this.getAttribute('visible') === 'true';
const typeConfig = {
success: {
iconBg: 'bg-green-100',
iconColor: 'text-green-600',
icon: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>'
},
error: {
iconBg: 'bg-red-100',
iconColor: 'text-red-600',
icon: '<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>'
},
warning: {
iconBg: 'bg-yellow-100',
iconColor: 'text-yellow-600',
icon: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>'
},
info: {
iconBg: 'bg-blue-100',
iconColor: 'text-blue-600',
icon: '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line>'
}
};
const config = typeConfig[type] || typeConfig.success;
this.shadowRoot.innerHTML = `
<style>
@import url('https://cdn.tailwindcss.com');
:host {
display: block;
position: fixed;
bottom: 24px;
right: 24px;
z-index: 9999;
}
.toast {
background: white;
border-radius: 0.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border: 1px solid #e5e7eb;
padding: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
min-width: 300px;
max-width: 400px;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.toast.visible {
transform: translateY(0);
opacity: 1;
}
.icon-container {
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.close-btn {
color: #9ca3af;
cursor: pointer;
transition: color 0.2s;
}
.close-btn:hover {
color: #6b7280;
}
</style>
<div class="toast ${visible ? 'visible' : ''}">
<div class="icon-container ${config.iconBg}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 ${config.iconColor}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
${config.icon}
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="font-medium text-gray-900 text-sm">${title}</p>
${message ? `<p class="text-sm text-gray-600 mt-0.5">${message}</p>` : ''}
</div>
<button class="close-btn" onclick="this.getRootNode().host.hide()">
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
`;
}
}
customElements.define('toast-notification', ToastNotification);