2024-12-19 11:16:05 +01:00
|
|
|
<template>
|
|
|
|
<div
|
2025-01-05 13:43:13 +01:00
|
|
|
class="fixed right-0 flex flex-col gap-4 p-2 w-full md:w-80 z-50"
|
2024-12-19 11:16:05 +01:00
|
|
|
:class="position == 'bottom' ? 'bottom-0' : 'top-0'"
|
|
|
|
>
|
|
|
|
<TransitionGroup
|
|
|
|
:enter-active-class="notifications.length > 1 ? [props.enter, props.moveDelay].join(' ') : props.enter"
|
|
|
|
:enter-from-class="props.enterFrom"
|
|
|
|
:enter-to-class="props.enterTo"
|
|
|
|
:leave-active-class="props.leave"
|
|
|
|
:leave-from-class="props.leaveFrom"
|
|
|
|
:leave-to-class="props.leaveTo"
|
|
|
|
:move-class="props.move"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
v-for="notification in sortedNotifications"
|
|
|
|
:key="notification.id"
|
|
|
|
class="relative p-2 bg-white flex flex-row gap-2 w-full overflow-hidden rounded-lg shadow-md"
|
|
|
|
:class="[
|
|
|
|
notification.type == 'error' ? 'border border-red-400' : '',
|
|
|
|
notification.type == 'warning' ? 'border border-red-400' : '',
|
|
|
|
notification.type == 'info' ? 'border border-gray-400' : '',
|
|
|
|
]"
|
|
|
|
>
|
|
|
|
<!-- @mouseover="hovering(notification.id, true)"
|
|
|
|
@mouseleave="hovering(notification.id, false)" -->
|
|
|
|
<ExclamationCircleIcon
|
|
|
|
v-if="notification.type == 'error'"
|
|
|
|
class="flex items-center justify-center min-w-12 w-12 h-12 bg-red-500 rounded-lg text-white p-1"
|
|
|
|
/>
|
|
|
|
<ExclamationTriangleIcon
|
|
|
|
v-if="notification.type == 'warning'"
|
|
|
|
class="flex items-center justify-center min-w-12 w-12 h-12 bg-red-500 rounded-lg text-white p-1"
|
|
|
|
/>
|
|
|
|
<InformationCircleIcon
|
|
|
|
v-if="notification.type == 'info'"
|
|
|
|
class="flex items-center justify-center min-w-12 w-12 h-12 bg-gray-500 rounded-lg text-white p-1"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div class="flex flex-col">
|
|
|
|
<span
|
|
|
|
class="font-semibold"
|
|
|
|
:class="[
|
|
|
|
notification.type == 'error' ? 'text-red-500' : '',
|
|
|
|
notification.type == 'warning' ? 'text-red-500' : '',
|
|
|
|
notification.type == 'info' ? 'text-gray-700' : '',
|
|
|
|
]"
|
|
|
|
>{{ notification.title }}</span
|
|
|
|
>
|
|
|
|
<p class="text-sm text-gray-600">{{ notification.text }}</p>
|
|
|
|
</div>
|
|
|
|
<XMarkIcon
|
|
|
|
@click="close(notification.id)"
|
|
|
|
class="absolute top-2 right-2 w-6 h-6 cursor-pointer text-gray-500"
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="absolute left-0 bottom-0 h-1 bg-gray-500 transition-[width] duration-[4900ms] ease-linear"
|
|
|
|
:class="notification.indicator ? 'w-0' : 'w-full'"
|
|
|
|
></div>
|
|
|
|
</div>
|
|
|
|
</TransitionGroup>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { defineComponent, TransitionGroup } from "vue";
|
|
|
|
import { mapState, mapActions } from "pinia";
|
|
|
|
import { useNotificationStore } from "@/stores/notification";
|
|
|
|
import {
|
|
|
|
ExclamationTriangleIcon,
|
|
|
|
ExclamationCircleIcon,
|
|
|
|
InformationCircleIcon,
|
|
|
|
XMarkIcon,
|
|
|
|
} from "@heroicons/vue/24/outline";
|
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
maxNotifications?: number;
|
|
|
|
enter?: string;
|
|
|
|
enterFrom?: string;
|
|
|
|
enterTo?: string;
|
|
|
|
leave?: string;
|
|
|
|
leaveFrom?: string;
|
|
|
|
leaveTo?: string;
|
|
|
|
move?: string;
|
|
|
|
moveDelay?: string;
|
|
|
|
position?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
maxNotifications: 10,
|
|
|
|
enter: "transform ease-out duration-300 transition",
|
|
|
|
enterFrom: "translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4",
|
|
|
|
enterTo: "translate-y-0 opacity-100 sm:translate-x-0",
|
|
|
|
leave: "transition ease-in duration-500",
|
|
|
|
leaveFrom: "opacity-100",
|
|
|
|
leaveTo: "opacity-0",
|
|
|
|
move: "transition duration-500",
|
|
|
|
moveDelay: "delay-300",
|
|
|
|
position: "bottom",
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
export default defineComponent({
|
|
|
|
computed: {
|
|
|
|
...mapState(useNotificationStore, ["notifications", "timeouts"]),
|
|
|
|
sortedNotifications() {
|
|
|
|
if (this.position === "bottom") {
|
|
|
|
return [...this.notifications];
|
|
|
|
}
|
|
|
|
return [...this.notifications].reverse();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
...mapActions(useNotificationStore, ["revoke"]),
|
2025-01-12 13:25:55 +01:00
|
|
|
close(id: string) {
|
2024-12-19 11:16:05 +01:00
|
|
|
this.revoke(id);
|
|
|
|
},
|
2025-01-12 13:25:55 +01:00
|
|
|
hovering(id: string, value: boolean, timeout?: number) {
|
2024-12-19 11:16:05 +01:00
|
|
|
if (value) {
|
|
|
|
clearTimeout(this.timeouts[id]);
|
|
|
|
} else {
|
|
|
|
this.timeouts[id] = setTimeout(() => {
|
|
|
|
this.revoke(id);
|
|
|
|
}, timeout);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|