Files
nexcurrency/components/converter/converter-card-multi-conversion.tsx

108 lines
3.6 KiB
TypeScript

"use client";
import { AnimatePresence, motion } from "framer-motion";
import { Loader2 } from "lucide-react";
import { CurrencyIcon } from "@/components/converter/currency-icon";
import { formatAmount } from "@/lib/format";
import type { RateAsset } from "@/lib/rates";
type AmountValidationResult =
| { ok: true; value: number }
| { ok: false; error: string };
interface MultiConversionItem {
asset: RateAsset;
value: number;
}
interface ConverterCardMultiConversionProps {
shouldReduceMotion: boolean;
debouncedValidation: AmountValidationResult;
fromAsset?: RateAsset;
multiConversions: MultiConversionItem[];
isRatesLoading: boolean;
}
export function ConverterCardMultiConversion({
shouldReduceMotion,
debouncedValidation,
fromAsset,
multiConversions,
isRatesLoading,
}: ConverterCardMultiConversionProps) {
return (
<div className="rounded-xl border border-border/70 bg-background/40 p-4">
<div className="flex items-center justify-between gap-2">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">
Multi conversion
</p>
{fromAsset && debouncedValidation.ok ? (
<span className="text-xs text-muted-foreground">
for {formatAmount(debouncedValidation.value, fromAsset)} {fromAsset.code}
</span>
) : null}
</div>
{!debouncedValidation.ok ? (
<p className="mt-3 text-sm text-red-300">{debouncedValidation.error}</p>
) : null}
{debouncedValidation.ok && multiConversions.length === 0 ? (
<p className="mt-3 text-sm text-muted-foreground">
No additional assets available for multi conversion.
</p>
) : null}
{debouncedValidation.ok && multiConversions.length > 0 ? (
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
{multiConversions.map(({ asset, value }) => (
<div
key={asset.code}
className="rounded-lg border border-border/60 bg-background/60 p-3"
>
<div className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<CurrencyIcon code={asset.code} type={asset.type} size="sm" />
{asset.code}
</div>
<p className="mt-1 text-base font-medium text-foreground">
<AnimatePresence initial={false} mode="wait">
<motion.span
key={`${asset.code}-${formatAmount(value, asset)}`}
className="inline-block"
initial={
shouldReduceMotion
? false
: { opacity: 0, y: 4, filter: "blur(1px)" }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, y: 0, filter: "blur(0px)" }
}
exit={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: -4, filter: "blur(1px)" }
}
transition={{ duration: shouldReduceMotion ? 0.01 : 0.15 }}
>
{formatAmount(value, asset)} {asset.code}
</motion.span>
</AnimatePresence>
</p>
</div>
))}
</div>
) : null}
{isRatesLoading ? (
<div className="mt-3 inline-flex items-center gap-2 text-xs text-muted-foreground">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
Updating multi conversion...
</div>
) : null}
</div>
);
}