spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import { fmtValue, unitLabel } from '@/lib/format';23export interface CompareBarDatum {4 label: string;5 value: number | null; // null = no figure (rendered as "—", never as zero)6 note?: string | null; // e.g. "2024 · observed" or "registry level: Lung cancer"7 href?: string;8 muted?: boolean;9}1011/**12 * Compact horizontal bar list for the compare page (one small chart per metric). Bars are plain13 * divs so the block stays responsive on narrow screens; the number always accompanies the bar and14 * missing values are shown as a dash, never as an empty bar of length zero.15 */16export function CompareBars({ title, unit, data, caption }: { title: string; unit?: string | null; data: CompareBarDatum[]; caption?: React.ReactNode }) {17 const values = data.map((d) => d.value).filter((v): v is number => v != null && Number.isFinite(v));18 const max = Math.max(...values, Number.EPSILON);19 return (20 <figure className="min-w-0 border-t border-rule-strong pt-2">21 <figcaption className="mb-1.5 flex items-baseline justify-between gap-2 text-[12.5px]">22 <span className="font-medium text-ink">{title}</span>23 {unit ? <span className="text-[11px] text-ink-3">{unitLabel(unit)}</span> : null}24 </figcaption>25 <ol className="space-y-1.5" aria-label={`${title} — bar comparison`}>26 {data.map((d, i) => {27 const has = d.value != null && Number.isFinite(d.value);28 const pct = has ? Math.max(1, (Math.abs(d.value!) / max) * 100) : 0;29 return (30 <li key={`${d.label}-${i}`} className="text-[12.5px]">31 <div className="flex items-baseline justify-between gap-2">32 <span className="min-w-0 truncate text-ink-2">33 {d.href ? (34 <a className="ci-link" href={d.href}>35 {d.label}36 </a>37 ) : (38 d.label39 )}40 </span>41 <span className="ci-num shrink-0 text-ink">{has ? fmtValue(d.value, unit) : '—'}</span>42 </div>43 <div className="mt-0.5 h-[7px] w-full bg-paper-3" role="presentation">44 {has ? <div className="h-full" style={{ width: `${pct}%`, background: d.muted ? 'var(--color-ink-4)' : 'var(--color-accent)' }} /> : null}45 </div>46 {d.note ? <div className="mt-0.5 text-[11px] text-ink-3">{d.note}</div> : null}47 </li>48 );49 })}50 </ol>51 {caption ? <p className="mt-1.5 text-[11.5px] text-ink-3">{caption}</p> : null}52 </figure>53 );54}55