Fix settings field rows, defaults and narrow layouts

Module rows drop the field-label colon and inset, the reminder number
fields align, target defaults print at their own precision, the
appointment offer wraps instead of squeezing to one word per line, the
export format legend matches its neighbour label, and the profile-photo
explainer reads as body text.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 20:20:03 +02:00
1 parent 2dda933d77
commit 37f922af97
10 files changed
+41 -19

No files matched your search

@@ -113,7 +113,7 @@ describe("change-password card placement", () => {
"utf8",
);
for (const marker of [
"settings.passwordReset",
"settings.passwordTitle",
"settings.changePassword",
"/api/auth/password",
// A change-password form is the pair current + new. The Account form
@@ -73,4 +73,13 @@ describe("<ThresholdsEditorSection> glucose unit", () => {
expect(html).toContain('value="72"');
expect(html).toContain('min="40"');
});
it("prints a whole-number default without a trailing .0", () => {
// A bound reads at its own precision: the fixed one decimal printed
// "70.0–99.0 mg/dL" and "8,000.0–15,000.0 steps".
authUser.current = { glucoseUnit: "mg/dL", unitPreference: "metric" };
const html = render();
expect(html).toMatch(/70–99 mg\/dL/);
expect(html).not.toMatch(/70\.0–99\.0/);
});
});
@@ -114,7 +114,7 @@ export function AvatarSection() {
Two strings rather than one merged key: the same wording is the
upload's own constraint copy, and a translator keeps the shorter unit
either way. */}
<p className="text-muted-foreground text-sm">
<p className="text-sm">
{t("settings.avatar.description")} {t("settings.avatar.hint")}
</p>
<div className="flex items-center gap-4">
@@ -171,7 +171,7 @@ export function AvatarSection() {
{msg && (
<p
role="alert"
className={`mt-3 text-sm ${
className={`text-sm ${
msgType === "success" ? "text-success" : "text-destructive"
}`}
>
+4 -4
View File
@@ -244,7 +244,7 @@ export function LowStockCard({
/>
<p className="text-sm">{t("notifications.lowStock.detail")}</p>
{enabled && (
<div className="flex min-h-11 items-center gap-3">
<div className="flex min-h-11 items-center justify-between gap-3">
<label htmlFor="low-stock-days" className="text-sm font-medium">
{t("notifications.lowStock.daysLabel")}
</label>
@@ -254,7 +254,7 @@ export function LowStockCard({
inputMode="numeric"
min={MIN_DAYS}
max={MAX_DAYS}
className="w-auto"
className="w-24 text-right tabular-nums"
value={draft ?? String(days)}
disabled={!isAuthenticated || saving}
onChange={(e) => setDraft(e.target.value)}
@@ -275,7 +275,7 @@ export function LowStockCard({
own supply tab. */}
{enabled && (
<div>
<div className="flex min-h-11 items-center gap-3">
<div className="flex min-h-11 items-center justify-between gap-3">
<label htmlFor="low-stock-lead" className="text-sm font-medium">
{t("notifications.lowStock.leadLabel")}
</label>
@@ -285,7 +285,7 @@ export function LowStockCard({
inputMode="numeric"
min={MIN_LEAD}
max={MAX_LEAD}
className="w-auto"
className="w-24 text-right tabular-nums"
value={leadDraft ?? String(leadDays)}
disabled={!isAuthenticated || saving}
onChange={(e) => setLeadDraft(e.target.value)}
@@ -98,7 +98,10 @@ export function ModuleToggleRow({
aria-hidden="true"
/>
<div className="min-w-0 space-y-0.5">
<Label htmlFor={inputId} className="text-sm font-medium">
{/* A row title beside a switch, not a field label: no colon, and
on the description's left edge (the primitive's `pl-1` would
inset it), matching the admin `SettingsToggle` rows. */}
<Label htmlFor={inputId} noColon className="pl-0 text-sm font-medium">
{label}
</Label>
<p className="text-muted-foreground text-xs">{description}</p>
@@ -203,7 +203,7 @@ export function MoodReminderCard({
/>
<p className="text-sm">{t("notifications.moodReminder.detail")}</p>
{enabled && (
<div className="flex min-h-11 items-center gap-3">
<div className="flex min-h-11 items-center justify-between gap-3">
<label htmlFor="mood-reminder-hour" className="text-sm font-medium">
{t("notifications.moodReminder.hourLabel")}
</label>
@@ -229,8 +229,8 @@ export function MoodReminderCard({
aria-live="polite"
className={
msgType === "error"
? "text-destructive mt-3 text-sm"
: "text-muted-foreground mt-3 text-sm"
? "text-destructive text-sm"
: "text-muted-foreground text-sm"
}
>
{msg}
+2 -2
View File
@@ -96,7 +96,7 @@ export function PasswordCard() {
<SettingsCard data-slot="settings-password-card">
<SettingsCardHeader
icon={Shield}
title={t("settings.passwordReset")}
title={t("settings.passwordTitle")}
description={t("settings.changePasswordDescription")}
/>
<SettingsCardActions>
@@ -122,7 +122,7 @@ export function PasswordCard() {
setMsgType(null);
}
}}
title={t("settings.passwordReset")}
title={t("settings.passwordTitle")}
description={t("settings.changePasswordDescription")}
className="sm:max-w-xl"
>
@@ -74,8 +74,11 @@ export function ReportVisitOffer({
data-slot="report-visit-offer"
className="border-border flex flex-wrap items-center gap-2 rounded-lg border p-3"
>
{/* Content, not meta: it is the offer, and it names its own source. */}
<p className="text-foreground flex min-w-0 flex-1 items-center gap-2 text-sm">
{/* Content, not meta: it is the offer, and it names its own source. On
a phone the sentence takes the full row and the two buttons wrap
below it; with a zero basis it was squeezed into a one-word column
beside them. */}
<p className="text-foreground flex min-w-0 flex-1 basis-full items-center gap-2 text-sm sm:basis-0">
<CalendarClock className="size-4 shrink-0" aria-hidden />
<span>
{t("settings.healthRecord.visitOffer", {
@@ -88,7 +91,7 @@ export function ReportVisitOffer({
type="button"
variant="outline"
size="sm"
className="min-h-11 sm:min-h-9"
className="ml-auto min-h-11 sm:ml-0 sm:min-h-9"
data-slot="report-visit-offer-apply"
onClick={() => onUseVisitDate(localDay(next.occurredAt))}
>
@@ -261,7 +261,7 @@ export function HealthRecordExportPanel() {
{/* Format + range share a row on desktop; on mobile they stack. */}
<div className="grid gap-4 sm:grid-cols-2">
<fieldset className="space-y-1.5">
<legend className="mb-1 text-sm font-medium">
<legend className="pl-1 text-sm leading-none font-medium after:content-[':']">
{t("settings.healthRecord.format")}
</legend>
<div className="flex flex-wrap gap-2" role="radiogroup">
@@ -142,7 +142,7 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
at all. The page subtitle is the one that goes. */}
<SettingsCardHeader
icon={SlidersHorizontal}
title={t("settings.sections.thresholds.title")}
title={t("thresholds.cardTitle")}
/>
{isLoading || !data ? (
@@ -307,6 +307,13 @@ function MetricRow({
minNum < maxNum;
const defaultRange = effective?.default;
// A bound reads at its own precision: "120–129 mmHg", "8,000–15,000 steps",
// "61.3–82.5 kg". A fixed one decimal printed "120.0–129.0" and
// "8,000.0–15,000.0" for metrics that are whole numbers by nature.
const formatBound = (value: number) => {
const rounded = Math.round(value * 10) / 10;
return fmt.number(rounded, Number.isInteger(rounded) ? 0 : 1);
};
return (
<div className="border-border space-y-3 rounded-lg border p-4">
@@ -315,7 +322,7 @@ function MetricRow({
<p className="text-sm font-medium">{t(METRIC_LABEL_KEYS[metric])}</p>
<p className="text-muted-foreground text-xs">
{defaultRange
? `${t("thresholds.defaultLabel")}: ${fmt.number(units.toDisplay(defaultRange.greenMin), 1)}–${fmt.number(units.toDisplay(defaultRange.greenMax), 1)} ${bounds.unit}`
? `${t("thresholds.defaultLabel")}: ${formatBound(units.toDisplay(defaultRange.greenMin))}–${formatBound(units.toDisplay(defaultRange.greenMax))} ${bounds.unit}`
: t("thresholds.unsetExplanation")}
</p>
</div>