From 0beaf093af93bcd4f379149609dc58704fbff697 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 15 Feb 2026 19:54:27 -0800 Subject: [PATCH 1/2] Fix collapsed toast content visibility in filtered stacks - keep the front-most visible toast readable when thread filtering hides others - extract visibility rule into `toast.logic.ts` - add unit tests for collapsed-content visibility behavior --- .../web/src/components/ui/toast.logic.test.ts | 16 ++ apps/web/src/components/ui/toast.logic.ts | 9 + apps/web/src/components/ui/toast.tsx | 185 +++++++++--------- 3 files changed, 122 insertions(+), 88 deletions(-) create mode 100644 apps/web/src/components/ui/toast.logic.test.ts create mode 100644 apps/web/src/components/ui/toast.logic.ts diff --git a/apps/web/src/components/ui/toast.logic.test.ts b/apps/web/src/components/ui/toast.logic.test.ts new file mode 100644 index 000000000000..def62c884fef --- /dev/null +++ b/apps/web/src/components/ui/toast.logic.test.ts @@ -0,0 +1,16 @@ +import { assert, describe, it } from "vitest"; +import { shouldHideCollapsedToastContent } from "./toast.logic"; + +describe("shouldHideCollapsedToastContent", () => { + it("keeps a single visible toast readable", () => { + assert.equal(shouldHideCollapsedToastContent(0, 1), false); + }); + + it("keeps the front-most toast readable in a visible stack", () => { + assert.equal(shouldHideCollapsedToastContent(0, 3), false); + }); + + it("hides non-front toasts until the stack is expanded", () => { + assert.equal(shouldHideCollapsedToastContent(1, 3), true); + }); +}); diff --git a/apps/web/src/components/ui/toast.logic.ts b/apps/web/src/components/ui/toast.logic.ts new file mode 100644 index 000000000000..500203033abb --- /dev/null +++ b/apps/web/src/components/ui/toast.logic.ts @@ -0,0 +1,9 @@ +export function shouldHideCollapsedToastContent( + visibleToastIndex: number, + visibleToastCount: number, +): boolean { + // Keep the front-most toast readable even if Base UI marks it as "behind" + // due to toasts hidden by thread filtering. + if (visibleToastCount <= 1) return false; + return visibleToastIndex > 0; +} diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 19f9bb6b6190..68bef3fbafd7 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -13,6 +13,7 @@ import { import { cn } from "~/lib/utils"; import { buttonVariants } from "~/components/ui/button"; import { useStore } from "~/store"; +import { shouldHideCollapsedToastContent } from "./toast.logic"; type ThreadToastData = { threadId?: string | null; @@ -146,6 +147,9 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { const { toasts } = Toast.useToastManager(); const activeThreadId = state.activeThreadId; const isTop = position.startsWith("top"); + const visibleToasts = toasts.filter((toast) => + shouldRenderForActiveThread(toast.data, activeThreadId), + ); useEffect(() => { const activeToastIds = new Set(toasts.map((toast) => toast.id)); @@ -172,100 +176,105 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { data-position={position} data-slot="toast-viewport" > - {toasts - .filter((toast) => shouldRenderForActiveThread(toast.data, activeThreadId)) - .map((toast) => { - const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; + {visibleToasts.map((toast, visibleIndex) => { + const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; + const hideCollapsedContent = shouldHideCollapsedToastContent( + visibleIndex, + visibleToasts.length, + ); - return ( - + + - - -
- {Icon && ( -
- -
- )} - -
- - -
-
- {toast.actionProps && ( - + {Icon && ( +
- {toast.actionProps.children} - + +
)} -
-
- ); - })} + +
+ + +
+ + {toast.actionProps && ( + + {toast.actionProps.children} + + )} + + + ); + })} ); From 93edf3492d55887e81ddde31b6f83b004d4435f8 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 15 Feb 2026 19:57:30 -0800 Subject: [PATCH 2/2] Fix top-right toast enter and exit transforms - Add position-specific start transform for top-right toasts - Apply matching top-right end transform when dismissing without swipe --- apps/web/src/components/ui/toast.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 68bef3fbafd7..7b4879e9f029 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -213,9 +213,11 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { // Starting and ending animations "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]", "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]", + "data-[position*=top]:data-[position*=right]:data-starting-style:transform-[translateX(calc(100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", "data-ending-style:opacity-0", // Ending animations (direction-aware) "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]", + "data-[position*=top]:data-[position*=right]:data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateX(calc(100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", "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))]", "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))]", "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",