ToastStack
toaststack-1779378817369.tsx
'use client'
import { useState, useCallback } from 'react'
type ToastType = 'success' | 'error' | 'warning' | 'info'
interface Toast {
id: number
type: ToastType
title: string
message?: string
}
const CONFIGS = {
success: { icon: '✓', color: '#22c55e', bg: 'rgba(34,197,94,0.1)', border: 'rgba(34,197,94,0.2)' },
error: { icon: '✕', color: '#ef4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.2)' },
warning: { icon: '⚠', color: '#f59e0b', bg: 'rgba(245,158,11,0.1)', border: 'rgba(245,158,11,0.2)' },
info: { icon: 'ℹ', color: '#6366f1', bg: 'rgba(99,102,241,0.1)', border: 'rgba(99,102,241,0.2)' },
}
const EXAMPLES: { type: ToastType; title: string; message?: string }[] = [
{ type: 'success', title: 'Component copied!', message: 'Code is ready to paste into your project.' },
{ type: 'error', title: 'Build failed', message: 'TypeScript error in Line 42. Check the console.' },
{ type: 'warning', title: 'Rate limit approaching', message: '85% of your monthly API quota used.' },
{ type: 'info', title: 'New components added', message: '47 new components published this week.' },
]
let nextId = 1
export default function ToastStack() {
const [toasts, setToasts] = useState<Toast[]>([])
const add = useCallback((type: ToastType, title: string, message?: string) => {
const id = nextId++
setToasts(t => [...t, { id, type, title, message }])
setTimeout(() => remove(id), 4000)
}, [])
function remove(id: number) {
setToasts(t => t.filter(x => x.id !== id))
}
return (
<div style={{ background: '#0A0A0A', padding: 40, minHeight: 360, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center', marginBottom: 40 }}>
{(Object.keys(CONFIGS) as ToastType[]).map(type => (
<button key={type} onClick={() => { const ex = EXAMPLES.find(e => e.type === type)!; add(type, ex.title, ex.message) }}
style={{ background: CONFIGS[type].bg, border: `1px solid ${CONFIGS[type].border}`, color: CONFIGS[type].color, borderRadius: 8, padding: '8px 16px', cursor: 'pointer', fontSize: 13, fontWeight: 600, textTransform: 'capitalize' }}>
{type}
</button>
))}
</div>
<div style={{ position: 'fixed', bottom: 24, right: 24, display: 'flex', flexDirection: 'column', gap: 10, zIndex: 9999, pointerEvents: 'none', width: 340 }}>
{toasts.map((toast) => {
const c = CONFIGS[toast.type]
return (
<div key={toast.id} style={{
background: '#1a1a1a', border: `1px solid ${c.border}`, borderLeft: `3px solid ${c.color}`,
borderRadius: 10, padding: '14px 16px', display: 'flex', gap: 12, alignItems: 'flex-start',
pointerEvents: 'all', animation: 'slideIn 0.3s ease', cursor: 'pointer',
boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
}} onClick={() => remove(toast.id)}>
<div style={{ width: 20, height: 20, borderRadius: '50%', background: c.bg, border: `1px solid ${c.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: c.color, fontSize: 11, fontWeight: 700, flexShrink: 0 }}>
{c.icon}
</div>
<div style={{ flex: 1 }}>
<div style={{ color: '#F5F5F0', fontSize: 13, fontWeight: 600 }}>{toast.title}</div>
{toast.message && <div style={{ color: 'rgba(255,255,255,0.45)', fontSize: 12, marginTop: 2, lineHeight: 1.5 }}>{toast.message}</div>}
</div>
<span style={{ color: 'rgba(255,255,255,0.3)', fontSize: 16, lineHeight: 1 }}>×</span>
</div>
)
})}
</div>
<div style={{ color: 'rgba(255,255,255,0.3)', fontSize: 13 }}>Click buttons above to show toasts</div>
<style>{`@keyframes slideIn { from { opacity: 0; transform: translateX(20px) } to { opacity: 1; transform: translateX(0) } }`}</style>
</div>
)
}Component info
CategoryNotifications
Frameworkreact
TierFREE
Views0
Copies0
About
Animated toast notification stack with different types, auto-dismiss, and drag-to-dismiss