feat(ui): compact toast notifications and skeleton editor selection

This commit is contained in:
Matthieu
2025-09-30 15:57:02 +02:00
parent 9a55e29b74
commit 7b2e509b04
2 changed files with 31 additions and 18 deletions

View File

@@ -1,5 +1,7 @@
<template>
<div class="toast-container fixed top-4 right-4 z-50 space-y-2 pointer-events-none">
<div
class="toast-container pointer-events-none fixed bottom-4 right-4 z-50 flex flex-col gap-2 items-end"
>
<TransitionGroup name="toast">
<div
v-for="toast in toasts"
@@ -7,11 +9,11 @@
class="toast-item"
:class="[
'transform transition-all duration-300 ease-in-out',
toast.visible ? 'translate-x-0 opacity-100 pointer-events-auto' : 'translate-x-full opacity-0 pointer-events-none'
toast.visible ? 'translate-y-0 opacity-100 pointer-events-auto' : 'translate-y-4 opacity-0 pointer-events-none'
]"
>
<div
class="alert shadow-lg max-w-sm"
class="alert toast-card shadow-md px-3 py-2 text-sm"
:class="getToastClasses(toast.type)"
>
<div class="flex items-center gap-2">
@@ -19,37 +21,37 @@
<div class="flex-shrink-0">
<IconLucideCheck
v-if="toast.type === 'success'"
class="w-5 h-5"
class="w-4 h-4"
aria-hidden="true"
/>
<IconLucideX
v-else-if="toast.type === 'error'"
class="w-5 h-5"
class="w-4 h-4"
aria-hidden="true"
/>
<IconLucideAlertTriangle
v-else-if="toast.type === 'warning'"
class="w-5 h-5"
class="w-4 h-4"
aria-hidden="true"
/>
<IconLucideInfo
v-else
class="w-5 h-5"
class="w-4 h-4"
aria-hidden="true"
/>
</div>
<!-- Message -->
<div class="flex-1">
<span class="text-sm font-medium">{{ toast.message }}</span>
<span class="font-medium">{{ toast.message }}</span>
</div>
<!-- Close button -->
<button
class="btn btn-ghost btn-xs"
class="btn btn-ghost btn-2xs"
@click="removeToast(toast.id)"
>
<IconLucideX class="w-4 h-4" aria-hidden="true" />
<IconLucideX class="w-3 h-3" aria-hidden="true" />
</button>
</div>
</div>
@@ -70,13 +72,13 @@ const { toasts, removeToast } = useToast()
const getToastClasses = (type) => {
switch (type) {
case 'success':
return 'alert-success'
return 'alert-success text-success-content'
case 'error':
return 'alert-error'
return 'alert-error text-error-content'
case 'warning':
return 'alert-warning'
return 'alert-warning text-warning-content'
case 'info':
return 'alert-info'
return 'alert-info text-info-content'
default:
return 'alert-info'
}
@@ -91,15 +93,21 @@ const getToastClasses = (type) => {
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
transform: translateY(16px);
}
.toast-leave-to {
opacity: 0;
transform: translateX(100%);
transform: translateY(16px);
}
.toast-move {
transition: transform 0.3s ease;
}
.toast-card {
max-width: 20rem;
pointer-events: auto;
border-radius: 0.75rem;
}
</style>

View File

@@ -1,10 +1,11 @@
import { ref } from 'vue'
const toasts = ref([])
const MAX_TOASTS = 3
let nextId = 1
export function useToast () {
const showToast = (message, type = 'info', duration = 5000) => {
const showToast = (message, type = 'info', duration = 3500) => {
const id = nextId++
const toast = {
id,
@@ -13,6 +14,10 @@ export function useToast () {
visible: true
}
if (toasts.value.length >= MAX_TOASTS) {
toasts.value.shift()
}
toasts.value.push(toast)
// Auto-remove after duration
@@ -27,7 +32,7 @@ export function useToast () {
return showToast(message, 'success', duration)
}
const showError = (message, duration = 7000) => {
const showError = (message, duration = 5000) => {
return showToast(message, 'error', duration)
}