diff options
author | Eric Bailey <git@esb.lol> | 2025-07-31 10:15:35 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-07-31 10:15:35 -0500 |
commit | 3bcfcba6d8176bac03202b496110915da748b0f1 (patch) | |
tree | 68c75c7c80945a8a5f5a32522dd9aa29f119e02a /src/components/Toast/index.web.tsx | |
parent | 33e071494881b13696e24b334857e594f29a4b1d (diff) | |
download | voidsky-3bcfcba6d8176bac03202b496110915da748b0f1.tar.zst |
Some toasts cleanup and reorg (#8748)
* Reorg * Move animation into css file * Update style comment * Extract core component, use platform-specific wrappers * Pull out platform specific styles * Just move styles into Toast component itself * Rename cleanup * Update API * Add duration optional prop * Add some type docs * add exp eased slide aniamtions * Make toasts full width on mobile web --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/components/Toast/index.web.tsx')
-rw-r--r-- | src/components/Toast/index.web.tsx | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx new file mode 100644 index 000000000..f6ceda568 --- /dev/null +++ b/src/components/Toast/index.web.tsx @@ -0,0 +1,107 @@ +/* + * 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 {atoms as a, useBreakpoints} from '#/alf' +import {DEFAULT_TOAST_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]) + + 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`Dismiss toast`)} + accessibilityHint="" + onPress={() => setActiveToast(undefined)} + /> + </View> + )} + </> + ) +} + +export const toast: ToastApi = { + show(props) { + if (toastTimeout) { + clearTimeout(toastTimeout) + } + + globalSetActiveToast?.({ + type: props.type, + content: props.content, + a11yLabel: props.a11yLabel, + }) + + toastTimeout = setTimeout(() => { + globalSetActiveToast?.(undefined) + }, props.duration || DEFAULT_TOAST_DURATION) + }, +} |