studio: add dismissable toasts with corner close button (#5509)
* studio: add dismissable toasts with corner close button - Enable Sonner's close button globally on the Toaster, so every toast (model load progress, model loaded, load failure, etc.) gets an X that users can click to dismiss without waiting for the auto-dismiss timer. This matches the Claude desktop notification behavior. - Drop the per-toast 'closeButton: false' overrides in the model load runtime so they inherit the global default. The existing 'onDismiss' handler already flips state to show an inline header status, so the X on the loading toast hides the toast without canceling the load (Cancel still aborts). - Pin the close button to the top-right corner inside the toast box. Overrides Sonner's left-side default placement, outside-corner translate, and hardcoded 'top: 0'. Top is set via a small rule in index.css because Sonner does not expose it as a CSS variable. - Add a small offset on the Toaster so toasts sit at the chat header line, shifted left of the parameters and settings buttons on the right edge instead of stacking on top of them. - Bump the post-load success and failure durations from 2s and 5s to 8s so users actually have time to read and click the new close X before the toast auto-dismisses. * studio: explicit boolean for closeButton prop to satisfy biome * studio: keep close button X visible in dark mode Two defensive fixes for the dark-mode close button visibility: - Use resolvedTheme so sonner's data-sonner-theme always matches the class next-themes applies to <html>. Passing theme can be 'system', which makes sonner resolve via its own media query; that can disagree with next-themes (Tauri webview, hydration races, OS quirks), leaving CSS vars dark while sonner still applies its light close-button colors (dark X on dark background). - Bump the close-icon stroke from sonner's default 1.5 to 2.25 so the X is readable on a 12x12 svg sitting on dark backgrounds. --------- Co-authored-by: shimmyshimmer <datta_mike@hotmail.com>
This commit is contained in:
parent
84d9d56062
commit
eb6b0c6db6
4 changed files with 30 additions and 12 deletions
|
|
@ -291,7 +291,14 @@ export function AppProvider({ children }: AppProviderProps) {
|
|||
<TauriWrapper>
|
||||
{children}
|
||||
</TauriWrapper>
|
||||
<Toaster position="top-right" visibleToasts={2} expand={true} />
|
||||
<Toaster
|
||||
position="top-right"
|
||||
visibleToasts={2}
|
||||
expand={true}
|
||||
closeButton={true}
|
||||
// Clear the chat header buttons on the right.
|
||||
offset={{ top: 12, right: 64 }}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <html>; sonner-side "system" resolution can drift.
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
theme={(resolvedTheme as ToasterProps["theme"]) ?? "light"}
|
||||
className="toaster group"
|
||||
duration={5000}
|
||||
icons={{
|
||||
|
|
@ -63,6 +65,13 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||
"--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}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue