Currency Transfer Form
Currency Transfer Form is a React and Tailwind CSS component from Kokonut UI, licensed MIT. Copy it and paste it into your project.
Kokonut UI
MIT
other
What it is
Currency Transfer Form is a React and Tailwind CSS component from Kokonut UI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i lucide-react motion
Licence
This component comes from Kokonut UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Currency Transfer Form
"use client";
/**
* @author: @dorianbaffier
* @description: Currency Transfer
* @version: 1.0.0
* @date: 2025-06-26
* @license: MIT
* @website: https://kokonutui.com
* @github: https://github.com/kokonut-labs/kokonutui
*/
import {
ArrowDownIcon,
ArrowUpDown,
ArrowUpIcon,
Check,
InfoIcon,
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
interface CheckmarkProps {
size?: number;
strokeWidth?: number;
color?: string;
className?: string;
}
const draw = {
hidden: { pathLength: 0, opacity: 0 },
visible: (i: number) => ({
pathLength: 1,
opacity: 1,
transition: {
pathLength: {
delay: i * 0.2,
type: "spring",
duration: 1.5,
bounce: 0.2,
ease: [0.22, 1, 0.36, 1],
},
opacity: { delay: i * 0.2, duration: 0.3 },
},
}),
};
export function Checkmark({
size = 100,
strokeWidth = 2,
color = "currentColor",
className = "",
}: CheckmarkProps) {
return (
<motion.svg
animate="visible"
className={className}
height={size}
initial="hidden"
viewBox="0 0 100 100"
width={size}
>
<title>Animated Checkmark</title>
<motion.circle
custom={0}
cx="50"
cy="50"
r="42"
stroke={color}
style={{
strokeWidth,
strokeLinecap: "round",
fill: "transparent",
filter: "drop-shadow(0 0 2px rgba(16, 185, 129, 0.2))",
}}
variants={draw as any}
/>
<motion.path
custom={1}
d="M32 50L45 63L68 35"
stroke={color}
style={{
strokeWidth: strokeWidth + 0.5,
strokeLinecap: "round",
strokeLinejoin: "round",
fill: "transparent",
filter: "drop-shadow(0 0 1px rgba(16, 185, 129, 0.3))",
}}
variants={draw as any}
/>
</motion.svg>
);
}
export default function CurrencyTransfer() {
const [isCompleted, setIsCompleted] = useState(false);
const transactionId = "TXN-DAB3UL494";
useEffect(() => {
const timer = setTimeout(() => {
setIsCompleted(true);
}, 1500);
return () => clearTimeout(timer);
}, []);
return (
<TooltipProvider>
<Card className="mx-auto flex h-[420px] w-full max-w-sm flex-col border border-zinc-200/60 bg-white p-6 shadow-[0_0_0_1px_rgba(0,0,0,0.03)] backdrop-blur-sm transition-all duration-500 hover:border-emerald-500/20 dark:border-zinc-800/60 dark:bg-zinc-900 dark:shadow-[0_0_0_1px_rgba(255,255,255,0.03)] dark:hover:border-emerald-500/20">
<CardContent className="flex flex-1 flex-col justify-center space-y-4">
<div className="flex h-[80px] items-center justify-center">
<motion.div
animate={{ opacity: 1, y: 0 }}
className="flex justify-center"
initial={{ opacity: 0, y: -10 }}
transition={{
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}}
>
<div className="relative flex h-[100px] w-[100px] items-center justify-center">
<motion.div
animate={{
opacity: [0, 1, 0.8],
}}
className="absolute inset-0 rounded-full bg-emerald-500/10 blur-2xl dark:bg-emerald-500/5"
initial={{ opacity: 0 }}
transition={{
duration: 1.5,
times: [0, 0.5, 1],
ease: [0.22, 1, 0.36, 1],
}}
/>
<AnimatePresence mode="wait">
{isCompleted ? (
<motion.div
animate={{
opacity: 1,
rotate: 0,
}}
className="flex h-[100px] w-[100px] items-center justify-center"
initial={{
opacity: 0,
rotate: -180,
}}
key="completed"
transition={{
duration: 0.6,
ease: "easeInOut",
}}
>
<div className="relative z-10 rounded-full border border-emerald-500 bg-white p-5 dark:bg-zinc-900">
<Check
className="h-10 w-10 text-emerald-500"
strokeWidth={3.5}
/>
</div>
</motion.div>
) : (
<motion.div
animate={{ opacity: 1 }}
className="flex h-[100px] w-[100px] items-center justify-center"
exit={{
opacity: 0,
rotate: 360,
}}
initial={{ opacity: 0 }}
key="progress"
transition={{
duration: 0.6,
ease: "easeInOut",
}}
>
<div className="relative z-10">
<motion.div
animate={{
rotate: 360,
scale: [1, 1.02, 1],
}}
className="absolute inset-0 rounded-full border-2 border-transparent"
style={{
borderLeftColor: "rgb(16 185 129)",
borderTopColor: "rgb(16 185 129 / 0.2)",
filter: "blur(0.5px)",
}}
transition={{
rotate: {
duration: 3,
repeat: Number.POSITIVE_INFINITY,
ease: "linear",
},
scale: {
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
},
}}
/>
<div className="relative z-10 rounded-full bg-white p-5 shadow-[0_0_15px_rgba(16,185,129,0.1)] dark:bg-zinc-900">
<ArrowUpDown className="h-10 w-10 text-emerald-500" />
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
</div>
<div className="flex h-[280px] flex-col">
<motion.div
animate={{ opacity: 1, y: 0 }}
className="mb-4 w-full space-y-2 text-center"
initial={{ opacity: 0, y: 10 }}
transition={{
delay: 0.3,
duration: 0.8,
ease: [0.22, 1, 0.36, 1],
}}
>
<AnimatePresence mode="wait">
{isCompleted ? (
<motion.h2
animate={{ opacity: 1, y: 0 }}
className="font-semibold text-lg text-zinc-900 uppercase tracking-tighter dark:text-zinc-100"
exit={{ opacity: 0, y: -20 }}
initial={{ opacity: 0, y: 20 }}
key="completed-title"
transition={{
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}}
>
Transfer Completed
</motion.h2>
) : (
<motion.h2
animate={{ opacity: 1, y: 0 }}
className="font-semibold text-lg text-zinc-900 uppercase tracking-tighter dark:text-zinc-100"
exit={{ opacity: 0, y: -20 }}
initial={{ opacity: 0, y: 20 }}
key="progress-title"
transition={{
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}}
>
Transfer in Progress
</motion.h2>
)}
</AnimatePresence>
<AnimatePresence mode="wait">
{isCompleted ? (
<motion.div
animate={{ opacity: 1, y: 0 }}
className="font-medium text-emerald-600 text-xs dark:text-emerald-400"
exit={{ opacity: 0, y: -10 }}
initial={{ opacity: 0, y: 10 }}
key="completed-id"
transition={{
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
>
Transaction ID: {transactionId}
</motion.div>
) : (
<motion.div
animate={{ opacity: 1, y: 0 }}
className="font-medium text-emerald-600 text-xs dark:text-emerald-400"
exit={{ opacity: 0, y: -10 }}
initial={{ opacity: 0, y: 10 }}
key="progress-status"
transition={{
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
>
Processing Transaction...
</motion.div>
)}
</AnimatePresence>
<div className="mt-4 flex items-center gap-4">
<motion.div
animate={{ opacity: 1 }}
className="relative flex-1"
initial={{ opacity: 0 }}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
<motion.div
animate={{
gap: isCompleted ? "0px" : "12px",
}}
className="relative flex flex-col items-start"
initial={{ gap: "12px" }}
transition={{
duration: 0.6,
ease: [0.32, 0.72, 0, 1],
}}
>
<motion.div
animate={{
y: 0,
scale: 1,
}}
className={cn(
"w-full rounded-xl border border-zinc-200 bg-zinc-50 p-2.5 backdrop-blur-md transition-all duration-300 dark:border-zinc-700/50 dark:bg-zinc-800/50",
isCompleted
? "rounded-b-none border-b-0"
: "hover:border-emerald-500/30"
)}
transition={{
duration: 0.6,
ease: [0.32, 0.72, 0, 1],
}}
>
<div className="w-full space-y-1">
<motion.span
animate={{ opacity: 1 }}
className="flex items-center gap-1.5 font-medium text-xs text-zinc-500 dark:text-zinc-400"
initial={{ opacity: 1 }}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
<ArrowUpIcon className="h-3 w-3" />
From
</motion.span>
<div className="flex flex-col gap-1.5">
<motion.div
animate={{ opacity: 1 }}
className="group flex items-center gap-2.5"
initial={{ opacity: 1 }}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
<motion.span
className="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-zinc-300 bg-white font-medium text-sm text-zinc-900 shadow-lg transition-colors duration-300 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100"
transition={{
type: "spring",
stiffness: 400,
damping: 10,
}}
whileHover={{
scale: 1.05,
}}
>
$
</motion.span>
<div className="flex flex-col items-start">
<AnimatePresence mode="wait">
<motion.span
animate={{
opacity: isCompleted ? 1 : 0.5,
}}
className={cn(
"font-medium text-zinc-900 tracking-tight dark:text-zinc-100"
)}
exit={{
opacity: isCompleted ? 1 : 0.5,
}}
initial={{
opacity: isCompleted ? 1 : 0.5,
}}
key={
isCompleted
? "completed-amount"
: "processing-amount"
}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
500.00 USD
</motion.span>
</AnimatePresence>
<motion.span
animate={{
opacity: 1,
}}
className="text-xs text-zinc-500 dark:text-zinc-400"
initial={{
opacity: 1,
}}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
Chase Bank ••••4589
</motion.span>
</div>
</motion.div>
</div>
</div>
</motion.div>
<motion.div
animate={{
y: 0,
scale: 1,
}}
className={cn(
"w-full rounded-xl border border-zinc-200 bg-zinc-50 p-2.5 backdrop-blur-md transition-all duration-300 dark:border-zinc-700/50 dark:bg-zinc-800/50",
isCompleted
? "rounded-t-none border-t-0"
: "hover:border-emerald-500/30"
)}
transition={{
duration: 0.6,
ease: [0.32, 0.72, 0, 1],
}}
>
<div className="w-full space-y-1">
<motion.span
animate={{ opacity: 1 }}
className="flex items-center gap-1.5 font-medium text-xs text-zinc-500 dark:text-zinc-400"
initial={{ opacity: 1 }}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
<ArrowDownIcon className="h-3 w-3" />
To
</motion.span>
<div className="flex flex-col gap-1.5">
<motion.div
animate={{ opacity: 1 }}
className="group flex items-center gap-2.5"
initial={{ opacity: 1 }}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
<motion.span
className="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-zinc-300 bg-white font-medium text-sm text-zinc-900 shadow-lg transition-colors duration-300 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100"
transition={{
type: "spring",
stiffness: 400,
damping: 10,
}}
whileHover={{
scale: 1.05,
}}
>
€
</motion.span>
<div className="flex flex-col items-start">
<AnimatePresence mode="wait">
<motion.span
animate={{
opacity: isCompleted ? 1 : 0.5,
}}
className={cn(
"font-medium text-zinc-900 tracking-tight dark:text-zinc-100"
)}
exit={{
opacity: isCompleted ? 1 : 0.5,
}}
initial={{
opacity: isCompleted ? 1 : 0.5,
}}
key={
isCompleted
? "completed-amount-eur"
: "processing-amount-eur"
}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
460.00 EUR
</motion.span>
</AnimatePresence>
<motion.span
animate={{
opacity: 1,
}}
className="text-xs text-zinc-500 dark:text-zinc-400"
initial={{
opacity: 1,
}}
transition={{
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}}
>
Deutsche Bank ••••7823
</motion.span>
</div>
</motion.div>
</div>
</div>
</motion.div>
</motion.div>
</motion.div>
</div>
<motion.div
animate={{ opacity: 1 }}
className="mt-2 flex items-center justify-center gap-2 text-xs text-zinc-500 dark:text-zinc-400"
initial={{ opacity: 0 }}
transition={{
delay: 0.5,
duration: 0.6,
ease: [0.22, 1, 0.36, 1],
}}
>
<AnimatePresence mode="wait">
{isCompleted ? (
<motion.span
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
initial={{ opacity: 0, y: 10 }}
key="completed-rate"
transition={{
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
>
Exchange Rate: 1 USD = 0.92 EUR
</motion.span>
) : (
<motion.span
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
initial={{ opacity: 0, y: 10 }}
key="calculating-rate"
transition={{
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
>
Calculating exchange rate...
</motion.span>
)}
</AnimatePresence>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3 w-3 text-zinc-400 transition-colors hover:text-zinc-600 dark:hover:text-zinc-300" />
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">
{isCompleted
? "Rate updated at 10:45 AM"
: "Please wait..."}
</p>
</TooltipContent>
</Tooltip>
</motion.div>
</motion.div>
</div>
</CardContent>
</Card>
</TooltipProvider>
);
}