| import { Menu, PanelLeftClose, Pencil } from "lucide-react"; |
| import "./TopBar.css"; |
| import { getNewsTimeframeLabel } from "../constants/timeframes.js"; |
|
|
| function fmt(n) { |
| if (n == null) return "—"; |
| if (n >= 1000) return new Intl.NumberFormat("en-US").format(n); |
| return String(n); |
| } |
|
|
| function fmtTime(iso) { |
| if (!iso) return "—"; |
| const d = new Date(iso); |
| return d.toLocaleString("en-GB", { |
| year: "numeric", |
| month: "short", |
| day: "2-digit", |
| hour: "2-digit", |
| minute: "2-digit", |
| }); |
| } |
|
|
| function divLabel(d) { |
| if (d == null) return { text: "—", tone: "" }; |
| if (d > 0.7) return { text: "high", tone: "danger" }; |
| if (d > 0.45) return { text: "moderate", tone: "warn" }; |
| return { text: "low", tone: "neutral" }; |
| } |
|
|
| export default function TopBar({ |
| query, |
| meta, |
| metaLoading, |
| menuOpen, |
| onToggleMenu, |
| onEditTopic, |
| }) { |
| const div = divLabel(meta?.divergenceIndex); |
|
|
| return ( |
| <div className="topbar"> |
| <button |
| className="topbar__menu-btn" |
| onClick={onToggleMenu} |
| aria-label="Toggle visualization menu" |
| title="Toggle visualization menu" |
| > |
| {menuOpen ? <PanelLeftClose size={15} /> : <Menu size={15} />} |
| <span>views</span> |
| </button> |
| |
| <div className="topbar__topic"> |
| <div className="label">Topic</div> |
| <button |
| className="topbar__topic-button" |
| onClick={onEditTopic} |
| title="Change topic" |
| > |
| <span className="topbar__topic-block"> |
| <span className="topbar__topic-text"> |
| {query.topic || "Untitled"} |
| </span> |
| <span className="topbar__topic-meta"> |
| {getNewsTimeframeLabel(query.newsTimeframe)} |
| </span> |
| </span> |
| <Pencil size={14} strokeWidth={1.5} className="topbar__topic-icon" /> |
| </button> |
| </div> |
| |
| <div className="topbar__metrics" aria-busy={metaLoading || undefined}> |
| <Metric label="Articles" value={fmt(meta?.totalArticles)} /> |
| <Metric label="Sources" value={fmt(meta?.totalSources)} /> |
| <Metric label="Countries" value={fmt(meta?.countriesCovered)} /> |
| <Metric label="Languages" value={fmt(meta?.languagesCovered)} /> |
| <Metric |
| label="Divergence" |
| value={ |
| meta?.divergenceIndex != null |
| ? meta.divergenceIndex.toFixed(2) |
| : "—" |
| } |
| accent={div.tone} |
| suffix={div.text} |
| /> |
| <Metric |
| label="Updated" |
| value={fmtTime(meta?.lastUpdated)} |
| tight |
| /> |
| </div> |
| </div> |
| ); |
| } |
|
|
| function Metric({ label, value, accent = "", suffix, tight = false }) { |
| return ( |
| <div className={`metric ${tight ? "metric--tight" : ""}`}> |
| <div className="label metric__label">{label}</div> |
| <div className={`metric__value metric__value--${accent}`}> |
| {value} |
| {suffix && <span className="metric__suffix">{suffix}</span>} |
| </div> |
| </div> |
| ); |
| } |
|
|