Spaces:
Running
Running
File size: 2,674 Bytes
2eb87e3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | import type { RecentActivityItem } from "@/shared/types";
import { formatRelativeTime } from "../shared/lib/format";
const ROW_H = 28; // px,无缝滚动依赖统一行高
const VISIBLE = 3;
function badge(r: RecentActivityItem) {
if (r.type === "project_submitted") {
return (
<span className="shrink-0 rounded bg-accent-50 px-1.5 py-px font-mono text-[10px] font-semibold uppercase tracking-wide text-accent-700">
New project
</span>
);
}
if (r.type === "disclosed") {
return (
<span className="shrink-0 rounded bg-ai-bg px-1.5 py-px font-mono text-[10px] font-semibold uppercase tracking-wide text-ai-ink">
Disclosed · {r.meta}
</span>
);
}
// scan_completed with findings
return (
<span className="shrink-0 rounded bg-sev-high-bg px-1.5 py-px font-mono text-[10px] font-semibold text-sev-high-ink">
{(r.meta ?? "").replace(" findings", "")} findings
</span>
);
}
/** 欢迎页滚动信息流(无边框、左右透明渐变):new project / new finding 摘要,无缝纵滚,hover 暂停 */
export function EventTicker({ events }: { events: RecentActivityItem[] | undefined }) {
const items = (events ?? [])
.filter(
(r) =>
r.type === "project_submitted" ||
r.type === "disclosed" ||
(r.type === "scan_completed" && r.meta && !r.meta.startsWith("+0")),
)
.slice(0, 8);
if (items.length === 0) return null;
const row = (r: RecentActivityItem, key: string) => (
<li key={key} className="flex items-center gap-2.5 text-[13px]" style={{ height: ROW_H }}>
{badge(r)}
<span className="min-w-0 truncate text-ink">{r.full_name ?? r.text}</span>
<span className="ml-auto shrink-0 font-mono text-[11px] text-ink-tertiary">
{formatRelativeTime(r.ts)}
</span>
</li>
);
return (
<div data-ticker aria-label="Live activity feed">
<div
className="overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]"
style={{ height: ROW_H * VISIBLE }}
>
<ul
className="ticker-track px-8"
style={{ animation: `ticker-y ${Math.max(8, items.length * 2.4)}s linear infinite` }}
>
{items.map((r, i) => row(r, `a-${i}`))}
{items.map((r, i) => row(r, `b-${i}`))}
</ul>
</div>
<style>{`
@keyframes ticker-y { to { transform: translateY(-50%); } }
[data-ticker]:hover .ticker-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .ticker-track { animation: none !important; } }
`}</style>
</div>
);
}
|