diff options
Diffstat (limited to 'apps/mobile/components')
| -rw-r--r-- | apps/mobile/components/ui/Toast.tsx | 215 |
1 files changed, 34 insertions, 181 deletions
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 }; |
