diff options
Diffstat (limited to 'src/view/com/util/Toast.web.tsx')
-rw-r--r-- | src/view/com/util/Toast.web.tsx | 148 |
1 files changed, 69 insertions, 79 deletions
diff --git a/src/view/com/util/Toast.web.tsx b/src/view/com/util/Toast.web.tsx index 949dce7ef..331a8b539 100644 --- a/src/view/com/util/Toast.web.tsx +++ b/src/view/com/util/Toast.web.tsx @@ -4,28 +4,19 @@ import {useEffect, useState} from 'react' import {Pressable, StyleSheet, Text, View} from 'react-native' +import {atoms as a, useTheme} from '#/alf' import { - FontAwesomeIcon, - type FontAwesomeIconStyle, - type Props as FontAwesomeProps, -} from '@fortawesome/react-native-fontawesome' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' - -const DURATION = 60000 - -export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' - -const TOAST_TYPE_TO_ICON: Record<ToastType, FontAwesomeProps['icon']> = { - default: 'check', - success: 'check', - error: 'exclamation', - warning: 'circle-exclamation', - info: 'info', -} + type ToastType, + TOAST_TYPE_TO_ICON, + getToastTypeStyles, + getToastWebAnimationStyles, + TOAST_WEB_KEYFRAMES, +} from './Toast.style' + +const DURATION = 3500 interface ActiveToast { text: string - icon: FontAwesomeProps['icon'] type: ToastType } type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void @@ -40,50 +31,45 @@ let toastTimeout: NodeJS.Timeout | undefined type ToastContainerProps = {} export const ToastContainer: React.FC<ToastContainerProps> = ({}) => { const [activeToast, setActiveToast] = useState<ActiveToast | undefined>() + const [isExiting, setIsExiting] = useState(false) + useEffect(() => { globalSetActiveToast = (t: ActiveToast | undefined) => { - setActiveToast(t) + if (!t && activeToast) { + setIsExiting(true) + setTimeout(() => { + setActiveToast(t) + setIsExiting(false) + }, 200) + } else { + setActiveToast(t) + setIsExiting(false) + } } - }) + }, [activeToast]) - const t = useTheme() + useEffect(() => { + const styleId = 'toast-animations' + if (!document.getElementById(styleId)) { + const style = document.createElement('style') + style.id = styleId + style.textContent = TOAST_WEB_KEYFRAMES + document.head.appendChild(style) + } + }, []) - const TOAST_TYPE_TO_STYLES = { - default: { - backgroundColor: t.atoms.text_contrast_low.color, - borderColor: t.atoms.border_contrast_medium.borderColor, - iconColor: '#fff', - textColor: '#fff', - }, - success: { - backgroundColor: '#059669', - borderColor: '#047857', - iconColor: '#fff', - textColor: '#fff', - }, - error: { - backgroundColor: t.palette.negative_100, - borderColor: t.palette.negative_400, - iconColor: t.palette.negative_600, - textColor: t.palette.negative_600, - }, - warning: { - backgroundColor: t.palette.negative_500, - borderColor: t.palette.negative_600, - iconColor: '#fff', - textColor: '#fff', - }, - info: { - backgroundColor: t.atoms.text_contrast_low.color, - borderColor: t.atoms.border_contrast_medium.borderColor, - iconColor: '#fff', - textColor: '#fff', - }, - } + const t = useTheme() + const toastTypeStyles = getToastTypeStyles(t) const toastStyles = activeToast - ? TOAST_TYPE_TO_STYLES[activeToast.type] - : TOAST_TYPE_TO_STYLES.default + ? toastTypeStyles[activeToast.type] + : toastTypeStyles.default + + const IconComponent = activeToast + ? TOAST_TYPE_TO_ICON[activeToast.type] + : TOAST_TYPE_TO_ICON.default + + const animationStyles = getToastWebAnimationStyles() return ( <> @@ -94,18 +80,24 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => { { backgroundColor: toastStyles.backgroundColor, borderColor: toastStyles.borderColor, + ...(isExiting + ? animationStyles.exiting + : animationStyles.entering), }, ]}> - <FontAwesomeIcon - icon={activeToast.icon} - size={20} - style={ - [ - styles.icon, - {color: toastStyles.iconColor}, - ] as FontAwesomeIconStyle - } - /> + <View + style={[ + styles.iconContainer, + { + backgroundColor: 'transparent', + }, + ]}> + <IconComponent + fill={toastStyles.iconColor} + size="sm" + style={styles.icon} + /> + </View> <Text style={[ styles.text, @@ -132,22 +124,12 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => { // methods // = -export function show( - text: string, - type: ToastType | FontAwesomeProps['icon'] = 'default', -) { +export function show(text: string, type: ToastType = 'default') { if (toastTimeout) { clearTimeout(toastTimeout) } - // Determine if type is a semantic type or direct icon - const isSemanticType = typeof type === 'string' && type in TOAST_TYPE_TO_ICON - const icon = isSemanticType - ? TOAST_TYPE_TO_ICON[type as ToastType] - : (type as FontAwesomeProps['icon']) - const toastType = isSemanticType ? (type as ToastType) : 'default' - - globalSetActiveToast?.({text, icon, type: toastType}) + globalSetActiveToast?.({text, type}) toastTimeout = setTimeout(() => { globalSetActiveToast?.(undefined) }, DURATION) @@ -161,10 +143,10 @@ const styles = StyleSheet.create({ bottom: 20, // @ts-ignore web only width: 'calc(100% - 40px)', - maxWidth: 350, + maxWidth: 380, padding: 20, flexDirection: 'row', - alignItems: 'center', + alignItems: 'flex-start', borderRadius: 10, borderWidth: 1, }, @@ -175,6 +157,14 @@ const styles = StyleSheet.create({ bottom: 0, right: 0, }, + iconContainer: { + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + }, icon: { flexShrink: 0, }, |