diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index bb163b152d..5e60bd7995 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -83,6 +83,13 @@ type PromiseToastOptions = Omit & { loading: React.ReactNode success: React.ReactNode | ((data: T) => React.ReactNode) error?: React.ReactNode | ((err: unknown) => React.ReactNode) + /** + * Delay (ms) before the loading toast is shown. If the promise settles + * before this delay elapses, the loading toast is skipped entirely and the + * success/error toast appears directly. Defaults to 0 (loading shown + * immediately). + */ + loadingDelay?: number } /** @@ -92,9 +99,17 @@ type PromiseToastOptions = Omit & { */ export function promise( input: Promise, - {loading, success, error, ...options}: PromiseToastOptions, + { + loading, + success, + error, + loadingDelay = 0, + ...options + }: PromiseToastOptions, ): Promise { const id = nanoid() + let settled = false + let loadingShown = false const render = ( content: React.ReactNode, @@ -114,19 +129,27 @@ export function promise( ) } - render(loading, 'pending') + const timer = setTimeout(() => { + if (settled) return + loadingShown = true + render(loading, 'pending') + }, loadingDelay) return input.then( data => { + settled = true + clearTimeout(timer) const content = typeof success === 'function' ? success(data) : success render(content, 'success') return data }, err => { + settled = true + clearTimeout(timer) if (error !== undefined) { const content = typeof error === 'function' ? error(err) : error render(content, 'error') - } else { + } else if (loadingShown) { sonner.dismiss(id) } throw err diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx index 8f2207ea3e..a51e7fedd6 100644 --- a/src/components/Toast/index.web.tsx +++ b/src/components/Toast/index.web.tsx @@ -83,6 +83,13 @@ type PromiseToastOptions = Omit & { loading: React.ReactNode success: React.ReactNode | ((data: T) => React.ReactNode) error?: React.ReactNode | ((err: unknown) => React.ReactNode) + /** + * Delay (ms) before the loading toast is shown. If the promise settles + * before this delay elapses, the loading toast is skipped entirely and the + * success/error toast appears directly. Defaults to 0 (loading shown + * immediately). + */ + loadingDelay?: number } /** @@ -92,9 +99,17 @@ type PromiseToastOptions = Omit & { */ export function promise( input: Promise, - {loading, success, error, ...options}: PromiseToastOptions, + { + loading, + success, + error, + loadingDelay = 0, + ...options + }: PromiseToastOptions, ): Promise { const id = nanoid() + let settled = false + let loadingShown = false const render = ( content: React.ReactNode, @@ -115,19 +130,27 @@ export function promise( ) } - render(loading, 'pending') + const timer = setTimeout(() => { + if (settled) return + loadingShown = true + render(loading, 'pending') + }, loadingDelay) return input.then( data => { + settled = true + clearTimeout(timer) const content = typeof success === 'function' ? success(data) : success render(content, 'success') return data }, err => { + settled = true + clearTimeout(timer) if (error !== undefined) { const content = typeof error === 'function' ? error(err) : error render(content, 'error') - } else { + } else if (loadingShown) { sonner.dismiss(id) } throw err diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index b263a1881a..f9ab4d6739 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1035,6 +1035,7 @@ export const ComposePost = ({ })() Toast.promise(appViewReady, { + loadingDelay: 500, loading: (