108 lines
3.6 KiB
TypeScript
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>
|
|
);
|
|
}
|