97 lines
2.0 KiB
Vue
97 lines
2.0 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
CheckCircleIcon,
|
|
InformationCircleIcon,
|
|
XCircleIcon,
|
|
XMarkIcon,
|
|
} from '@heroicons/vue/24/outline';
|
|
import type {ToastType} from './useToast';
|
|
import {useToastStore} from './useToast';
|
|
|
|
const toastStore = useToastStore();
|
|
|
|
const icons = {
|
|
success: CheckCircleIcon,
|
|
error: XCircleIcon,
|
|
info: InformationCircleIcon,
|
|
} as const;
|
|
|
|
const accent = (type: ToastType) => `accent-${type}`;
|
|
</script>
|
|
|
|
<template>
|
|
<div class="toast-container">
|
|
<TransitionGroup name="toast">
|
|
<div v-for="toast in toastStore.toasts" :key="toast.id" class="toast" :class="accent(toast.type)">
|
|
<component :is="icons[toast.type]" class="icon" />
|
|
<div class="body">
|
|
<span v-if="toast.title" class="title">{{ toast.title }}</span>
|
|
<span class="message">{{ toast.message }}</span>
|
|
</div>
|
|
<button class="btn-icon close" @click="toastStore.dismiss(toast.id)">
|
|
<XMarkIcon />
|
|
</button>
|
|
</div>
|
|
</TransitionGroup>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
@reference "@/style.css";
|
|
|
|
div.toast-container {
|
|
@apply fixed top-4 right-4 z-50 flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)];
|
|
}
|
|
|
|
div.toast {
|
|
@apply flex items-start gap-2 p-3 rounded bg-slate-700 border-l-4 shadow-lg;
|
|
|
|
&.accent-success {
|
|
@apply border-emerald-500;
|
|
> .icon { @apply text-emerald-500; }
|
|
}
|
|
&.accent-error {
|
|
@apply border-amber-700;
|
|
> .icon { @apply text-amber-700; }
|
|
}
|
|
&.accent-info {
|
|
@apply border-sky-600;
|
|
> .icon { @apply text-sky-600; }
|
|
}
|
|
|
|
> .icon {
|
|
@apply w-6 h-6 shrink-0;
|
|
}
|
|
|
|
> .body {
|
|
@apply flex flex-col grow min-w-0;
|
|
|
|
> .title {
|
|
@apply font-semibold;
|
|
}
|
|
> .message {
|
|
@apply break-words;
|
|
}
|
|
}
|
|
|
|
> .close {
|
|
@apply shrink-0;
|
|
}
|
|
}
|
|
|
|
.toast-enter-active,
|
|
.toast-leave-active,
|
|
.toast-move {
|
|
@apply transition-all duration-300;
|
|
}
|
|
.toast-enter-from {
|
|
@apply opacity-0 translate-x-8;
|
|
}
|
|
.toast-leave-to {
|
|
@apply opacity-0 -translate-y-8;
|
|
}
|
|
.toast-leave-active {
|
|
@apply absolute w-full;
|
|
}
|
|
</style>
|