Import source: web/src/components/TargetCard

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:20:10 +03:30
parent f8ad8d916c
commit 80b4f420ac
+82
View File
@@ -0,0 +1,82 @@
import { memo } from "react";
import { motion } from "motion/react";
import type { LiveTarget } from "../lib/types";
import { StatusRing } from "./StatusRing";
import { fmtInterval } from "../lib/format";
import { springs, STATE_COLOR, useUI } from "../lib/ui";
interface TargetCardProps {
target: LiveTarget;
index: number;
onSelect: (id: string) => void;
}
export const TargetCard = memo(function TargetCard({
target,
index,
onSelect,
}: TargetCardProps) {
const { t, lang, window, reduced } = useUI();
const uptime = window === "7d" ? target.uptime_7d : target.uptime_24h;
const delay = 0.1 + Math.min(index, 12) * 0.045;
return (
<motion.button
layoutId={`card-${target.id}`}
onClick={() => onSelect(target.id)}
className="group relative flex flex-col items-center text-center px-4 pt-5 pb-4 rounded-md border border-ink-800 bg-ink-900/35 cursor-pointer overflow-hidden"
initial={reduced ? false : { opacity: 0, y: 18 }}
animate={{ opacity: 1, y: 0 }}
exit={reduced ? undefined : { opacity: 0, scale: 0.96 }}
transition={reduced ? { duration: 0 } : { ...springs.gentle, delay }}
whileHover={
reduced
? undefined
: { y: -3, borderColor: "var(--color-ink-700)" }
}
whileTap={reduced ? undefined : { scale: 0.985 }}
aria-label={`${target.name} — ${t.state[target.state]}`}
>
{/* faint state wash at the top edge */}
<span
aria-hidden
className="absolute inset-x-0 top-0 h-px opacity-70 transition-opacity group-hover:opacity-100"
style={{
background: `linear-gradient(to right, transparent, ${STATE_COLOR[target.state]}, transparent)`,
}}
/>
<motion.div layoutId={`ring-${target.id}`} transition={springs.layout}>
<StatusRing target={target} uptime={uptime} delay={delay + 0.05} />
</motion.div>
<h3 className="mt-2 text-[13px] text-paper font-medium leading-snug">
{target.name}
</h3>
<p
className="mt-1 text-[10px] text-paper-faint truncate max-w-full tabular font-mono"
dir="ltr"
>
{target.endpoint}
</p>
<div className="mt-3 flex items-center gap-2 text-[10px] font-mono">
<span
className="size-[5px] rounded-full"
style={{ background: STATE_COLOR[target.state] }}
/>
<span style={{ color: STATE_COLOR[target.state] }}>
{t.state[target.state]}
</span>
<span className="text-ink-700">·</span>
<span className="text-paper-faint uppercase tracking-wider">
{target.type}
</span>
<span className="text-ink-700">·</span>
<span className="text-paper-faint tabular">
{t.interval} {fmtInterval(lang, target.interval_sec)}
</span>
</div>
</motion.button>
);
});