mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
fix(dashboard): never truncate the tile value, let the unit yield instead
The small KPI tiles in the dashboard strip clipped the numeric value at narrow widths: the headline value span carried `min-w-0 truncate` while the unit span was `shrink-0`, so on a dense strip (many tiles → narrow grid column) the number — not the unit — shrank below its content width and ellipsis-clipped, rendering "130 mmHg" as a truncated "13…". Flip the priority. The value span is now `shrink-0 whitespace-nowrap` so every digit always renders in full on one line, and the unit span takes `min-w-0 truncate` so it yields when the column is too narrow. The paired-value font step-down (text-3xl → text-2xl for BP systolic/ diastolic) stays as headroom. Add guards asserting the value node never carries truncate/min-w-0 and the full reading (single and paired) renders inside it.
This commit is contained in:
1 parent
9652047e8e
commit
be90c4b9ad
2 files changed
+107
-10
No files matched your search
@@ -123,6 +123,93 @@ describe("<TrendCard> directionSentiment", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("<TrendCard> value is never truncated", () => {
|
||||
// v1.8.7 — the maintainer is emphatic: the numeric value must always
|
||||
// render in full at any tile width (dense strip → narrowest column).
|
||||
// The value span keeps its full intrinsic width (`shrink-0
|
||||
// whitespace-nowrap`) and never carries `truncate`/`min-w-0`; when
|
||||
// space is tight the UNIT yields instead. These guards pin the
|
||||
// contract so a future layout churn can't reintroduce the clip.
|
||||
function valueSpan(html: string): string {
|
||||
const marker = 'data-slot="trend-card-value"';
|
||||
const idx = html.indexOf(marker);
|
||||
expect(idx).toBeGreaterThan(-1);
|
||||
// Walk back to the opening "<span" of the value node.
|
||||
const open = html.lastIndexOf("<span", idx);
|
||||
const close = html.indexOf("</span>", idx);
|
||||
return html.slice(open, close);
|
||||
}
|
||||
|
||||
it("renders the full BP value (130) — never clipped to '13'", () => {
|
||||
const html = render(
|
||||
<TrendCard
|
||||
label="BP systolic"
|
||||
latest={130}
|
||||
unit="mmHg"
|
||||
avg7={128}
|
||||
avg30={129}
|
||||
slope30={null}
|
||||
icon={Activity}
|
||||
/>,
|
||||
);
|
||||
// Value span renders the full reading (formatter adds one decimal).
|
||||
expect(valueSpan(html)).toContain("130.0");
|
||||
});
|
||||
|
||||
it("renders the full paired BP value (131/85)", () => {
|
||||
const html = render(
|
||||
<TrendCard
|
||||
label="BP"
|
||||
latest={131}
|
||||
unit="mmHg"
|
||||
avg7={130}
|
||||
avg30={130}
|
||||
slope30={null}
|
||||
icon={Activity}
|
||||
secondary={{ latest: 85, avg7: 84, avg30: 84 }}
|
||||
/>,
|
||||
);
|
||||
expect(valueSpan(html)).toContain("131.0/85.0");
|
||||
});
|
||||
|
||||
it("does not put truncate or min-w-0 on the value node", () => {
|
||||
const html = render(
|
||||
<TrendCard
|
||||
label="Weight"
|
||||
latest={80.4}
|
||||
unit="kg"
|
||||
avg7={80}
|
||||
avg30={80}
|
||||
slope30={null}
|
||||
icon={Activity}
|
||||
/>,
|
||||
);
|
||||
const value = valueSpan(html);
|
||||
expect(value).not.toContain("truncate");
|
||||
expect(value).not.toContain("min-w-0");
|
||||
// The value holds its width and the number stays on one line.
|
||||
expect(value).toContain("shrink-0");
|
||||
expect(value).toContain("whitespace-nowrap");
|
||||
});
|
||||
|
||||
it("lets the unit yield (min-w-0 truncate) instead of the value", () => {
|
||||
const html = render(
|
||||
<TrendCard
|
||||
label="VO2"
|
||||
latest={42}
|
||||
unit="mL/(kg·min)"
|
||||
avg7={42}
|
||||
avg30={42}
|
||||
slope30={null}
|
||||
icon={Activity}
|
||||
/>,
|
||||
);
|
||||
// The unit text renders in full in the markup; the truncate is a
|
||||
// density safeguard that only engages when the column is too narrow.
|
||||
expect(html).toContain("mL/(kg·min)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<TrendCard> responsive layout", () => {
|
||||
it("keeps long BP target tile content wrappable inside the card", () => {
|
||||
// v1.4.28 FB-C2 — the BD-Zielbereich tile dropped `avgAllTime`
|
||||
|
||||
@@ -265,21 +265,30 @@ export function TrendCard({
|
||||
ask: "rechts neben der Zahl … der Pfeil [ist], wie gerade der
|
||||
Trend ist". */}
|
||||
<div
|
||||
className="mt-2 flex min-w-0 items-baseline gap-x-1.5"
|
||||
className="mt-2 flex items-baseline gap-x-1.5"
|
||||
data-slot="trend-card-value-row"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"min-w-0 truncate font-semibold tracking-tight tabular-nums",
|
||||
"shrink-0 whitespace-nowrap font-semibold tracking-tight tabular-nums",
|
||||
// v1.8.7 — the value NEVER truncates, clips, or wraps
|
||||
// mid-number. The maintainer is emphatic: every digit of
|
||||
// the reading must render in full ("130 mmHg" must read as
|
||||
// "130", never "13" then "3 mmHg"). The headline value span
|
||||
// is `shrink-0 whitespace-nowrap` so it keeps its full
|
||||
// intrinsic width and wins the horizontal-space contest
|
||||
// against the unit. When the strip is dense (many tiles →
|
||||
// narrow column) the UNIT yields — it shrinks (`min-w-0
|
||||
// truncate`) and may ellipsis — rather than losing any
|
||||
// digit of the number. The earlier `min-w-0 truncate` on
|
||||
// this span was the bug: it let the value shrink below its
|
||||
// content width and clip the number.
|
||||
//
|
||||
// v1.4.49.4 — paired values (BP systolic/diastolic) render
|
||||
// as `131/85` and outgrow the `text-3xl` footprint on the
|
||||
// narrowest grid columns, ellipsis-clipping to "13…" on the
|
||||
// dashboard. The truncate stays as a defence-in-depth safety
|
||||
// net, but paired tiles drop one font step (text-3xl →
|
||||
// text-2xl) so the full pair stays visible at every column
|
||||
// width. Single-value tiles (weight, pulse, glucose, …)
|
||||
// keep text-3xl because their value never threatens the
|
||||
// budget on its own.
|
||||
// narrowest grid columns. Paired tiles drop one font step
|
||||
// (text-3xl → text-2xl) so the full pair fits; single-value
|
||||
// tiles (weight, pulse, glucose, …) keep text-3xl.
|
||||
secondary ? "text-2xl" : "text-3xl",
|
||||
// `leading-none` must come AFTER the font-size class in the
|
||||
// cn() arg list. Tailwind v4 `text-3xl` / `text-2xl` ship
|
||||
@@ -290,10 +299,11 @@ export function TrendCard({
|
||||
// across every tile in the strip).
|
||||
"leading-none",
|
||||
)}
|
||||
data-slot="trend-card-value"
|
||||
>
|
||||
{latest !== null ? renderPair(latest, secondary?.latest) : "—"}
|
||||
</span>
|
||||
<span className="text-muted-foreground shrink-0 text-sm tabular-nums">
|
||||
<span className="text-muted-foreground min-w-0 truncate text-sm tabular-nums">
|
||||
{unit}
|
||||
</span>
|
||||
<span
|
||||
|
||||
Reference in new issue
Block a user