SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
1.6 KB · 29 lines tsx
Raw Blame History
1'use client';23import { useDegraded, useLive, useNow } from '@/lib/live';45/** "LIVE • updated 3 s ago" — the text ticks every second from the last real event; the data does not move. */6export function LiveIndicator({ className = '', compact = false }: { className?: string; compact?: boolean }) {7  const { lastEventAt, connection } = useLive((s) => ({ lastEventAt: s.lastEventAt, connection: s.connection }));8  const { degraded } = useDegraded();9  const now = useNow(1000);10  const ago = lastEventAt ? Math.max(0, Math.round((now - lastEventAt) / 1000)) : null;11  const live = connection === 'open' && !degraded;12  const color = degraded ? 'var(--warn)' : connection === 'open' ? 'var(--ok)' : 'var(--ink-3)';13  const word = degraded ? 'DEGRADED' : connection === 'open' ? 'LIVE' : connection === 'reconnecting' ? 'RECONNECTING' : connection === 'connecting' ? 'CONNECTING' : 'OFFLINE';14  return (15    <span className={`inline-flex items-center gap-2 text-[11px] tracking-[0.1em] ${className}`} aria-live="off" suppressHydrationWarning>16      <span className="relative inline-flex size-1.5">17        <span className="size-1.5 rounded-full" style={{ background: color }} />18        {live && ago != null && ago < 2 && <span className="absolute inset-0 rounded-full opacity-60" style={{ background: color, transform: 'scale(2)', transition: 'transform 0.4s, opacity 0.4s', opacity: 0 }} />}19      </span>20      <span style={{ color }}>{word}</span>21      {!compact && ago != null && (22        <span className="num text-ink-3">23          · updated {ago < 60 ? `${ago} s` : `${Math.floor(ago / 60)} min`} ago24        </span>25      )}26    </span>27  );28}29