Improve snackbar style and duration
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="snackbar-wrapper pointer-events-none">
|
<div class="snackbar-wrapper pointer-events-none">
|
||||||
<transition-group name="snackbar" tag="div" class="snackbar-container">
|
<transition-group name="snackbar" tag="div" class="flex flex-col gap-2">
|
||||||
<div
|
<div
|
||||||
v-for="snack in snackbar.snacks"
|
v-for="snack in snackbar.snacks"
|
||||||
:key="snack.id"
|
:key="snack.id"
|
||||||
:class="['snackbar', snack.type]"
|
:class="['snackbar-item', typeClass(snack.type)]"
|
||||||
>
|
>
|
||||||
{{ snack.text }}
|
<span class="flex-1">{{ snack.text }}</span>
|
||||||
|
<button class="ml-3 text-white/70 hover:text-white" @click="snackbar.remove(snack.id)">×</button>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,10 +16,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useSnackbarStore } from '@/stores/useSnackbar'
|
import { useSnackbarStore } from '@/stores/useSnackbar'
|
||||||
|
|
||||||
|
const typeClass = (t) => {
|
||||||
|
switch (t) {
|
||||||
|
case 'success':
|
||||||
|
return 'bg-green-600/90'
|
||||||
|
case 'error':
|
||||||
|
return 'bg-red-600/90'
|
||||||
|
default:
|
||||||
|
return 'bg-gray-800/90'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const snackbar = useSnackbarStore()
|
const snackbar = useSnackbarStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.snackbar-wrapper {
|
.snackbar-wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
@@ -26,42 +39,16 @@ const snackbar = useSnackbarStore()
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.snackbar-container {
|
.snackbar-item {
|
||||||
display: flex;
|
@apply pointer-events-auto text-white px-4 py-3 rounded-md shadow-lg flex items-start;
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.snackbar {
|
|
||||||
pointer-events: auto;
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbar.info {
|
|
||||||
background-color: #323232;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbar.success {
|
|
||||||
background-color: #22c55e;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbar.error {
|
|
||||||
background-color: #ef4444;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbar-enter-active,
|
.snackbar-enter-active,
|
||||||
.snackbar-leave-active {
|
.snackbar-leave-active {
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
transition: opacity 0.35s, transform 0.35s;
|
||||||
}
|
}
|
||||||
.snackbar-enter-from,
|
.snackbar-enter-from,
|
||||||
.snackbar-leave-to {
|
.snackbar-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(20px);
|
transform: translateY(16px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ export const useRealtimeStore = defineStore('realtime', {
|
|||||||
const snackbar = useSnackbarStore();
|
const snackbar = useSnackbarStore();
|
||||||
const idPart = payload.new?.id || payload.old?.id;
|
const idPart = payload.new?.id || payload.old?.id;
|
||||||
const message = `${payload.operation} ${payload.table}${idPart ? ' #' + idPart : ''}`;
|
const message = `${payload.operation} ${payload.table}${idPart ? ' #' + idPart : ''}`;
|
||||||
snackbar.push({ text: message });
|
const type = payload.operation === 'DELETE' ? 'error' :
|
||||||
|
payload.operation === 'INSERT' ? 'success' : 'info';
|
||||||
|
snackbar.push({ text: message, type });
|
||||||
|
|
||||||
// mark badge for module and operation
|
// mark badge for module and operation
|
||||||
if (this.badges[payload.table]) {
|
if (this.badges[payload.table]) {
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { defineStore } from 'pinia'
|
|||||||
|
|
||||||
export const useSnackbarStore = defineStore('snackbar', {
|
export const useSnackbarStore = defineStore('snackbar', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
snacks: []
|
snacks: [],
|
||||||
|
maxSnacks: 5
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
push({ text, type = 'info', duration = 4000 }) {
|
push({ text, type = 'info', duration = 6000 }) {
|
||||||
const id = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2)
|
const id = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2)
|
||||||
const snack = { id, text, type }
|
const snack = { id, text, type }
|
||||||
|
if (this.snacks.length >= this.maxSnacks) {
|
||||||
|
this.snacks.shift()
|
||||||
|
}
|
||||||
this.snacks.push(snack)
|
this.snacks.push(snack)
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
setTimeout(() => this.remove(id), duration)
|
setTimeout(() => this.remove(id), duration)
|
||||||
|
|||||||
Reference in New Issue
Block a user