90 lines
2.9 KiB
Vue
90 lines
2.9 KiB
Vue
<template>
|
||
<ToastProvider>
|
||
<ToastRoot v-for="toast in model" :key="toast.id" :duration="toast.duration" class="ToastRoot bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 group" :open="toast.state ?? true" @update:open="(state: boolean) => tryClose(toast, state)" :data-type="toast.type ?? 'info'">
|
||
<div class="grid grid-cols-8 px-3 pt-2 pb-2">
|
||
<ToastTitle v-if="toast.title" class="font-semibold text-xl col-span-7 text-light-70 dark:text-dark-70" asChild><h4>{{ toast.title }}</h4></ToastTitle>
|
||
<ToastClose v-if="toast.closeable" aria-label="Close" class="text-xl -translate-y-2 translate-x-4 cursor-pointer"><span aria-hidden>×</span></ToastClose>
|
||
<ToastDescription v-if="toast.content" class="text-sm col-span-8 text-light-70 dark:text-dark-70" asChild><span>{{ toast.content }}</span></ToastDescription>
|
||
</div>
|
||
<TimerProgress v-if="toast.timer" shape="thin" :delay="toast.duration" class="mb-0 mt-0 w-full group-data-[type=error]:bg-light-redBack dark:group-data-[type=error]:bg-dark-redBack group-data-[type=error]:*:bg-light-red dark:group-data-[type=error]:*:bg-dark-red
|
||
group-data-[type=success]:bg-light-greenBack dark:group-data-[type=success]:bg-dark-greenBack group-data-[type=success]:*:bg-light-green dark:group-data-[type=success]:*:bg-dark-green" @finish="() => tryClose(toast, false)" />
|
||
</ToastRoot>
|
||
|
||
<ToastViewport class="fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72" />
|
||
</ToastProvider>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const model = defineModel<ExtraToastConfig[]>();
|
||
|
||
function tryClose(config: ExtraToastConfig, state: boolean)
|
||
{
|
||
if(!state)
|
||
{
|
||
const m = model.value;
|
||
if(m)
|
||
{
|
||
const idx = m?.findIndex(e => e.id === config.id);
|
||
m[idx].state = false;
|
||
model.value = m;
|
||
}
|
||
setTimeout(() => model.value?.splice(model.value?.findIndex(e => e.id === config.id), 1), 500);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.ToastRoot[data-type='error'] {
|
||
@apply border-light-red;
|
||
@apply dark:border-dark-red;
|
||
@apply bg-light-redBack;
|
||
@apply dark:bg-dark-redBack;
|
||
}
|
||
.ToastRoot[data-type='success'] {
|
||
@apply border-light-green;
|
||
@apply dark:border-dark-green;
|
||
@apply bg-light-greenBack;
|
||
@apply dark:bg-dark-greenBack;
|
||
}
|
||
.ToastRoot[data-state='open'] {
|
||
animation: slideIn .15s cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
.ToastRoot[data-state='closed'] {
|
||
animation: hide .1s ease-in;
|
||
}
|
||
.ToastRoot[data-swipe='move'] {
|
||
transform: translateX(var(--radix-toast-swipe-move-x));
|
||
}
|
||
.ToastRoot[data-swipe='cancel'] {
|
||
transform: translateX(0);
|
||
transition: transform .2s ease-out;
|
||
}
|
||
.ToastRoot[data-swipe='end'] {
|
||
animation: swipeRight .1s ease-out;
|
||
}
|
||
|
||
@keyframes hide {
|
||
from {
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(calc(100% + var(--viewport-padding)));
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
@keyframes swipeRight {
|
||
from {
|
||
transform: translateX(var(--radix-toast-swipe-end-x));
|
||
}
|
||
to {
|
||
transform: translateX(100%);
|
||
}
|
||
}
|
||
</style> |