chore(ui): apply M7 + L1 + L3 + L4 polish across medication form, motion-reduce, details aria, therapy timeline

W10 design-review carry-over items. M7 lifts the medication-form
day-of-week buttons, the more-horizontal trigger, and the schedule
label-input gaps to the 44 px / space-y-2 standard used by every
other settings surface. L1 pairs every animate-spin/pulse/bounce
with motion-reduce:animate-none across the component tree so users
with prefers-reduced-motion get a still surface. L3 adds the
aria-controls + panel-id pairing to the inventory + coach-evidence
<details> blocks for parity with the Health Score provenance
accordion. L4 inserts a sr-only h4 per therapy-timeline entry so
screen-reader users can jump between medication blocks.
This commit is contained in:
Marc-André Bombeck committed 2026-05-15 17:13:42 +02:00
1 parent 19b5129306
commit 32a023dfff
71 files changed
+165 -136

No files matched your search

+2 -2
View File
@@ -265,7 +265,7 @@ export function AboutSection() {
{/* Updates — proper heading + last-check timestamp + manual button
that proxies through `/api/version/check-updates`. The v1.4.2
version called `api.github.com` directly from the browser, which
the production CSP blocked silently — that's why "nothing
the production CSP blocked silently — that motion-reduce:animate-none's why "nothing
happened" when the user clicked the button. */}
{version && (
<div className="bg-card border-border rounded-xl border p-6">
@@ -284,7 +284,7 @@ export function AboutSection() {
disabled={checking}
>
{checking ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<RefreshCw className="mr-2 h-4 w-4" />
)}
+4 -4
View File
@@ -407,7 +407,7 @@ export function AccountSection() {
wide on sm+). Date of birth is the bottom of the
biological-profile block; language is the only UI
preference on this card. They sit together to close the
"single-cell row" gap that broke the form's grid. */}
"single-cell row" gap that broke the form motion-reduce:animate-none's grid. */}
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="dob">{t("settings.dateOfBirth")}</Label>
@@ -459,7 +459,7 @@ export function AccountSection() {
<div className="flex justify-end">
<Button type="submit" disabled={saving}>
{saving ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -483,7 +483,7 @@ export function AccountSection() {
disabled={passkeyLoading}
>
{passkeyLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<KeyRound className="mr-2 h-4 w-4" />
)}
@@ -728,7 +728,7 @@ function PasskeyListSection({ isAuthenticated }: { isAuthenticated: boolean }) {
which on a 393px viewport hid the right-most column —
including the destructive delete action. Render the desktop
table only at `≥ md`, and at `< md` paint a card-list where
every passkey's name, device type, backup status, created
every passkey motion-reduce:animate-none's name, device type, backup status, created
date, and delete action are all visible without scrolling. */}
<div className="border-border mt-3 hidden overflow-x-auto rounded-lg border md:block">
<table className="w-full text-sm">
+1 -1
View File
@@ -281,7 +281,7 @@ function DataResetCard() {
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm" disabled={deleting}>
{deleting ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Trash2 className="mr-1 h-3.5 w-3.5" />
)}
+7 -7
View File
@@ -716,7 +716,7 @@ function CodexProviderForm({
/* ────────────────────────────────────────────────────────────────
* OpenAI form — API key + model select + collapsed Base URL override.
* Save mutation flips both `aiProvider` (for the legacy single-result
* resolver) and `aiOpenaiKeyEncrypted` (the user's key) so an OPENAI
* resolver) and `aiOpenaiKeyEncrypted` (the user motion-reduce:animate-none's key) so an OPENAI
* pick is visible to every code path that reads the row.
* ──────────────────────────────────────────────────────────────── */
@@ -889,7 +889,7 @@ function OpenAIProviderForm({
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -1045,7 +1045,7 @@ function AnthropicProviderForm({
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -1202,7 +1202,7 @@ function LocalProviderForm({
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -1454,7 +1454,7 @@ function FallbackChainCard({
data-testid="ai-fallback-chain-save"
>
{saveMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -1659,7 +1659,7 @@ function RuntimeActionsRow({
data-testid="ai-test-active-provider"
>
{testing ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Sparkles className="mr-2 h-4 w-4" />
)}
@@ -1673,7 +1673,7 @@ function RuntimeActionsRow({
disabled={regen}
>
{regen ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<RefreshCw className="mr-2 h-4 w-4" />
)}
+1 -1
View File
@@ -210,7 +210,7 @@ function ApiTokensCard() {
size="sm"
disabled={creating || !newName.trim()}
>
{creating && <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />}
{creating && <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />}
{t("common.create")}
</Button>
</form>
@@ -241,7 +241,7 @@ export function DashboardLayoutSection({ id }: { id: string }) {
{isLoading || !layout ? (
<div className="text-muted-foreground flex items-center gap-2 text-sm">
<Loader2 className="h-4 w-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin motion-reduce:animate-none" />
{t("common.loading")}
</div>
) : (
@@ -342,7 +342,7 @@ export function DashboardLayoutSection({ id }: { id: string }) {
disabled={saveMutation.isPending}
>
{saveMutation.isPending && (
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
)}
{t("common.save")}
</Button>
+4 -4
View File
@@ -224,7 +224,7 @@ function DoctorReportCard() {
onClick={() => setOpen(true)}
disabled={busy}
>
{busy && <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />}
{busy && <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />}
{t("settings.sections.export.cards.doctorReport.action")}
</Button>
}
@@ -358,7 +358,7 @@ function CsvCard({
disabled={busy}
>
{busy ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Download className="mr-1 h-3.5 w-3.5" />
)}
@@ -445,7 +445,7 @@ function MedicationsCsvCard() {
disabled={busy}
>
{busy ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Download className="mr-1 h-3.5 w-3.5" />
)}
@@ -534,7 +534,7 @@ function FullBackupCard() {
disabled={busy}
>
{busy ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<FileText className="mr-1 h-3.5 w-3.5" />
)}
@@ -434,7 +434,7 @@ function WithingsCard({
}
>
{credsSaving ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-1 h-3.5 w-3.5" />
)}
@@ -463,7 +463,7 @@ function WithingsCard({
disabled={syncing}
>
{syncing ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<RefreshCw className="mr-1 h-3.5 w-3.5" />
)}
@@ -473,7 +473,7 @@ function WithingsCard({
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm" disabled={syncing}>
{syncing ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<RefreshCw className="mr-1 h-3.5 w-3.5" />
)}
@@ -726,7 +726,7 @@ function MoodLogCard({
disabled={saving || (!url.trim() && !apiKey.trim())}
size="sm"
>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />}
<Save className="mr-2 h-4 w-4" />
{t("common.save")}
</Button>
@@ -792,7 +792,7 @@ function MoodLogCard({
disabled={syncing}
onClick={() => handleSync(false)}
>
{syncing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{syncing && <Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />}
<RefreshCw className="mr-2 h-4 w-4" />
{t("settings.moodLogSync")}
</Button>
@@ -112,7 +112,7 @@ export function NotificationStatusCard() {
if (isLoading) {
return (
<div className="bg-card border-border rounded-xl border p-6">
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin motion-reduce:animate-none" />
</div>
);
}
@@ -279,7 +279,7 @@ function ChannelRow({
className="min-h-11"
>
{reEnablePending ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<RotateCw className="mr-1 h-3.5 w-3.5" />
)}
@@ -296,7 +296,7 @@ function ChannelRow({
className="min-h-11"
>
{testPending ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Send className="mr-1 h-3.5 w-3.5" />
)}
+2 -2
View File
@@ -195,7 +195,7 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
onClick={() => test.mutate()}
>
{test.isPending ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Send className="mr-1 h-3.5 w-3.5" />
)}
@@ -203,7 +203,7 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
</Button>
<Button type="submit" disabled={save.isPending}>
{save.isPending && (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
)}
{t("common.save")}
</Button>
+2 -2
View File
@@ -200,7 +200,7 @@ export function TelegramCard({
onClick={handleTest}
>
{testing ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Send className="mr-2 h-4 w-4" />
)}
@@ -208,7 +208,7 @@ export function TelegramCard({
</Button>
<Button type="submit" disabled={saving}>
{saving ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Save className="mr-2 h-4 w-4" />
)}
@@ -87,7 +87,7 @@ export function TestConnectionButton({
disabled={disabled || testing}
>
{testing ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Plug className="mr-1 h-3.5 w-3.5" />
)}
+3 -3
View File
@@ -182,7 +182,7 @@ export function WebPushCard() {
<div className="mt-4 space-y-3">
{loading ? (
<div className="flex items-center gap-2">
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin motion-reduce:animate-none" />
</div>
) : !isSupported ? (
<p className="text-muted-foreground text-sm">
@@ -202,7 +202,7 @@ export function WebPushCard() {
disabled={actionLoading}
>
{actionLoading && (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
)}
{t("settings.webPushUnsubscribe")}
</Button>
@@ -214,7 +214,7 @@ export function WebPushCard() {
disabled={actionLoading}
>
{actionLoading ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<BellRing className="mr-1 h-3.5 w-3.5" />
)}