fix(day): fit the GKI tile's pair line in its column

The single-reading line spelled out both readings with their names and
units and was cut off after the glucose in every layout. Show the two
numbers with one unit, which fits the tile, and keep the worded pair for
assistive tech.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 00:46:30 +01:00
1 parent a7e98b980e
commit 128cf55ab6
12 files changed
+95 -12

No files matched your search

@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { locales } from "@/lib/i18n/config";
/**
* The GKI tile sits in a two-column grid, about 150 px of text wide at phone
* width and in the 420 px day layer alike. The pair line under the index has
* to fit there whole, so both readings stay readable; the worded pair goes to
* assistive tech.
*/
const POINT = {
at: "2026-11-01T08:00:00.000Z",
value: 3.8,
ketoneId: "k1",
glucoseId: "g1",
glucoseAt: "2026-11-01T08:00:00.000Z",
ketonesMmol: 0.25,
glucoseMmol: 12.5,
};
async function render(locale: string) {
const { I18nProvider } = await import("@/lib/i18n/context");
const { DayGkiTile } = await import("../day-gki-tile");
const messages = (await import(`../../../../messages/${locale}.json`))
.default;
return renderToStaticMarkup(
<I18nProvider initialLocale={locale as never} initialMessages={messages}>
<DayGkiTile points={[POINT]} />
</I18nProvider>,
);
}
function visiblePair(html: string): string {
const match = html.match(
/<span aria-hidden="true" class="[^"]*truncate text-xs[^"]*">([^<]*)<\/span>/,
);
if (!match) throw new Error(`no visible pair line in ${html}`);
return match[1]!;
}
describe("<DayGkiTile>", () => {
it.each([...locales])(
"keeps the pair line short enough for the tile in %s",
async (locale) => {
const line = visiblePair(await render(locale));
expect(line).toMatch(/12[.,]5/);
expect(line).toMatch(/0[.,]25/);
expect(line.length).toBeLessThanOrEqual(20);
},
);
it("names both readings for assistive tech", async () => {
const html = await render("en");
expect(html).toMatch(
/<span class="sr-only">Glucose 12\.5 mmol\/L \/ ketones 0\.25 mmol\/L<\/span>/,
);
});
});
+25 -12
View File
@@ -23,6 +23,14 @@ export function DayGkiTile({ points }: { points: readonly DayGki[] }) {
const fmt = useFormatters();
const latest = points[points.length - 1];
if (!latest) return null;
const pair = {
glucose: fmt.number(latest.glucoseMmol, 1),
ketones: fmt.number(
latest.ketonesMmol,
ketoneFractionDigits(latest.ketonesMmol),
),
unit: GKI_COMPONENT_UNIT,
};
return (
<li data-slot="day-gki" className="flex min-w-0">
<Link
@@ -36,18 +44,23 @@ export function DayGkiTile({ points }: { points: readonly DayGki[] }) {
<span className="truncate text-base font-semibold tabular-nums">
{fmt.number(latest.value, 1)}
</span>
<span className="text-muted-foreground truncate text-xs tabular-nums">
{points.length > 1
? tCount("day.gkiCount", points.length)
: t("day.gkiPair", {
glucose: fmt.number(latest.glucoseMmol, 1),
ketones: fmt.number(
latest.ketonesMmol,
ketoneFractionDigits(latest.ketonesMmol),
),
unit: GKI_COMPONENT_UNIT,
})}
</span>
{points.length > 1 ? (
<span className="text-muted-foreground truncate text-xs tabular-nums">
{tCount("day.gkiCount", points.length)}
</span>
) : (
<>
{/* The tile is about 150 px of text wide: the numbers alone fit,
the worded pair is for assistive tech. */}
<span
aria-hidden="true"
className="text-muted-foreground truncate text-xs tabular-nums"
>
{t("day.gkiPairShort", pair)}
</span>
<span className="sr-only">{t("day.gkiPair", pair)}</span>
</>
)}
</Link>
</li>
);