11<script lang="ts" setup>
2- import { ref , watchEffect } from ' vue' ;
2+ import { computed , ref , watchEffect } from ' vue' ;
33import { useToastsStore } from ' ./useToastsStore' ;
44
5+ const props = defineProps <YvToastsProps >();
6+
57const { activeToasts, toast } = useToastsStore ();
68
9+ type YvToastsProps = {
10+ /**
11+ * Number of toasts visible by default
12+ */
13+ stackLimit? : number ;
14+ };
15+
716// Store timeout data for each toast
817const toastTimers = ref (new Map < string , {
918 timeoutId: number ;
@@ -13,7 +22,6 @@ const toastTimers = ref(new Map<string, {
1322 stop : () => void ;
1423}> ());
1524
16- // Default durations for different toast types (in milliseconds)
1725const defaultDurations = {
1826 success: 4000 ,
1927 error: 6000 ,
@@ -51,17 +59,14 @@ function handleToastClick(toastId: string) {
5159}
5260
5361function createAutoDissmiss(toastId : string , type : string , duration ? : number ) {
54- // Check if toast should auto-dismiss
5562 const shouldAutoDismiss = duration !== Infinity && duration !== 0 ;
5663 if (! shouldAutoDismiss )
5764 return null ;
5865
59- // Get duration
6066 const autoDismissDelay = duration
6167 || defaultDurations [type as keyof typeof defaultDurations ]
6268 || defaultDurations .info ;
6369
64- // Create timeout
6570 const startTime = Date .now ();
6671 const timeoutId = window .setTimeout (() => {
6772 toast .dismiss (toastId );
@@ -83,32 +88,23 @@ function createAutoDissmiss(toastId: string, type: string, duration?: number) {
8388function pauseAutoDissmiss(toastId : string ) {
8489 const timer = toastTimers .value .get (toastId );
8590 if (timer ) {
86- // Clear the current timeout
8791 clearTimeout (timer .timeoutId );
88-
89- // Calculate remaining time
9092 const elapsed = Date .now () - timer .startTime ;
9193 timer .remainingTime = Math .max (0 , timer .originalDuration - elapsed );
92-
9394 toastTimers .value .set (toastId , timer );
9495 }
9596}
9697
9798function resumeAutoDissmiss(toastId : string ) {
9899 const timer = toastTimers .value .get (toastId );
99100 if (timer && timer .remainingTime > 0 ) {
100- // Create new timeout with remaining time
101101 timer .startTime = Date .now ();
102102 timer .originalDuration = timer .remainingTime ;
103-
104103 timer .timeoutId = window .setTimeout (() => {
105104 toast .dismiss (toastId );
106105 toastTimers .value .delete (toastId );
107106 }, timer .remainingTime );
108-
109- // Update the stop function to reference the new timeoutId
110107 timer .stop = () => clearTimeout (timer .timeoutId );
111-
112108 toastTimers .value .set (toastId , timer );
113109 }
114110}
@@ -117,7 +113,6 @@ function resumeAutoDissmiss(toastId: string) {
117113watchEffect (() => {
118114 const activeToastIds = new Set (activeToasts .map (t => t .id ));
119115
120- // Set up timers for new toasts
121116 activeToasts .forEach ((activeToast ) => {
122117 if (! toastTimers .value .has (activeToast .id )) {
123118 createAutoDissmiss (
@@ -128,7 +123,6 @@ watchEffect(() => {
128123 }
129124 });
130125
131- // Clean up timers for removed toasts
132126 Array .from (toastTimers .value .keys ()).forEach ((toastId ) => {
133127 if (! activeToastIds .has (toastId )) {
134128 const timer = toastTimers .value .get (toastId );
@@ -139,18 +133,37 @@ watchEffect(() => {
139133 }
140134 });
141135});
136+
137+ // --- STACKING LOGIC ---
138+ const stackLimit = props .stackLimit || 4 ; // Number of toasts visible by default
139+ const isStackHovered = ref (false );
140+
141+ const visibleToasts = computed (() => {
142+ if (isStackHovered .value )
143+ return activeToasts ;
144+ return activeToasts .slice (0 , stackLimit );
145+ });
146+
147+ const hiddenCount = computed (() => {
148+ const count = activeToasts .length - stackLimit ;
149+ return count > 0 ? count : 0 ;
150+ });
142151 </script >
143152
144153<template >
145154 <div class =" fixed top-0 left-0 right-0 z-[13] pointer-events-none" >
146- <div class =" flex flex-col items-center gap-2 p-4 pt-[55px]" >
155+ <div
156+ class =" flex flex-col items-center gap-2 p-4 pt-[55px]"
157+ @mouseenter =" isStackHovered = true"
158+ @mouseleave =" isStackHovered = false"
159+ >
147160 <TransitionGroup
148161 name =" toast"
149162 tag =" div"
150163 class =" flex flex-col items-center gap-2"
151164 >
152165 <div
153- v-for =" activeToast in activeToasts "
166+ v-for =" activeToast in visibleToasts "
154167 :key =" activeToast.id"
155168 sc-toast-base bg =" opacity-90 background"
156169 flex =" ~ items-center gap-3"
@@ -162,13 +175,10 @@ watchEffect(() => {
162175 @mouseenter =" pauseAutoDissmiss(activeToast.id)"
163176 @mouseleave =" resumeAutoDissmiss(activeToast.id)"
164177 >
165- <!-- Icon -->
166178 <span
167179 class =" inline-block text-sm align-middle"
168180 :class =" getToastIcon(activeToast.type || 'info')"
169181 />
170-
171- <!-- Content -->
172182 <div class =" flex-1" >
173183 <div v-if =" activeToast.title" class =" font-medium" >
174184 {{ activeToast.title }}
@@ -183,6 +193,13 @@ watchEffect(() => {
183193 </div >
184194 </div >
185195 </TransitionGroup >
196+
197+ <div
198+ v-if =" hiddenCount > 0 && !isStackHovered"
199+ class =" text-xs cursor-pointer text-neutral dark:text-neutral-400/90"
200+ >
201+ +{{ hiddenCount }} more
202+ </div >
186203 </div >
187204 </div >
188205</template >
0 commit comments