feat: first commit

This commit is contained in:
sittichok Ouamsiri committed 2026-06-15 21:25:57 +07:00
commit 3395ab6dd3
88 files changed
+10034

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
import { motion } from "framer-motion";
import { useState } from "react";
import type { DayCount } from "../lib/types";
import { weekdayLabel } from "../lib/format";
interface Props {
data: DayCount[];
height?: number;
}
export function BarChart({ data, height = 92 }: Props) {
const [hover, setHover] = useState<number | null>(null);
const max = Math.max(1, ...data.map((d) => d.count));
return (
<div className="flex items-end gap-1.5" style={{ height }}>
{data.map((d, i) => {
const ratio = d.count / max;
const isPeak = d.count === max && max > 0;
const active = hover === i;
return (
<div
key={d.date}
className="group relative flex h-full flex-1 flex-col items-center justify-end gap-1.5"
onMouseEnter={() => setHover(i)}
onMouseLeave={() => setHover(null)}
>
{/* tooltip */}
<motion.div
initial={false}
animate={{
opacity: active ? 1 : 0,
y: active ? 0 : 6,
scale: active ? 1 : 0.8,
}}
transition={{ type: "spring", stiffness: 520, damping: 22 }}
className="pointer-events-none absolute -top-1 z-10 -translate-y-full whitespace-nowrap rounded-lg bg-[var(--ink)] px-2 py-1 text-[11px] font-semibold text-[var(--bg)]"
>
{d.count} clicks
</motion.div>
<div className="flex w-full flex-1 items-end">
<motion.div
initial={{ height: 0 }}
animate={{ height: `${Math.max(ratio * 100, 4)}%` }}
transition={{
type: "spring",
stiffness: 260,
damping: 18,
delay: i * 0.05,
}}
className={`w-full rounded-md ${
isPeak || active
? "bg-accent"
: "bg-[var(--ring)] group-hover:bg-[var(--muted)]"
}`}
style={{ minHeight: 4 }}
/>
</div>
<span className="text-[10px] font-medium text-muted">
{weekdayLabel(d.date)}
</span>
</div>
);
})}
</div>
);
}