Code
import * as React from "react";
import { PartyPopper, Truck } from "lucide-react";
import { cn } from "@/lib/utils";
export interface FreeShippingBarProps {
subtotal: number;
threshold: number;
currency?: string;
locale?: string;
className?: string;
}
export function FreeShippingBar({ subtotal, threshold, currency = "EUR", locale = "en-IE", className }: FreeShippingBarProps) {
const pct = Math.min(100, (subtotal / threshold) * 100);
const left = Math.max(0, threshold - subtotal);
const fmt = new Intl.NumberFormat(locale, { style: "currency", currency });
const done = left === 0;
return (
<div className={cn("w-full rounded-xl border bg-card p-4", className)}>
<p className="flex items-center gap-2 text-sm">
{done ? <PartyPopper className="size-4 text-emerald-500" /> : <Truck className="size-4 text-primary" />}
{done ? (
<span className="font-medium">You unlocked free shipping!</span>
) : (
<span>
Add <span className="font-semibold">{fmt.format(left)}</span> more for <span className="font-semibold">free shipping</span>
</span>
)}
</p>
<div
className="relative mt-3 h-2 overflow-hidden rounded-full bg-muted"
role="progressbar"
aria-valuenow={Math.round(pct)}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className={cn(
"h-full rounded-full transition-[width] duration-700 ease-out",
done ? "bg-emerald-500" : "bg-gradient-to-r from-primary/70 to-primary",
)}
style={{ width: `${pct}%` }}
/>
</div>
<div className="mt-1.5 flex justify-between text-xs text-muted-foreground tabular-nums">
<span>{fmt.format(0)}</span>
<span>{fmt.format(threshold)}</span>
</div>
</div>
);
}