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

View File

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