Files
auto-trade/features/trade/components/orderbook/AnimatedQuantity.tsx

103 lines
2.9 KiB
TypeScript
Raw Normal View History

2026-02-10 11:16:39 +09:00
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion } from "framer-motion";
interface AnimatedQuantityProps {
value: number;
format?: (val: number) => string;
className?: string;
/** 값 변동 시 배경 깜빡임 */
useColor?: boolean;
2026-02-11 15:27:03 +09:00
/** 정렬 방향 (ask: 우측 정렬/왼쪽으로 확장, bid: 좌측 정렬/오른쪽으로 확장) */
side?: "ask" | "bid";
2026-02-10 11:16:39 +09:00
}
/**
* ±diff를 .
*/
export function AnimatedQuantity({
value,
format = (v) => v.toLocaleString(),
className,
useColor = false,
2026-02-11 15:27:03 +09:00
side = "bid",
2026-02-10 11:16:39 +09:00
}: AnimatedQuantityProps) {
const prevRef = useRef(value);
const [diff, setDiff] = useState<number | null>(null);
const [flash, setFlash] = useState<"up" | "down" | null>(null);
useEffect(() => {
if (prevRef.current === value) return;
const delta = value - prevRef.current;
prevRef.current = value;
if (delta === 0) return;
setDiff(delta);
setFlash(delta > 0 ? "up" : "down");
const timer = setTimeout(() => {
setDiff(null);
setFlash(null);
}, 1200);
return () => clearTimeout(timer);
}, [value]);
return (
<span
className={cn(
"relative inline-flex items-center gap-1 tabular-nums",
className,
)}
>
{/* 배경 깜빡임 */}
<AnimatePresence>
{useColor && flash && (
<motion.span
initial={{ opacity: 0.5 }}
animate={{ opacity: 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
className={cn(
"absolute inset-0 z-0 rounded-sm",
2026-02-11 15:27:03 +09:00
flash === "up" ? "bg-red-200/50" : "bg-blue-200/50",
2026-02-10 11:16:39 +09:00
)}
/>
)}
</AnimatePresence>
2026-02-11 15:27:03 +09:00
{/* 매도(Ask)일 경우 Diff가 먼저 와야 텍스트가 우측 정렬된 상태에서 흔들리지 않음 */}
{side === "ask" && <DiffChange diff={diff} />}
2026-02-10 11:16:39 +09:00
{/* 수량 값 */}
<span className="relative z-10">{format(value)}</span>
2026-02-11 15:27:03 +09:00
{/* 매수(Bid)일 경우 Diff가 뒤에 와야 텍스트가 좌측 정렬된 상태에서 흔들리지 않음 */}
{side !== "ask" && <DiffChange diff={diff} />}
2026-02-10 11:16:39 +09:00
</span>
);
}
2026-02-11 15:27:03 +09:00
function DiffChange({ diff }: { diff: number | null }) {
return (
<AnimatePresence>
{diff != null && diff !== 0 && (
<motion.span
initial={{ opacity: 1, scale: 1 }}
animate={{ opacity: 0, scale: 0.85 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.2, ease: "easeOut" }}
className={cn(
"relative z-20 whitespace-nowrap text-[9px] font-bold leading-none tabular-nums",
diff > 0 ? "text-red-500" : "text-blue-600 dark:text-blue-400",
)}
>
{diff > 0 ? `+${diff.toLocaleString()}` : diff.toLocaleString()}
</motion.span>
)}
</AnimatePresence>
);
}