Files
nexcurrency/components/converter/converter-card-converted-value.tsx

143 lines
4.7 KiB
TypeScript

"use client";
import { AnimatePresence, motion } from "framer-motion";
import { Check, Copy, Link2 } from "lucide-react";
import { CurrencyIcon } from "@/components/converter/currency-icon";
import { Button } from "@/components/ui/button";
import type { RateAsset } from "@/lib/rates";
interface ConverterCardConvertedValueProps {
shouldReduceMotion: boolean;
fromAsset?: RateAsset;
toAsset?: RateAsset;
pairTransitionKey: string;
convertedDisplay: string;
forAmountDisplay: string;
isShareLinkCopied: boolean;
isCopied: boolean;
canCopyConvertedValue: boolean;
onCopyShareLink: () => void;
onCopyConvertedValue: () => void;
}
export function ConverterCardConvertedValue({
shouldReduceMotion,
fromAsset,
toAsset,
pairTransitionKey,
convertedDisplay,
forAmountDisplay,
isShareLinkCopied,
isCopied,
canCopyConvertedValue,
onCopyShareLink,
onCopyConvertedValue,
}: ConverterCardConvertedValueProps) {
return (
<div className="rounded-xl border border-border/70 bg-background/50 p-4">
<div className="flex items-center justify-between gap-2">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">
Converted value
</p>
<div className="flex items-center gap-1">
<Button
type="button"
variant="ghost"
size="icon"
onClick={onCopyShareLink}
className="h-7 w-7 text-muted-foreground hover:text-foreground"
aria-label={isShareLinkCopied ? "Share link copied" : "Copy share link"}
>
{isShareLinkCopied ? (
<Check className="h-3.5 w-3.5 text-cyan-200" />
) : (
<Link2 className="h-3.5 w-3.5" />
)}
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={onCopyConvertedValue}
disabled={!canCopyConvertedValue}
className="h-7 w-7 text-muted-foreground hover:text-foreground"
aria-label={
isCopied
? "Converted value copied"
: "Copy converted value to clipboard"
}
>
{isCopied ? (
<Check className="h-3.5 w-3.5 text-cyan-200" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</Button>
</div>
</div>
{fromAsset && toAsset ? (
<AnimatePresence initial={false} mode="wait">
<motion.div
key={pairTransitionKey}
className="mt-2 flex items-center gap-2 text-xs text-muted-foreground"
initial={
shouldReduceMotion ? false : { opacity: 0, y: 4, filter: "blur(2px)" }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, y: 0, filter: "blur(0px)" }
}
exit={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: -4, filter: "blur(2px)" }
}
transition={{ duration: shouldReduceMotion ? 0.01 : 0.16 }}
>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border/70 bg-background/60 px-2 py-1">
<CurrencyIcon
code={fromAsset.code}
type={fromAsset.type}
size="sm"
/>
{fromAsset.code}
</span>
<span>to</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border/70 bg-background/60 px-2 py-1">
<CurrencyIcon code={toAsset.code} type={toAsset.type} size="sm" />
{toAsset.code}
</span>
</motion.div>
</AnimatePresence>
) : null}
<p className="mt-2 break-all text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
<AnimatePresence initial={false} mode="wait">
<motion.span
key={convertedDisplay}
className="inline-block"
initial={
shouldReduceMotion ? false : { opacity: 0, y: 6, filter: "blur(2px)" }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, y: 0, filter: "blur(0px)" }
}
exit={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: -6, filter: "blur(2px)" }
}
transition={{ duration: shouldReduceMotion ? 0.01 : 0.18 }}
>
{convertedDisplay}
</motion.span>
</AnimatePresence>
</p>
{fromAsset ? (
<p className="mt-2 text-sm text-muted-foreground">
for {forAmountDisplay} {fromAsset.code}
</p>
) : null}
</div>
);
}