From 016433d48292d6bdd64565a2a03e716abca9f408 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Thu, 1 Jan 2026 14:35:04 +0000
Subject: feat(mobile): use react native sonner

---
 apps/mobile/components/ui/Toast.tsx | 215 ++++++------------------------------
 1 file changed, 34 insertions(+), 181 deletions(-)

(limited to 'apps/mobile/components/ui/Toast.tsx')

diff --git a/apps/mobile/components/ui/Toast.tsx b/apps/mobile/components/ui/Toast.tsx
index 96323263..722c93ab 100644
--- a/apps/mobile/components/ui/Toast.tsx
+++ b/apps/mobile/components/ui/Toast.tsx
@@ -1,8 +1,4 @@
-import { createContext, useContext, useEffect, useRef, useState } from "react";
-import { Animated, Platform, View } from "react-native";
-import { FullWindowOverlay } from "react-native-screens";
-import { Text } from "@/components/ui/Text";
-import { cn } from "@/lib/utils";
+import { toast as sonnerToast } from "sonner-native";
 
 const toastVariants = {
   default: "bg-foreground",
@@ -11,184 +7,41 @@ const toastVariants = {
   info: "bg-blue-500",
 };
 
-interface ToastProps {
-  id: number;
-  message: string;
-  onHide: (id: number) => void;
-  variant?: keyof typeof toastVariants;
-  duration?: number;
-  showProgress?: boolean;
-}
-function Toast({
-  id,
-  message,
-  onHide,
-  variant = "default",
-  duration = 3000,
-  showProgress = true,
-}: ToastProps) {
-  const opacity = useRef(new Animated.Value(0)).current;
-  const progress = useRef(new Animated.Value(0)).current;
-
-  useEffect(() => {
-    Animated.sequence([
-      Animated.timing(opacity, {
-        toValue: 1,
-        duration: 500,
-        useNativeDriver: true,
-      }),
-      Animated.timing(progress, {
-        toValue: 1,
-        duration: duration - 1000,
-        useNativeDriver: false,
-      }),
-      Animated.timing(opacity, {
-        toValue: 0,
-        duration: 500,
-        useNativeDriver: true,
-      }),
-    ]).start(() => onHide(id));
-  }, [duration]);
-
-  return (
-    <Animated.View
-      className={`
-        ${toastVariants[variant]}
-        m-2 mb-1 transform rounded-lg p-4 transition-all
-      `}
-      style={{
-        opacity,
-        transform: [
-          {
-            translateY: opacity.interpolate({
-              inputRange: [0, 1],
-              outputRange: [-20, 0],
-            }),
-          },
-        ],
-      }}
-    >
-      <Text className="text-left font-semibold text-background">{message}</Text>
-      {showProgress && (
-        <View className="mt-2 rounded">
-          <Animated.View
-            className="h-2 rounded bg-white opacity-30 dark:bg-black"
-            style={{
-              width: progress.interpolate({
-                inputRange: [0, 1],
-                outputRange: ["0%", "100%"],
-              }),
-            }}
-          />
-        </View>
-      )}
-    </Animated.View>
-  );
-}
-
 type ToastVariant = keyof typeof toastVariants;
 
-interface ToastMessage {
-  id: number;
-  text: string;
-  variant: ToastVariant;
-  duration?: number;
-  position?: string;
-  showProgress?: boolean;
-}
-interface ToastContextProps {
-  toast: (t: {
-    message: string;
-    variant?: keyof typeof toastVariants;
-    duration?: number;
-    position?: "top" | "bottom";
-    showProgress?: boolean;
-  }) => void;
-  removeToast: (id: number) => void;
-}
-const ToastContext = createContext<ToastContextProps | undefined>(undefined);
-
-// TODO: refactor to pass position to Toast instead of ToastProvider
-function ToastProvider({
-  children,
-  position = "top",
-}: {
-  children: React.ReactNode;
-  position?: "top" | "bottom";
-}) {
-  const [messages, setMessages] = useState<ToastMessage[]>([]);
-
-  const toast: ToastContextProps["toast"] = ({
-    message,
-    variant = "default",
-    duration = 3000,
-    position = "top",
-    showProgress = true,
-  }: {
-    message: string;
-    variant?: ToastVariant;
-    duration?: number;
-    position?: "top" | "bottom";
-    showProgress?: boolean;
-  }) => {
-    setMessages((prev) => [
-      ...prev,
-      {
-        id: Date.now(),
-        text: message,
-        variant,
-        duration,
-        position,
-        showProgress,
-      },
-    ]);
-  };
-
-  const removeToast = (id: number) => {
-    setMessages((prev) => prev.filter((message) => message.id !== id));
-  };
-
-  const content = (
-    <View
-      className={cn("absolute left-0 right-0", {
-        "top-[45px]": position === "top",
-        "bottom-0": position === "bottom",
-      })}
-    >
-      {messages.map((message) => (
-        <Toast
-          key={message.id}
-          id={message.id}
-          message={message.text}
-          variant={message.variant}
-          duration={message.duration}
-          showProgress={message.showProgress}
-          onHide={removeToast}
-        />
-      ))}
-    </View>
-  );
-
-  return (
-    <ToastContext.Provider value={{ toast, removeToast }}>
-      {children}
-      {/* Use FullWindowOverlay on iOS to ensure toasts appear above all content */}
-      {/* Platform specific implementation due to FullWindowOverlay being iOS-only */}
-      {Platform.OS === "ios" ? (
-        <FullWindowOverlay>{content}</FullWindowOverlay>
-      ) : (
-        content
-      )}
-    </ToastContext.Provider>
-  );
-}
-
+// Compatibility wrapper for sonner-native
 function useToast() {
-  const context = useContext(ToastContext);
-  if (!context) {
-    throw new Error("useToast must be used within ToastProvider");
-  }
-  return context;
+  return {
+    toast: ({
+      message,
+      variant = "default",
+      duration = 3000,
+    }: {
+      message: string;
+      variant?: ToastVariant;
+      duration?: number;
+      position?: "top" | "bottom";
+      showProgress?: boolean;
+    }) => {
+      // Map variants to sonner-native methods
+      switch (variant) {
+        case "success":
+          sonnerToast.success(message, { duration });
+          break;
+        case "destructive":
+          sonnerToast.error(message, { duration });
+          break;
+        case "info":
+          sonnerToast.info(message, { duration });
+          break;
+        default:
+          sonnerToast(message, { duration });
+      }
+    },
+    removeToast: () => {
+      // sonner-native handles dismissal automatically
+    },
+  };
 }
 
-export { ToastProvider, ToastVariant, Toast, toastVariants, useToast };
+export { ToastVariant, toastVariants, useToast };
-- 
cgit v1.2.3-70-g09d2

