Cart progress animation
Cart progress animation
import React, { useEffect, useRef, useState } from "react"; import { Animated, Easing } from "react-native"; import LinearGradient from "react-native-linear-gradient"; import { View, Text } from "@evlop/native-components"; import { Money } from "@evlop/shopify"; const MILESTONES = [ { key: "m1", label: "5% off", amount: 30 }, { key: "m2", label: "10% off", amount: 100 }, { key: "m3", label: "15% off", amount: 200 } ]; const BAR_HEIGHT = 20; const TICK_OUTER = 12; // outer circle size const TICK_INNER = 8; // inner dot size const AppBlock: NativeAppBlock = function ({ cart }) { const total = +(cart?.totalPrice?.amount ?? 0); const currency = cart?.totalPrice?.currencyCode ?? "USD"; const max = MILESTONES[MILESTONES.length - 1].amount; const progress = Math.min(total / max, 1); const [containerWidth, setContainerWidth] = useState(0); const animatedWidth = useRef(new Animated.Value(0)).current; useEffect(() => { if (containerWidth <= 0) return; const toValue = progress * containerWidth; Animated.timing(animatedWidth, { toValue, duration: 650, easing: Easing.out(Easing.cubic), useNativeDriver: false }).start(); }, [progress, containerWidth, animatedWidth]); const nextMilestone = MILESTONES.find(m => total < m.amount); const AnimatedGradient: any = Animated.createAnimatedComponent(LinearGradient as any); return ( <View padding={18} backgroundColor="gray-50"> <View borderRadius={16} padding={18} backgroundColor="white" borderWidth={1} borderColor="gray-200" shadowColor="gray-900" > <Text fontSize={16} fontWeight="700" marginBottom={14} color="gray-900"> {nextMilestone ? ( <> Add <Money fontWeight="700" amount={String(Math.max(0, (nextMilestone.amount - total).toFixed(2)))} currencyCode={currency} /> more to get {nextMilestone.label} </> ) : ( "You reached the top reward 🎉" )} </Text> <View onLayout={e => setContainerWidth(e.nativeEvent.layout.width)} height={BAR_HEIGHT} borderRadius={BAR_HEIGHT / 2} backgroundColor="gray-100" borderWidth={1} borderColor="gray-200" position="relative" overflow="hidden" > {/* Gradient progress fill */} <AnimatedGradient colors={["#60A5FA", "#7C3AED"]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={{ width: animatedWidth, height: BAR_HEIGHT, borderRadius: BAR_HEIGHT / 2, elevation: 3 }} /> {/* subtle overlay shadow for depth */} <View position="absolute" left={0} right={0} top={0} height={BAR_HEIGHT} borderRadius={BAR_HEIGHT / 2} style={{ shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.06, shadowRadius: 4 }} /> {/* milestone ticks */} {containerWidth > 0 && MILESTONES.map(m => { const rawLeft = (m.amount / max) * containerWidth; const left = Math.min(Math.max(rawLeft - TICK_OUTER / 2, 0), containerWidth - TICK_OUTER); const reached = total >= m.amount; return ( <View key={m.key} position="absolute" top={-(TICK_OUTER - BAR_HEIGHT) / 2} width={TICK_OUTER} height={TICK_OUTER} alignItems="center" justifyContent="center" style={{ left }} > <View width={TICK_OUTER} height={TICK_OUTER} borderRadius={TICK_OUTER / 2} borderWidth={2} alignItems="center" justifyContent="center" borderColor={reached ? "primary-500" : "gray-200"} backgroundColor={reached ? "rgba(124,58,237,0.08)" : "transparent"} style={{ shadowColor: reached ? "#7C3AED" : "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: reached ? 0.12 : 0.04, shadowRadius: 6 }} > <View width={TICK_INNER} height={TICK_INNER} borderRadius={TICK_INNER / 2} backgroundColor={reached ? "primary-500" : "white"} style={{ borderWidth: reached ? 0 : 1, borderColor: "#E5E7EB" }} /> </View> </View> ); })} </View> <View marginTop={12} position="relative" minHeight={44}> {containerWidth > 0 && MILESTONES.map(m => { const rawLeft = (m.amount / max) * containerWidth; const left = Math.min(Math.max(rawLeft - 48, 0), containerWidth - 96); const reached = total >= m.amount; return ( <View key={m.key + "-label"} position="absolute" width={96} alignItems="center" style={{ left }}> <Text fontSize={12} fontWeight="800" textAlign="center" color={reached ? "primary-600" : "gray-600"}> {m.label} </Text> <Text fontSize={12} color="gray-500" textAlign="center" marginTop={4}> <Money fontWeight="Bold" fontSize={12} color="gray-500" amount={String(m.amount)} currencyCode={currency} /> </Text> </View> ); })} </View> </View> </View> ); }; export default AppBlock;
{}
Loading...