22
33
44<div class =" afcl-toast flex items-center w-full p-4 rounded-lg shadow-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText border-l-4"
5- :class =" toast.variant == 'info' ? 'border-lightPrimary dark:border-darkPrimary' : toast.variant == 'danger' ? 'border-red-500 dark:border-red-800' : toast.variant == 'warning' ? 'border-orange-500 dark:border-orange-700' : 'border-green-500 dark:border-green-800' "
5+ :class =" variantConfig.borderClass "
66 role =" alert"
7+ @mouseenter =" pauseTimer"
8+ @mouseleave =" resumeTimer"
79>
8- <div v-if =" toast.variant == 'info'" class =" af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity rounded-lg dark:bg-darkPrimary dark:!text-blue-100" >
9- <IconInfoCircleSolid class="w-5 h-5" aria-hidden="true" />
10- </div >
11- <div v-else-if =" toast.variant == 'danger'" class =" af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200" >
12- <IconCloseCircleSolid class="w-5 h-5" aria-hidden="true" />
13- </div >
14- <div v-else-if =" toast.variant == 'warning'" class =" af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-700 dark:text-orange-200" >
15- <IconExclamationCircleSolid class="w-5 h-5" aria-hidden="true" />
16-
17- </div >
18- <div v-else class =" af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200" >
19- <IconCheckCircleSolid class="w-5 h-5" aria-hidden="true" />
10+ <div class =" relative w-8 h-8 flex items-center justify-center" >
11+ <div class =" absolute af-toast-icon inline-flex items-center justify-center flex-shrink-0 rounded-lg w-full h-full" :class =" variantConfig.iconClass" >
12+ <component :is =" variantConfig.icon" class =" w-5 h-5" aria-hidden =" true" />
13+ </div >
14+ <svg v-if =" hasTimer" class =" absolute inset-0 w-full h-full rotate-180" >
15+ <rect
16+ x =" 1"
17+ y =" 1"
18+ width =" calc(100% - 2px)"
19+ height =" calc(100% - 2px)"
20+ :class =" variantConfig.strokeClass"
21+ rx =" 8"
22+ fill =" none"
23+ stroke-width =" 2"
24+ pathLength =" 100"
25+ :stroke-dasharray =" `100`"
26+ :stroke-dashoffset =" dashOffset"
27+ />
28+ </svg >
2029 </div >
2130 <div class =" flex flex-col items-center justify-center break-all overflow-hidden [display:-webkit-box] [-webkit-box-orient:vertical] [-webkit-line-clamp:70]" >
2231 <div class =" ms-3 text-sm font-normal max-w-xs pr-2" v-if =" toast.messageHtml" v-html =" toast.messageHtml" ></div >
3645 <path stroke =" currentColor" stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
3746 </svg >
3847 </button >
39- <!-- <div class="h-full ml-3 w-1 rounded-r-lg" :class="toast.variant == 'info' ? 'bg-lightPrimary dark:bg-darkPrimary' : toast.variant == 'danger' ? 'bg-red-500 dark:bg-red-800' : toast.variant == 'warning' ? 'bg-orange-500 dark:bg-orange-700' : 'bg-green-500 dark:bg-green-800'"></div> -->
4048</div >
4149
4250
4351</template >
4452
4553<script setup lang="ts">
46- import { onMounted } from ' vue' ;
54+ import { onMounted , onUnmounted , computed , ref } from ' vue' ;
4755import { useToastStore } from ' @/stores/toast' ;
4856import { IconInfoCircleSolid , IconCloseCircleSolid , IconExclamationCircleSolid , IconCheckCircleSolid } from ' @iconify-prerendered/vue-flowbite' ;
4957
@@ -59,6 +67,69 @@ const props = defineProps<{
5967 buttons? : { value: any ; label: string }[];
6068 }
6169}>();
70+
71+ const VARIANT_CONFIGS = {
72+ info: {
73+ icon: IconInfoCircleSolid ,
74+ borderClass: ' border-lightPrimary dark:border-darkPrimary' ,
75+ iconClass: ' text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity dark:bg-darkPrimary dark:!text-blue-100' ,
76+ strokeClass: ' stroke-lightPrimary dark:stroke-darkPrimary' ,
77+ },
78+ danger: {
79+ icon: IconCloseCircleSolid ,
80+ borderClass: ' border-red-500 dark:border-red-800' ,
81+ iconClass: ' text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200' ,
82+ strokeClass: ' stroke-red-500 dark:stroke-red-800' ,
83+ },
84+ warning: {
85+ icon: IconExclamationCircleSolid ,
86+ borderClass: ' border-orange-500 dark:border-orange-700' ,
87+ iconClass: ' text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200' ,
88+ strokeClass: ' stroke-orange-500 dark:stroke-orange-700' ,
89+ },
90+ success: {
91+ icon: IconCheckCircleSolid ,
92+ borderClass: ' border-green-500 dark:border-green-800' ,
93+ iconClass: ' text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200' ,
94+ strokeClass: ' stroke-green-500 dark:stroke-green-800' ,
95+ },
96+ } as const ;
97+
98+ const variantConfig = computed (() => VARIANT_CONFIGS [props .toast .variant as keyof typeof VARIANT_CONFIGS ] ?? VARIANT_CONFIGS .success );
99+
100+ const hasTimer = computed (() => props .toast .timeout !== ' unlimited' );
101+ const totalMs = (typeof props .toast .timeout === ' number' ? props .toast .timeout : 10 ) * 1e3 ;
102+ const dashOffset = ref (0 );
103+ let rafId: number | null = null ;
104+ let remainingMs = totalMs ;
105+ let startedAt = 0 ;
106+
107+ function frame(now : number ) {
108+ const leftMs = Math .max (0 , remainingMs - (now - startedAt ));
109+ dashOffset .value = (1 - leftMs / totalMs ) * 100 ;
110+ if (leftMs <= 0 ) {
111+ rafId = null ;
112+ // resolve with undefined on auto-timeout
113+ toastStore .resolveToast (props .toast .id );
114+ emit (' close' );
115+ return ;
116+ }
117+ rafId = requestAnimationFrame (frame );
118+ }
119+
120+ function pauseTimer() {
121+ if (rafId === null ) return ;
122+ cancelAnimationFrame (rafId );
123+ rafId = null ;
124+ remainingMs = Math .max (0 , remainingMs - (performance .now () - startedAt ));
125+ }
126+
127+ function resumeTimer() {
128+ if (! hasTimer .value || rafId !== null || remainingMs <= 0 ) return ;
129+ startedAt = performance .now ();
130+ rafId = requestAnimationFrame (frame );
131+ }
132+
62133function closeToast() {
63134 // resolve with undefined on close (X button)
64135 toastStore .resolveToast (props .toast .id );
@@ -71,14 +142,11 @@ function onButtonClick(value: any) {
71142}
72143
73144onMounted (() => {
74- if (props .toast .timeout === ' unlimited' ) return ;
75- else {
76- setTimeout (() => {
77- // resolve with undefined on auto-timeout
78- toastStore .resolveToast (props .toast .id );
79- emit (' close' );
80- }, (props .toast .timeout || 10 ) * 1e3 );
81- }
145+ resumeTimer ();
146+ });
147+
148+ onUnmounted (() => {
149+ if (rafId !== null ) cancelAnimationFrame (rafId );
82150});
83151
84152 </script >
0 commit comments