대시보드 중간 커밋

This commit is contained in:
2026-02-10 11:16:39 +09:00
parent 851a2acd69
commit ca01f33d71
60 changed files with 6947 additions and 1292 deletions

View File

@@ -0,0 +1,90 @@
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;
}
/**
* 실시간 수량 표시 — 값이 변할 때 ±diff를 인라인으로 보여줍니다.
*/
export function AnimatedQuantity({
value,
format = (v) => v.toLocaleString(),
className,
useColor = false,
}: 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",
flash === "up" ? "bg-red-200/50" : "bg-blue-200/50",
)}
/>
)}
</AnimatePresence>
{/* 수량 값 */}
<span className="relative z-10">{format(value)}</span>
{/* ±diff (인라인 표시) */}
<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",
diff > 0 ? "text-red-500" : "text-blue-500",
)}
>
{diff > 0 ? `+${diff.toLocaleString()}` : diff.toLocaleString()}
</motion.span>
)}
</AnimatePresence>
</span>
);
}