fix(settings): align the Vorsorge settings card with the shared pattern

Adopt the standard SettingsCard + SettingsCardHeader contract for the
three Vorsorge settings groups (view, order, manage): bordered card
surface, neutral muted section icon, text-lg title, and a short muted
description, replacing the bare sections with text-sm headings so the
surface reads like every other Settings section. Add a reorder
description string across all locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-20 16:56:42 +02:00
1 parent aae65b8bb1
commit ed99d207e4
7 files changed
+90 -73

No files matched your search

+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Lege fest, ob die Einträge als Karten oder als kompakte Liste angezeigt werden.",
"reorder": {
"heading": "Reihenfolge",
"description": "Ziehe die Einträge, um ihre Reihenfolge festzulegen.",
"empty": "Noch keine Einträge zum Sortieren."
}
},
+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Choose whether entries show as cards or as a compact list.",
"reorder": {
"heading": "Order",
"description": "Drag the entries to set the order they appear in.",
"empty": "No entries to reorder yet."
}
},
+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Elige si las entradas se muestran como tarjetas o como lista compacta.",
"reorder": {
"heading": "Orden",
"description": "Arrastra las entradas para definir el orden en que aparecen.",
"empty": "Aún no hay entradas para ordenar."
}
},
+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Choisissez si les entrées s'affichent en cartes ou en liste compacte.",
"reorder": {
"heading": "Ordre",
"description": "Faites glisser les entrées pour définir leur ordre d'affichage.",
"empty": "Aucune entrée à réordonner pour l'instant."
}
},
+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Scegli se le voci appaiono come schede o come elenco compatto.",
"reorder": {
"heading": "Ordine",
"description": "Trascina le voci per definire l'ordine in cui appaiono.",
"empty": "Nessuna voce da riordinare."
}
},
+1
View File
@@ -6472,6 +6472,7 @@
"viewDescription": "Wybierz, czy pozycje mają być pokazywane jako karty czy jako zwarta lista.",
"reorder": {
"heading": "Kolejność",
"description": "Przeciągnij pozycje, aby ustawić kolejność ich wyświetlania.",
"empty": "Brak pozycji do uporządkowania."
}
},
@@ -10,10 +10,14 @@
* `useModuleListPrefs("vorsorge")`; the toggles persist through the existing
* reminder PATCH route.
*/
import { ArrowUpDown, LayoutGrid, ListChecks } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Switch } from "@/components/ui/switch";
import { Skeleton } from "@/components/ui/skeleton";
import { useTranslations } from "@/lib/i18n/context";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { ModuleViewToggle } from "@/components/module-list/module-view-toggle";
import {
ModuleOrderEditor,
@@ -52,82 +56,89 @@ export function VorsorgeSettings() {
: t("measurementReminders.selfPlanned"),
}));
// v1.18.10 (W7) — the three Vorsorge settings groups adopt the shared
// `SettingsCard` + `SettingsCardHeader` contract (rounded-xl bordered
// surface, neutral muted icon, `text-lg` title, short muted description)
// so this surface reads like every other Settings section instead of bare
// `<section>` blocks with `text-sm` headings.
return (
<div className="space-y-8">
<section className="space-y-3">
<div className="flex items-center justify-between gap-3">
<h2 className="text-sm font-semibold">
{t("moduleList.viewHeading")}
</h2>
<ModuleViewToggle view={prefs.view} onChange={setView} />
</div>
<p className="text-muted-foreground text-sm">
{t("moduleList.viewDescription")}
</p>
</section>
<div className="space-y-6">
<SettingsCard>
<SettingsCardHeader
icon={LayoutGrid}
title={t("moduleList.viewHeading")}
description={t("moduleList.viewDescription")}
status={<ModuleViewToggle view={prefs.view} onChange={setView} />}
/>
</SettingsCard>
<section className="space-y-3">
<h2 className="text-sm font-semibold">
{t("moduleList.reorder.heading")}
</h2>
{isLoading ? (
<div className="space-y-2">
{Array.from({ length: 3 }, (_, i) => (
<Skeleton key={i} className="h-12 w-full" />
))}
</div>
) : (
<ModuleOrderEditor items={reorderItems} onChange={setOrder} />
)}
</section>
<section className="space-y-3">
<h2 className="text-sm font-semibold">
{t("measurementReminders.manage.title")}
</h2>
<p className="text-muted-foreground text-sm">
{t("measurementReminders.manage.description")}
</p>
{isLoading ? (
<Skeleton className="h-24 w-full" />
) : (reminders?.length ?? 0) === 0 ? (
<p className="text-muted-foreground text-sm">
{t("measurementReminders.manage.empty")}
</p>
) : (
<Card>
<CardContent className="divide-border divide-y p-0">
{ordered.map((reminder) => (
<div
key={reminder.id}
className="flex items-center justify-between gap-3 px-4 py-3"
>
<span
id={`vorsorge-reminder-label-${reminder.id}`}
className="min-w-0 truncate text-sm"
>
{resolveLabel(reminder, t)}
</span>
<Switch
checked={reminder.enabled}
onCheckedChange={(next) =>
update.mutate({
id: reminder.id,
body: { enabled: next },
})
}
disabled={update.isPending}
// L3 — label each row's Switch by its own name span so SR
// users hear the reminder name (plus the switch role's
// on/off state) instead of N identical generic labels.
aria-labelledby={`vorsorge-reminder-label-${reminder.id}`}
/>
</div>
<SettingsCard>
<SettingsCardHeader
icon={ArrowUpDown}
title={t("moduleList.reorder.heading")}
description={t("moduleList.reorder.description")}
/>
<div className="mt-4 pl-7">
{isLoading ? (
<div className="space-y-2">
{Array.from({ length: 3 }, (_, i) => (
<Skeleton key={i} className="h-12 w-full" />
))}
</CardContent>
</Card>
)}
</section>
</div>
) : (
<ModuleOrderEditor items={reorderItems} onChange={setOrder} />
)}
</div>
</SettingsCard>
<SettingsCard>
<SettingsCardHeader
icon={ListChecks}
title={t("measurementReminders.manage.title")}
description={t("measurementReminders.manage.description")}
/>
<div className="mt-4 pl-7">
{isLoading ? (
<Skeleton className="h-24 w-full" />
) : (reminders?.length ?? 0) === 0 ? (
<p className="text-muted-foreground text-sm">
{t("measurementReminders.manage.empty")}
</p>
) : (
<Card>
<CardContent className="divide-border divide-y p-0">
{ordered.map((reminder) => (
<div
key={reminder.id}
className="flex items-center justify-between gap-3 px-4 py-3"
>
<span
id={`vorsorge-reminder-label-${reminder.id}`}
className="min-w-0 truncate text-sm"
>
{resolveLabel(reminder, t)}
</span>
<Switch
checked={reminder.enabled}
onCheckedChange={(next) =>
update.mutate({
id: reminder.id,
body: { enabled: next },
})
}
disabled={update.isPending}
// L3 — label each row's Switch by its own name span so SR
// users hear the reminder name (plus the switch role's
// on/off state) instead of N identical generic labels.
aria-labelledby={`vorsorge-reminder-label-${reminder.id}`}
/>
</div>
))}
</CardContent>
</Card>
)}
</div>
</SettingsCard>
</div>
);
}