From 7b2e61bf4dd1e10ade956b2ac091dbb44d41d525 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 14 Aug 2025 09:51:40 -0500 Subject: Integrate Sonner for toasts (#8839) * Integrate Sonner for toasts * Fix animation on iOS * Refactor API * Update e2e file --- src/components/Toast/index.web.tsx | 134 +++++++++---------------------------- 1 file changed, 31 insertions(+), 103 deletions(-) (limited to 'src/components/Toast/index.web.tsx') diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx index f2517e28d..857ed7b39 100644 --- a/src/components/Toast/index.web.tsx +++ b/src/components/Toast/index.web.tsx @@ -1,112 +1,40 @@ -/* - * Note: relies on styles in #/styles.css - */ - -import {useEffect, useState} from 'react' -import {AccessibilityInfo, Pressable, View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {toast as sonner, Toaster} from 'sonner' -import {atoms as a, useBreakpoints} from '#/alf' -import {DEFAULT_TOAST_DURATION} from '#/components/Toast/const' +import {atoms as a} from '#/alf' +import {DURATION} from '#/components/Toast/const' import {Toast} from '#/components/Toast/Toast' -import {type ToastApi, type ToastType} from '#/components/Toast/types' - -const TOAST_ANIMATION_STYLES = { - entering: { - animation: 'toastFadeIn 0.3s ease-out forwards', - }, - exiting: { - animation: 'toastFadeOut 0.2s ease-in forwards', - }, -} - -interface ActiveToast { - type: ToastType - content: React.ReactNode - a11yLabel: string -} -type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void -let globalSetActiveToast: GlobalSetActiveToast | undefined -let toastTimeout: NodeJS.Timeout | undefined -type ToastContainerProps = {} - -export const ToastContainer: React.FC = ({}) => { - const {_} = useLingui() - const {gtPhone} = useBreakpoints() - const [activeToast, setActiveToast] = useState() - const [isExiting, setIsExiting] = useState(false) - - useEffect(() => { - globalSetActiveToast = (t: ActiveToast | undefined) => { - if (!t && activeToast) { - setIsExiting(true) - setTimeout(() => { - setActiveToast(t) - setIsExiting(false) - }, 200) - } else { - if (t) { - AccessibilityInfo.announceForAccessibility(t.a11yLabel) - } - setActiveToast(t) - setIsExiting(false) - } - } - }, [activeToast]) +import {type BaseToastOptions} from '#/components/Toast/types' +/** + * Toasts are rendered in a global outlet, which is placed at the top of the + * component tree. + */ +export function ToastOutlet() { return ( - <> - {activeToast && ( - - - setActiveToast(undefined)} - /> - - )} - + ) } -export const toast: ToastApi = { - show(props) { - if (toastTimeout) { - clearTimeout(toastTimeout) - } - - globalSetActiveToast?.({ - type: props.type, - content: props.content, - a11yLabel: props.a11yLabel, - }) +/** + * Access the full Sonner API + */ +export const api = sonner - toastTimeout = setTimeout(() => { - globalSetActiveToast?.(undefined) - }, props.duration || DEFAULT_TOAST_DURATION) - }, +/** + * Our base toast API, using the `Toast` export of this file. + */ +export function show( + content: React.ReactNode, + {type, ...options}: BaseToastOptions = {}, +) { + sonner(, { + unstyled: true, // required on web + ...options, + duration: options?.duration ?? DURATION, + }) } -- cgit 1.4.1