68 lines
2.8 KiB
Vue
68 lines
2.8 KiB
Vue
<script setup>
|
|
import { ref, nextTick, onMounted, watch } from 'vue';
|
|
|
|
const props = defineProps({
|
|
toasts: {
|
|
type: Array,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['close']);
|
|
|
|
const closeToast = (id) => {
|
|
emit('close', id);
|
|
};
|
|
|
|
// Re-run lucide icons when toasts change
|
|
watch(() => props.toasts, () => {
|
|
nextTick(() => {
|
|
if (window.lucide) window.lucide.createIcons();
|
|
});
|
|
}, { deep: true });
|
|
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
if (window.lucide) window.lucide.createIcons();
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<teleport to="body">
|
|
<div id="notif-container">
|
|
<div v-for="toast in toasts" :key="toast.id"
|
|
:class="['toast', toast.type, { active: toast.active, 'fly-out': toast.flyingOut }]"
|
|
@click="closeToast(toast.id)">
|
|
<div class="toast-icon"><i :data-lucide="toast.icon"></i></div>
|
|
<div style="flex:1">
|
|
<div class="toast-title">{{ toast.title }}</div>
|
|
<div class="toast-desc">{{ toast.desc }}</div>
|
|
</div>
|
|
<div class="toast-progress" :style="{ transform: `scaleX(${toast.progress/100})` }"></div>
|
|
</div>
|
|
</div>
|
|
</teleport>
|
|
</template>
|
|
|
|
<style scoped>
|
|
#notif-container { position: fixed; top: 85px; right: 20px; display: flex; flex-direction: column; gap: 12px; z-index: 5000; pointer-events: auto; backdrop-filter: none; }
|
|
.toast { background: rgba(13,13,13,0.95); border: 1px solid #222; padding: 14px 20px; border-radius: 12px; display: flex; align-items: center; gap: 14px; width: 300px; transform: translateX(120%); transition: 0.4s cubic-bezier(0.2, 0, 0, 1); box-shadow: 0 10px 40px rgba(0,0,0,0.9); position: relative; overflow: hidden; cursor: pointer; backdrop-filter: none; }
|
|
.toast.active { transform: translateX(0); }
|
|
.toast.fly-out { transform: translate(100px, -200px) scale(0); opacity: 0; }
|
|
.toast-icon { padding: 8px; border-radius: 10px; color: white; display: flex; align-items: center; justify-content: center; }
|
|
.toast-progress { position: absolute; bottom: 0; left: 0; height: 3px; background: rgba(255,255,255,0.1); width: 100%; transform-origin: left; }
|
|
.toast:hover { transform: scale(1.05) translateX(-5px); z-index: 1001; }
|
|
.toast.green { border-left: 4px solid var(--green); }
|
|
.toast.green .toast-icon { background: rgba(0,255,157,0.1); color: var(--green); }
|
|
.toast.magenta { border-left: 4px solid var(--magenta); }
|
|
.toast.magenta .toast-icon { background: rgba(255,0,122,0.1); color: var(--magenta); }
|
|
.toast-title { font-size: 11px; font-weight: 900; color: #fff; letter-spacing: 0.5px; margin-bottom: 2px; text-transform: uppercase; }
|
|
.toast-desc { font-size: 11px; color: #bbb; font-weight: 500; }
|
|
|
|
:global(:root) {
|
|
--green: #00ff9d;
|
|
--magenta: #ff007a;
|
|
}
|
|
</style>
|