diff options
Diffstat (limited to 'src/components/Toast/index.web.tsx')
-rw-r--r-- | src/components/Toast/index.web.tsx | 134 |
1 files changed, 31 insertions, 103 deletions
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<ToastContainerProps> = ({}) => { - const {_} = useLingui() - const {gtPhone} = useBreakpoints() - const [activeToast, setActiveToast] = useState<ActiveToast | undefined>() - 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 && ( - <View - style={[ - a.fixed, - { - left: a.px_xl.paddingLeft, - right: a.px_xl.paddingLeft, - bottom: a.px_xl.paddingLeft, - ...(isExiting - ? TOAST_ANIMATION_STYLES.exiting - : TOAST_ANIMATION_STYLES.entering), - }, - gtPhone && [ - { - maxWidth: 380, - }, - ], - ]}> - <Toast content={activeToast.content} type={activeToast.type} /> - <Pressable - style={[a.absolute, a.inset_0]} - accessibilityLabel={_( - msg({ - message: `Dismiss message`, - comment: `Accessibility label for dismissing a toast notification`, - }), - )} - accessibilityHint="" - onPress={() => setActiveToast(undefined)} - /> - </View> - )} - </> + <Toaster + position="bottom-left" + gap={a.gap_sm.gap} + offset={a.p_xl.padding} + mobileOffset={a.p_xl.padding} + /> ) } -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(<Toast content={content} type={type} />, { + unstyled: true, // required on web + ...options, + duration: options?.duration ?? DURATION, + }) } |