refactor(converter): split converter card into modular section components
This commit is contained in:
107
components/converter/converter-card-multi-conversion.tsx
Normal file
107
components/converter/converter-card-multi-conversion.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user