diff --git a/studio/frontend/src/app/provider.tsx b/studio/frontend/src/app/provider.tsx index 8360186d1e..83238dadf0 100644 --- a/studio/frontend/src/app/provider.tsx +++ b/studio/frontend/src/app/provider.tsx @@ -291,7 +291,14 @@ export function AppProvider({ children }: AppProviderProps) { {children} - + ); } diff --git a/studio/frontend/src/components/ui/sonner.tsx b/studio/frontend/src/components/ui/sonner.tsx index 80319f10db..5bd3078761 100644 --- a/studio/frontend/src/components/ui/sonner.tsx +++ b/studio/frontend/src/components/ui/sonner.tsx @@ -13,11 +13,13 @@ import { useTheme } from "next-themes"; import { Toaster as Sonner, type ToasterProps } from "sonner"; const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme(); + // Use resolvedTheme so sonner's data-sonner-theme always matches the class + // next-themes puts on ; sonner-side "system" resolution can drift. + const { resolvedTheme } = useTheme(); return ( { "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", "--border-radius": "var(--radius)", + // Pin close button to the top-right corner inside the toast. + // Overrides sonner's default left placement and outside-corner + // translate; top offset is set via a rule in index.css since sonner + // hardcodes `top: 0` (not a CSS variable). + "--toast-close-button-start": "unset", + "--toast-close-button-end": "8px", + "--toast-close-button-transform": "none", } as React.CSSProperties } // No swipe gestures; keeps toast text selectable. @@ -71,7 +80,6 @@ const Toaster = ({ ...props }: ToasterProps) => { classNames: { toast: "cn-toast", description: "!text-muted-foreground", - closeButton: "!top-3 !right-3 !translate-y-0", }, }} {...props} diff --git a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts index c0982a05c0..03f80c19f2 100644 --- a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts +++ b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts @@ -729,7 +729,6 @@ export function useChatModelRuntime() { cancelLoading, ), duration: Infinity, - closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) { @@ -852,7 +851,6 @@ export function useChatModelRuntime() { cancelLoading, ), duration: Infinity, - closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) return; @@ -892,7 +890,6 @@ export function useChatModelRuntime() { cancelLoading, ), duration: Infinity, - closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) return; @@ -955,7 +952,6 @@ export function useChatModelRuntime() { cancelLoading, ), duration: Infinity, - closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) return; @@ -987,8 +983,7 @@ export function useChatModelRuntime() { toast.success(`${displayName} loaded`, { id: toastId, description: undefined, - closeButton: false, - duration: 2000, + duration: 8000, }); } notifyNative({ @@ -1007,8 +1002,7 @@ export function useChatModelRuntime() { toast.error(message, { id: toastId, description: undefined, - closeButton: false, - duration: 5000, + duration: 8000, }); } notifyNative({ diff --git a/studio/frontend/src/index.css b/studio/frontend/src/index.css index 237370df1f..3702149059 100644 --- a/studio/frontend/src/index.css +++ b/studio/frontend/src/index.css @@ -1160,3 +1160,12 @@ animation: none; mix-blend-mode: normal; } + +/* Override sonner's hardcoded top: 0 on the toast close button. */ +[data-sonner-toast][data-styled="true"] [data-close-button] { + top: 8px !important; +} +/* Bump the X stroke so it stays visible against dark backgrounds. */ +[data-sonner-toast][data-styled="true"] [data-close-button] svg { + stroke-width: 2.25; +}