| // src/context/LoadingContext.tsx | |
| import { createContext, useContext, useState,useEffect, useCallback, useRef, ReactNode } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { Loader2 } from "lucide-react"; | |
| // ============================================ | |
| // Types | |
| // ============================================ | |
| interface LoadingItem { | |
| id: string; | |
| message?: string; | |
| } | |
| interface LoadingContextValue { | |
| isLoading: boolean; | |
| loadingCount: number; | |
| loadingMessages: string[]; | |
| startLoading: (id?: string, message?: string) => string; | |
| stopLoading: (id: string) => void; | |
| stopAllLoading: () => void; | |
| withLoading: <T>(promise: Promise<T>, message?: string) => Promise<T>; | |
| } | |
| // ============================================ | |
| // Context | |
| // ============================================ | |
| const LoadingContext = createContext<LoadingContextValue | null>(null); | |
| export function LoadingProvider({ children }: { children: ReactNode }) { | |
| const [loadingItems, setLoadingItems] = useState<LoadingItem[]>([]); | |
| const loadingRef = useRef(loadingItems); | |
| // Keep ref in sync | |
| useEffect(() => { | |
| loadingRef.current = loadingItems; | |
| }, [loadingItems]); | |
| // ============================================ | |
| // Start Loading | |
| // ============================================ | |
| const startLoading = useCallback((id?: string, message?: string): string => { | |
| const loadingId = id || `${Date.now()}-${Math.random().toString(36).substr(2, 6)}`; | |
| setLoadingItems(prev => [...prev, { id: loadingId, message }]); | |
| return loadingId; | |
| }, []); | |
| // ============================================ | |
| // Stop Loading | |
| // ============================================ | |
| const stopLoading = useCallback((id: string) => { | |
| setLoadingItems(prev => prev.filter(item => item.id !== id)); | |
| }, []); | |
| // ============================================ | |
| // Stop All Loading | |
| // ============================================ | |
| const stopAllLoading = useCallback(() => { | |
| setLoadingItems([]); | |
| }, []); | |
| // ============================================ | |
| // With Loading | |
| // ============================================ | |
| const withLoading = useCallback(async <T,>( | |
| promise: Promise<T>, | |
| message?: string | |
| ): Promise<T> => { | |
| const id = startLoading(undefined, message); | |
| try { | |
| const result = await promise; | |
| return result; | |
| } finally { | |
| stopLoading(id); | |
| } | |
| }, [startLoading, stopLoading]); | |
| // ============================================ | |
| // Derived State | |
| // ============================================ | |
| const isLoading = loadingItems.length > 0; | |
| const loadingMessages = loadingItems.map(item => item.message).filter(Boolean) as string[]; | |
| // ============================================ | |
| // Value | |
| // ============================================ | |
| const value: LoadingContextValue = { | |
| isLoading, | |
| loadingCount: loadingItems.length, | |
| loadingMessages, | |
| startLoading, | |
| stopLoading, | |
| stopAllLoading, | |
| withLoading, | |
| }; | |
| return ( | |
| <LoadingContext.Provider value={value}> | |
| {children} | |
| <AnimatePresence> | |
| {isLoading && ( | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| className="fixed inset-0 z-[200] flex items-center justify-center bg-black/20 backdrop-blur-sm pointer-events-none" | |
| > | |
| <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-xl flex flex-col items-center gap-3 pointer-events-auto"> | |
| <motion.div | |
| animate={{ rotate: 360 }} | |
| transition={{ duration: 1, repeat: Infinity, ease: "linear" }} | |
| > | |
| <Loader2 className="w-8 h-8 text-primary" /> | |
| </motion.div> | |
| {loadingMessages.length > 0 && ( | |
| <p className="text-sm text-gray-600 dark:text-gray-300"> | |
| {loadingMessages[loadingMessages.length - 1]} | |
| </p> | |
| )} | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </LoadingContext.Provider> | |
| ); | |
| } | |
| export const useLoading = () => { | |
| const ctx = useContext(LoadingContext); | |
| if (!ctx) throw new Error("useLoading must be used within LoadingProvider"); | |
| return ctx; | |
| }; |
Xet Storage Details
- Size:
- 4.31 kB
- Xet hash:
- 37012e1df1aea2eddc1a73b7802960330d1d0bd0cb0405847958642f85dbf679
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.