| @@ -13,6 +13,7 @@ import { · 12 unmodified lines |
| 13 | 13 | import { cn } from "~/lib/utils"; |
| 14 | 14 | import { buttonVariants } from "~/components/ui/button"; |
| 15 | 15 | import { useStore } from "~/store"; |
| 16 | +import { shouldHideCollapsedToastContent } from "./toast.logic"; |
| 16 | 17 | |
| 17 | 18 | type ThreadToastData = { |
| 18 | 19 | threadId?: string | null; |
| @@ -146,6 +147,9 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { · 127 unmodified lines |
| 146 | 147 | const { toasts } = Toast.useToastManager<ThreadToastData>(); |
| 147 | 148 | const activeThreadId = state.activeThreadId; |
| 148 | 149 | const isTop = position.startsWith("top"); |
| 150 | + const visibleToasts = toasts.filter((toast) => |
| 151 | + shouldRenderForActiveThread(toast.data, activeThreadId), |
| 152 | + ); |
| 149 | 153 | |
| 150 | 154 | useEffect(() => { |
| 151 | 155 | const activeToastIds = new Set(toasts.map((toast) => toast.id)); |
| @@ -172,100 +176,105 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { · 20 unmodified lines |
| 172 | 176 | data-position={position} |
| 173 | 177 | data-slot="toast-viewport" |
| 174 | 178 | > |
| 175 | | − {toasts |
| 176 | | − .filter((toast) => shouldRenderForActiveThread(toast.data, activeThreadId)) |
| 177 | | − .map((toast) => { |
| 178 | | − const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; |
| 179 | + {visibleToasts.map((toast, visibleIndex) => { |
| 180 | + const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; |
| 181 | + const hideCollapsedContent = shouldHideCollapsedToastContent( |
| 182 | + visibleIndex, |
| 183 | + visibleToasts.length, |
| 184 | + ); |
| 179 | 185 | |
| 180 | | − return ( |
| 181 | | − <Toast.Root |
| 186 | + return ( |
| 187 | + <Toast.Root |
| 188 | + className={cn( |
| 189 | + "absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 190 | + // Base positioning using data-position |
| 191 | + "data-[position*=right]:right-0 data-[position*=right]:left-auto", |
| 192 | + "data-[position*=left]:right-auto data-[position*=left]:left-0", |
| 193 | + "data-[position*=center]:right-0 data-[position*=center]:left-0", |
| 194 | + "data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top", |
| 195 | + "data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom", |
| 196 | + // Gap fill for hover |
| 197 | + "after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full", |
| 198 | + "data-[position*=top]:after:top-full", |
| 199 | + "data-[position*=bottom]:after:bottom-full", |
| 200 | + // Define some variables |
| 201 | + "[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]", |
| 202 | + // Define offset-y variable |
| 203 | + "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]", |
| 204 | + "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]", |
| 205 | + // Default state transform |
| 206 | + "data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", |
| 207 | + "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", |
| 208 | + // Limited state |
| 209 | + "data-limited:opacity-0", |
| 210 | + // Expanded state |
| 211 | + "data-expanded:h-(--toast-height)", |
| 212 | + "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]", |
| 213 | + // Starting and ending animations |
| 214 | + "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]", |
| 215 | + "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]", |
| 216 | + "data-ending-style:opacity-0", |
| 217 | + // Ending animations (direction-aware) |
| 218 | + "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]", |
| 219 | + "data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 220 | + "data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 221 | + "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", |
| 222 | + "data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", |
| 223 | + // Ending animations (expanded) |
| 224 | + "data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 225 | + "data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 226 | + "data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", |
| 227 | + "data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", |
| 228 | + )} |
| 229 | + data-position={position} |
| 230 | + key={toast.id} |
| 231 | + swipeDirection={ |
| 232 | + position.includes("center") |
| 233 | + ? [isTop ? "up" : "down"] |
| 234 | + : position.includes("left") |
| 235 | + ? ["left", isTop ? "up" : "down"] |
| 236 | + : ["right", isTop ? "up" : "down"] |
| 237 | + } |
| 238 | + toast={toast} |
| 239 | + > |
| 240 | + <ThreadToastVisibleAutoDismiss |
| 241 | + dismissAfterVisibleMs={toast.data?.dismissAfterVisibleMs} |
| 242 | + toastId={toast.id} |
| 243 | + /> |
| 244 | + <Toast.Content |
| 182 | 245 | className={cn( |
| 183 | | − "absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 184 | | − // Base positioning using data-position |
| 185 | | − "data-[position*=right]:right-0 data-[position*=right]:left-auto", |
| 186 | | − "data-[position*=left]:right-auto data-[position*=left]:left-0", |
| 187 | | − "data-[position*=center]:right-0 data-[position*=center]:left-0", |
| 188 | | − "data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top", |
| 189 | | − "data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom", |
| 190 | | − // Gap fill for hover |
| 191 | | − "after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full", |
| 192 | | − "data-[position*=top]:after:top-full", |
| 193 | | − "data-[position*=bottom]:after:bottom-full", |
| 194 | | − // Define some variables |
| 195 | | − "[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]", |
| 196 | | − // Define offset-y variable |
| 197 | | − "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]", |
| 198 | | − "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]", |
| 199 | | − // Default state transform |
| 200 | | − "data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", |
| 201 | | − "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", |
| 202 | | − // Limited state |
| 203 | | − "data-limited:opacity-0", |
| 204 | | − // Expanded state |
| 205 | | − "data-expanded:h-(--toast-height)", |
| 206 | | − "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]", |
| 207 | | − // Starting and ending animations |
| 208 | | − "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]", |
| 209 | | − "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]", |
| 210 | | − "data-ending-style:opacity-0", |
| 211 | | − // Ending animations (direction-aware) |
| 212 | | − "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]", |
| 213 | | − "data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 214 | | − "data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 215 | | − "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", |
| 216 | | − "data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", |
| 217 | | − // Ending animations (expanded) |
| 218 | | − "data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 219 | | − "data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", |
| 220 | | − "data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", |
| 221 | | − "data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", |
| 246 | + "pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-expanded:opacity-100", |
| 247 | + hideCollapsedContent && |
| 248 | + "not-data-expanded:pointer-events-none not-data-expanded:opacity-0", |
| 222 | 249 | )} |
| 223 | | − data-position={position} |
| 224 | | − key={toast.id} |
| 225 | | − swipeDirection={ |
| 226 | | − position.includes("center") |
| 227 | | − ? [isTop ? "up" : "down"] |
| 228 | | − : position.includes("left") |
| 229 | | − ? ["left", isTop ? "up" : "down"] |
| 230 | | − : ["right", isTop ? "up" : "down"] |
| 231 | | − } |
| 232 | | − toast={toast} |
| 233 | 250 | > |
| 234 | | − <ThreadToastVisibleAutoDismiss |
| 235 | | − dismissAfterVisibleMs={toast.data?.dismissAfterVisibleMs} |
| 236 | | − toastId={toast.id} |
| 237 | | − /> |
| 238 | | − <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-behind:not-data-expanded:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100"> |
| 239 | | − <div className="flex gap-2"> |
| 240 | | − {Icon && ( |
| 241 | | − <div |
| 242 | | − className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0" |
| 243 | | − data-slot="toast-icon" |
| 244 | | − > |
| 245 | | − <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" /> |
| 246 | | − </div> |
| 247 | | − )} |
| 248 | | − |
| 249 | | − <div className="flex flex-col gap-0.5"> |
| 250 | | − <Toast.Title className="font-medium" data-slot="toast-title" /> |
| 251 | | − <Toast.Description |
| 252 | | − className="text-muted-foreground" |
| 253 | | − data-slot="toast-description" |
| 254 | | − /> |
| 255 | | − </div> |
| 256 | | − </div> |
| 257 | | − {toast.actionProps && ( |
| 258 | | − <Toast.Action |
| 259 | | − className={buttonVariants({ size: "xs" })} |
| 260 | | − data-slot="toast-action" |
| 251 | + <div className="flex gap-2"> |
| 252 | + {Icon && ( |
| 253 | + <div |
| 254 | + className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0" |
| 255 | + data-slot="toast-icon" |
| 261 | 256 | > |
| 262 | | − {toast.actionProps.children} |
| 263 | | − </Toast.Action> |
| 257 | + <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" /> |
| 258 | + </div> |
| 264 | 259 | )} |
| 265 | | − </Toast.Content> |
| 266 | | − </Toast.Root> |
| 267 | | − ); |
| 268 | | − })} |
| 260 | + |
| 261 | + <div className="flex flex-col gap-0.5"> |
| 262 | + <Toast.Title className="font-medium" data-slot="toast-title" /> |
| 263 | + <Toast.Description |
| 264 | + className="text-muted-foreground" |
| 265 | + data-slot="toast-description" |
| 266 | + /> |
| 267 | + </div> |
| 268 | + </div> |
| 269 | + {toast.actionProps && ( |
| 270 | + <Toast.Action className={buttonVariants({ size: "xs" })} data-slot="toast-action"> |
| 271 | + {toast.actionProps.children} |
| 272 | + </Toast.Action> |
| 273 | + )} |
| 274 | + </Toast.Content> |
| 275 | + </Toast.Root> |
| 276 | + ); |
| 277 | + })} |
| 269 | 278 | </Toast.Viewport> |
| 270 | 279 | </Toast.Portal> |
| 271 | 280 | ); |