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...