slidein notification
show error message to user
This commit is contained in:
parent
354d3b8972
commit
b1dbb806c6
4 changed files with 183 additions and 1 deletions
130
src/components/Notification.vue
Normal file
130
src/components/Notification.vue
Normal file
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<div
|
||||
class="fixed right-0 flex flex-col gap-4 p-2 w-full md:w-80"
|
||||
: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"]),
|
||||
close(id: number) {
|
||||
this.revoke(id);
|
||||
},
|
||||
hovering(id: number, value: boolean, timeout?: number) {
|
||||
if (value) {
|
||||
clearTimeout(this.timeouts[id]);
|
||||
} else {
|
||||
this.timeouts[id] = setTimeout(() => {
|
||||
this.revoke(id);
|
||||
}, timeout);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue