spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
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