feat(#33): Add a global toast/notification component
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<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,
|
||||
.toast-leave-to {
|
||||
@apply opacity-0 translate-x-8;
|
||||
}
|
||||
.toast-leave-active {
|
||||
@apply absolute w-full;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user