Show observers as a full-width list on mobile (#122)

This commit is contained in:
MrAlders0n authored and GitHub committed 2026-10-01 07:55:47 -04:00
1 parent e6e680192a
commit 441dcdee6c
2 files changed
+13 -13

No files matched your search

+1 -1
View File
@@ -80,7 +80,7 @@ export function ObserverPage({ wsManager }: { wsManager: WsManager }) {
</span>}
/>
<div className="relative flex min-h-0 min-w-0 flex-1">
<div className="flex min-h-0 w-full shrink-0 flex-col p-4 md:w-[260px] md:pr-0">
<div className="flex min-h-0 w-full shrink-0 flex-col md:w-[260px] md:p-4 md:pr-0">
<ObserverSidebar observers={observers} filtered={Boolean(search || status || type || broker || scope)} isPending={directory.isPending} isError={directory.isError}
onRetry={() => void directory.refetch()} range={range} selectedId={id} onSelect={select} />
</div>
+12 -12
View File
@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { getTopObservers } from "../../api/client";
import { useRegion } from "../../hooks/useRegion";
import { formatCount } from "../../lib/formatters";
import { Card } from "../stats/cards";
import { Segmented } from "../stats/Segmented";
import { RANGE_MS, type StatsRange } from "../stats/types";
import { observerName as nameOf } from "./observer-filter";
@@ -36,17 +35,18 @@ export function ObserverSidebar({ observers, filtered, isPending, isError, onRet
}, [observers, sort, counts]);
return (
<Card
title={t("tabs.Observers")}
right={<Segmented size="sm" ariaLabel={t("observerPage.sort")} value={sort} onChange={(v) => setSort(v as Sort)}
options={[{ value: "activity", label: t("observerPage.sortActivity") }, { value: "name", label: t("observerPage.sortName") }]} />}
className="flex min-h-0 w-full flex-col"
>
// a card at md+, an edge-to-edge list like the Nodes tab below it
<div className="flex min-h-0 w-full flex-col bg-bg-base md:rounded-lg md:border md:border-border md:bg-bg-surface md:p-3.5">
<div className="flex shrink-0 items-center justify-between gap-2 border-b border-border-subtle px-4 py-2 md:mb-2.5 md:border-0 md:p-0">
<div className="font-mono text-[11px] font-semibold uppercase tracking-wider text-text-normal">{t("tabs.Observers")}</div>
<Segmented size="sm" ariaLabel={t("observerPage.sort")} value={sort} onChange={(v) => setSort(v as Sort)}
options={[{ value: "activity", label: t("observerPage.sortActivity") }, { value: "name", label: t("observerPage.sortName") }]} />
</div>
{isPending ? <div className="py-6 text-center font-mono text-[11px] text-text-dim">{t("common.loading")}</div>
: isError ? <button type="button" onClick={onRetry} className="py-6 text-center font-mono text-[11px] text-danger">{t("common.loadFailed")} · {t("observerPage.retry")}</button>
: rows.length === 0 ? <div className="py-6 text-center font-mono text-[11px] text-text-dim">{t(filtered ? "observerPage.noMatches" : "observerPage.none")}</div>
: (
<div role="listbox" aria-label={t("tabs.Observers")} className="-mx-1 flex min-h-0 flex-col gap-0.5 overflow-y-auto px-1">
<div role="listbox" aria-label={t("tabs.Observers")} className="flex min-h-0 flex-col overflow-y-auto md:-mx-1 md:gap-0.5 md:px-1">
{rows.map((o) => {
const active = o.id === selectedId;
const count = counts.get(o.id);
@@ -57,14 +57,14 @@ export function ObserverSidebar({ observers, filtered, isPending, isError, onRet
role="option"
aria-selected={active}
onClick={() => onSelect(o.id)}
className={`relative shrink-0 overflow-hidden rounded border-l-2 px-2.5 py-1.5 text-left transition-colors ${
className={`relative shrink-0 overflow-hidden border-b border-l-2 border-b-border-subtle px-4 py-3 text-left md:rounded md:border-b-0 md:px-2.5 md:py-1.5 transition-colors ${
active ? "border-primary bg-primary/10" : "border-transparent hover:bg-text-normal/3"
}`}
>
{count != null && <div className="absolute inset-y-0 left-0 bg-secondary/15" style={{ width: `${(count / max) * 100}%` }} aria-hidden />}
{count != null && <div className="absolute inset-y-0 left-0 bg-secondary/8 md:bg-secondary/15" style={{ width: `${(count / max) * 100}%` }} aria-hidden />}
<div className="relative flex items-center gap-2">
<span aria-hidden className={`h-1.5 w-1.5 shrink-0 rounded-full ${deriveObserverStatus(o) === "online" ? "bg-green" : "bg-text-dim/30"}`} />
<span className={`min-w-0 flex-1 truncate font-mono text-[12px] ${active ? "text-text-bright" : "text-text-normal"}`}>{nameOf(o)}</span>
<span className={`min-w-0 flex-1 truncate font-mono text-[13px] md:text-[12px] ${active ? "text-text-bright" : "text-text-normal"}`}>{nameOf(o)}</span>
<span className="shrink-0 font-mono text-[11px] tabular-nums text-text-muted">{count != null ? formatCount(count) : "—"}</span>
</div>
</button>
@@ -72,6 +72,6 @@ export function ObserverSidebar({ observers, filtered, isPending, isError, onRet
})}
</div>
)}
</Card>
</div>
);
}