feat: add toast notification system
This commit is contained in:
43
static/toast.ts
Normal file
43
static/toast.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
interface ToastOptions {
|
||||
message: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
const toastContainer = () => {
|
||||
let container = document.getElementById('toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'toast-container';
|
||||
container.className = 'fixed bottom-4 right-4 z-[100] flex flex-col gap-2';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
return container;
|
||||
};
|
||||
|
||||
const showToast = ({ message, duration = 3000 }: ToastOptions) => {
|
||||
const container = toastContainer();
|
||||
const toast = document.createElement('div');
|
||||
|
||||
toast.className = 'bg-white/10 border border-white/20 flex items-center gap-3 px-4 py-3 shadow-lg transform transition-all duration-300 translate-y-2 opacity-0';
|
||||
toast.innerHTML = `
|
||||
<span class="text-sm text-neutral-200">${message}</span>
|
||||
<button class="ml-2 opacity-50 hover:opacity-100" onclick="this.parentElement.remove()">
|
||||
<svg class="size-4 text-neutral-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
toast.classList.remove('translate-y-2', 'opacity-0');
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('translate-y-2', 'opacity-0');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
};
|
||||
|
||||
(window as unknown as Record<string, unknown>).showToast = showToast;
|
||||
|
||||
export { showToast };
|
||||
@@ -34,7 +34,18 @@
|
||||
<script type="module" src="/dist/static/search.js" defer></script>
|
||||
<script type="module" src="/dist/static/sort_filter.js" defer></script>
|
||||
<script type="module" src="/dist/static/dedupe.js" defer></script>
|
||||
<script type="module" src="/dist/static/toast.js" defer></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
<script>
|
||||
document.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.classList.contains('error')) {
|
||||
if (window.showToast) showToast({ message: 'Failed to load content' });
|
||||
}
|
||||
});
|
||||
document.addEventListener('htmx:responseError', function(evt) {
|
||||
if (window.showToast) showToast({ message: 'Something went wrong' });
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// Initialize sidebar state immediately to prevent layout shift/transitions
|
||||
(function() {
|
||||
@@ -97,6 +108,7 @@
|
||||
watchlistIds.delete(id)
|
||||
btn.classList.remove('in-watchlist')
|
||||
btn.setAttribute('aria-label', 'Add to Watchlist')
|
||||
if (window.showToast) showToast({ message: 'Removed from watchlist' })
|
||||
|
||||
// Update dropdown status if on anime page
|
||||
syncWatchlistDropdown(id, false)
|
||||
@@ -104,6 +116,7 @@
|
||||
watchlistIds.add(id)
|
||||
btn.classList.add('in-watchlist')
|
||||
btn.setAttribute('aria-label', 'Remove from Watchlist')
|
||||
if (window.showToast) showToast({ message: 'Added to watchlist' })
|
||||
|
||||
// Update dropdown status if on anime page
|
||||
syncWatchlistDropdown(id, true)
|
||||
@@ -123,7 +136,11 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (window.showToast) showToast({ message: 'Failed to update watchlist' })
|
||||
}
|
||||
}).catch(() => {
|
||||
if (window.showToast) showToast({ message: 'Something went wrong' })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user