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%
3.5 KB · 82 lines tsx
Raw Blame History
1'use client';23import { AnimatedNumber } from '@/components/ui/AnimatedNumber';4import { Bar, Delta } from '@/components/ui/primitives';5import { fmt, fmtDelta } from '@/lib/format';6import { useDegraded, useLive } from '@/lib/live';7import { pressureColor } from '@/lib/pressure';8import type { Component, GlobalPressure } from '@/lib/types';910/** Component rows (desktop: dense table · phone: swipeable scroll-snap chips). */11export function ComponentRows({ initial }: { initial: GlobalPressure | null }) {12  const live = useLive((s) => s.global);13  const updates = useLive((s) => s.updates);14  const { degraded } = useDegraded();15  const comps: Component[] = (live ?? initial)?.components ?? [];16  if (!comps.length) return <p className="text-xs text-ink-3">Components unavailable.</p>;17  const dim = degraded ? 'opacity-50 saturate-50' : '';1819  return (20    <div className={dim}>21      {/* phone: chips */}22      <ul className="snap-row -mx-3 px-3 md:hidden" aria-label="Components">23        {comps.map((c) => (24          <li key={c.id} className="snap-item panel w-[150px] p-3">25            <div className="flex items-baseline justify-between">26              <span className="text-[12px] text-ink">{c.label}</span>27              <span className="num text-[10px] text-ink-3">w {fmt(c.weight, 2)}</span>28            </div>29            <div className="num mt-1 text-[28px] leading-none" style={{ color: pressureColor(c.score) }}>30              <AnimatedNumber value={c.score} duration={updates ? 600 : 0} />31            </div>32            <Bar value={c.score} className="mt-2" />33            <div className="mt-1.5 flex justify-between text-[11px]">34              <Delta value={c.delta_1h} trend={c.trend} />35              <span className="num text-ink-2">{fmtDelta(c.contribution)} pts</span>36            </div>37          </li>38        ))}39      </ul>40      {/* desktop: table */}41      <table className="tbl hidden w-full table-fixed md:table">42        <thead>43          <tr>44            <th className="w-[44%]">Component · main driver</th>45            <th className="r w-[13%]">Score</th>46            <th className="w-[15%]"></th>47            <th className="r w-[14%]">Δ1h</th>48            <th className="r w-[14%]">Contrib.</th>49          </tr>50        </thead>51        <tbody>52          {comps.map((c) => (53            <tr key={c.id} className="align-top">54              <td className="whitespace-normal">55                <div className="flex items-baseline gap-2">56                  <span className="text-ink">{c.label}</span>57                  <span className="num text-[10.5px] text-ink-3">w {fmt(c.weight, 2)} · conf {Math.round(c.confidence * 100)} %</span>58                </div>59                {c.drivers[0] && (60                  <div className="mt-0.5 text-[11.5px] leading-snug text-ink-2" title={c.drivers.map((d) => `${fmtDelta(d.points)} ${d.label}`).join('\n')}>61                    <span className="num text-ink">{fmtDelta(c.drivers[0].points)}</span> {c.drivers[0].label}62                  </div>63                )}64              </td>65              <td className="num r text-[16px]" style={{ color: pressureColor(c.score) }}>66                <AnimatedNumber value={c.score} duration={updates ? 600 : 0} />67              </td>68              <td className="pt-3">69                <Bar value={c.score} />70              </td>71              <td className="r">72                <Delta value={c.delta_1h} trend={c.trend} />73              </td>74              <td className="num r">{fmtDelta(c.contribution)}</td>75            </tr>76          ))}77        </tbody>78      </table>79    </div>80  );81}82