Add toast notification system: implement useToast composable and ToastContainer component
This commit is contained in:
1 parent
46951be40f
commit
2b368cede4
2 files changed
+107
No files matched your search
@@ -0,0 +1,30 @@
|
||||
type Toast = {
|
||||
id: number
|
||||
message: string
|
||||
type?: "success" | "error" | "info"
|
||||
}
|
||||
|
||||
const toasts = ref<Toast[]>([])
|
||||
|
||||
export const useToast = () => {
|
||||
const show = (
|
||||
message: string,
|
||||
type: Toast["type"] = "info",
|
||||
duration = 5 * 1000
|
||||
): number => {
|
||||
const id = toasts.value.length + 1
|
||||
toasts.value.push({ id, message, type })
|
||||
|
||||
setTimeout(() => {
|
||||
remove(id)
|
||||
}, duration)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
const remove = (id: number) => {
|
||||
toasts.value = toasts.value.filter((t) => t.id != id)
|
||||
}
|
||||
|
||||
return { toasts, show, remove }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
const { toasts, remove } = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="toast-container">
|
||||
<TransitionGroup name="toast">
|
||||
<div
|
||||
v-for="toast in toasts"
|
||||
:key="toast.id"
|
||||
class="toast"
|
||||
:class="[toast.type]"
|
||||
@click="remove(toast.id)"
|
||||
>
|
||||
{{ toast.message }}
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/variables" as *;
|
||||
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
background-color: $color-background;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $standard-border-radius;
|
||||
|
||||
padding: 10px 16px;
|
||||
min-width: 200px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.toast-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
|
||||
.toast-enter-active {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.toast-enter-to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast-leave-active {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.toast-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user