about summary refs log tree commit diff
path: root/src/view/com/util/Toast.web.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util/Toast.web.tsx')
-rw-r--r--src/view/com/util/Toast.web.tsx148
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,
   },