From 04765ea8ac52e4c44bc2bb5b58714d6cbec9d34f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 10 Jun 2026 16:54:15 +0200 Subject: [PATCH] style: one spacing, title and skeleton language across the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Icons inside buttons drop their manual margins in favour of the button's built-in gap (two hundred call sites), form field stacks settle on one spacing step, card titles on exactly two sizes, inline field errors on one text size, and a dozen hand-rolled loading pulses move onto the shared skeleton primitive while keeping their silhouettes. The intake dialog wording joins the rest of the app on 'übersprungen', and the UI guidelines now document the two deliberate footer and touch-target systems. --- docs/ui-guidelines.md | 22 +- messages/de.json | 2 +- src/app/auth/login/page.tsx | 10 +- src/app/auth/register/page.tsx | 4 +- src/app/bugreport/page.tsx | 4 +- src/app/insights/loading.tsx | 19 +- src/app/insights/scores/[metric]/page.tsx | 2 +- src/app/insights/values/[type]/page.tsx | 6 +- src/app/insights/workouts/[id]/page.tsx | 2 +- src/app/loading.tsx | 7 +- src/app/measurements/page.tsx | 6 +- src/app/medications/[id]/page.tsx | 2 +- src/app/medications/page.tsx | 4 +- src/app/mood/page.tsx | 2 +- src/app/notifications/page.tsx | 16 +- src/app/page.tsx | 34 +- src/components/admin/backups-section.tsx | 20 +- src/components/admin/bug-report-section.tsx | 2 +- src/components/admin/danger-zone-section.tsx | 4 +- .../admin/feedback-inbox-section.tsx | 12 +- src/components/admin/glitchtip-section.tsx | 4 +- src/components/admin/host-metrics-chart.tsx | 3 +- .../admin/login-overview-section.tsx | 2 +- src/components/admin/reminders-section.tsx | 16 +- .../admin/system-status-section.tsx | 7 +- src/components/admin/umami-section.tsx | 4 +- .../admin/user-management-section.tsx | 6 +- .../admin/web-push-vapid-section.tsx | 2 +- src/components/cycle/cycle-settings.tsx | 2 +- src/components/cycle/log-day-sheet.tsx | 16 +- .../medication-intake-quick-add.test.tsx | 18 +- .../dashboard/medication-intake-quick-add.tsx | 7 +- .../data-list/selection-action-bar.tsx | 4 +- .../doctor-report/doctor-report-dialog.tsx | 7 +- src/components/error-details.tsx | 8 +- .../gamification/recent-achievements-card.tsx | 7 +- .../correlation-card-skeleton.test.ts | 10 +- src/components/insights/correlation-card.tsx | 3 +- src/components/insights/daily-briefing.tsx | 11 +- .../insights/insight-status-card.tsx | 19 +- .../insights/insights-edit-mode.tsx | 31 +- .../insights/mood/mood-correlation-cards.tsx | 3 +- src/components/insights/sleep-hypnogram.tsx | 25 +- src/components/insights/sleep-overview.tsx | 7 +- .../insights/sleep-stage-stacked-bar.tsx | 10 +- src/components/insights/sub-page-shell.tsx | 2 +- src/components/insights/trend-annotation.tsx | 14 +- src/components/layout/top-bar.tsx | 5 +- .../measurements/measurement-form.tsx | 14 +- .../measurements/measurement-list.tsx | 375 +++++----- .../ResearchModeAcknowledgmentDialog.tsx | 196 +++--- .../medications/SideEffectsSection.tsx | 4 +- .../card-parts/medication-intake-actions.tsx | 8 +- .../detail/MedicationDetailTabs.tsx | 17 +- .../medications/dose-history-add-dialog.tsx | 16 +- .../medications/intake-edit-dialog.tsx | 4 +- .../medications/intake-import-dialog.tsx | 6 +- .../medications/log-injection-site-dialog.tsx | 2 +- .../medications/log-intake-dialog.tsx | 8 +- .../scheduling/CourseWindowRow.tsx | 10 +- .../scheduling/DoseWindowEditor.tsx | 11 +- .../scheduling/TimesOfDayChips.tsx | 2 +- .../sections/data-portability-row.tsx | 2 +- .../sections/phase-config-sheet.tsx | 2 +- .../wizard/MedicationWizardDialog.tsx | 52 +- .../medications/wizard/steps/Step1Name.tsx | 2 +- .../medications/wizard/steps/Step3Dose.tsx | 12 +- src/components/mood/mood-form.tsx | 272 ++++--- src/components/mood/mood-list.tsx | 666 +++++++++--------- src/components/settings/about-me-section.tsx | 17 +- src/components/settings/about-section.tsx | 16 +- src/components/settings/account-section.tsx | 24 +- src/components/settings/advanced-section.tsx | 12 +- src/components/settings/ai-section.tsx | 40 +- src/components/settings/api-section.tsx | 4 +- .../settings/coach-memory-section.tsx | 2 +- .../settings/dashboard-layout-section.tsx | 6 +- src/components/settings/export-section.tsx | 24 +- .../settings/health-record-export-panel.tsx | 241 ++++--- src/components/settings/import-panel.tsx | 50 +- .../settings/insights-pill-order-section.tsx | 21 +- .../settings/integrations-section.tsx | 105 +-- .../settings/notification-status-card.tsx | 19 +- src/components/settings/ntfy-card.tsx | 2 +- src/components/settings/sources-section.tsx | 6 +- src/components/settings/telegram-card.tsx | 8 +- .../settings/test-connection-button.tsx | 6 +- .../settings/thresholds-editor-section.tsx | 2 +- src/components/settings/web-push-card.tsx | 6 +- src/components/targets/target-edit-sheet.tsx | 134 ++-- src/components/ui/button.tsx | 4 +- src/components/ui/responsive-sheet.tsx | 15 +- 92 files changed, 1433 insertions(+), 1445 deletions(-) diff --git a/docs/ui-guidelines.md b/docs/ui-guidelines.md index 224a8c21d..1afa09ad3 100644 --- a/docs/ui-guidelines.md +++ b/docs/ui-guidelines.md @@ -48,7 +48,7 @@ Tailwind's default 4-pixel scale. Reach for these stops first: | ------------------------------------ | ------------- | ------ | | Inline gap between icon and label | `gap-1` | 4 | | Tight stack inside a card | `gap-2` | 8 | -| Default form-field stack | `space-y-3` | 12 | +| Label-to-field stack (form fields) | `space-y-2` | 8 | | Section block (heading + body) | `space-y-4` | 16 | | Page section separator | `space-y-6` | 24 | | Dashboard tile-row gap, hero spacing | `gap-4` | 16 | @@ -65,13 +65,18 @@ these classes first: | Use | Class | | ---------------------------------- | --------------------------------------- | | Page title (h1) | `text-2xl font-semibold tracking-tight` | -| Section heading (h2) | `text-lg font-semibold` | -| Card title | `text-base font-medium` | +| Section heading / section card | `text-lg font-semibold` | +| Tile / inner card title | `text-base font-semibold` | | Body text | `text-sm` | | Muted helper / metadata | `text-sm text-muted-foreground` | | Microcopy / footer | `text-xs text-muted-foreground` | | Tabular data (latency, large nums) | `font-mono tabular-nums` | +Card titles come in exactly two sizes: `text-lg font-semibold` for a +section-level card (one per page region) and `text-base font-semibold` +for tiles and inner cards. Don't introduce a third level — a smaller +heading is a sign the content wants a list row, not a card. + Never use `font-bold` for emphasis — bump to `font-semibold` instead and let the design system breathe. @@ -381,7 +386,16 @@ We aim for **WCAG 2.1 AA**, with the following non-negotiables: - Color contrast ≥ 4.5:1 for normal text, 3:1 for large text and UI components. Verified by `@axe-core/playwright` in CI on every page. - Touch targets ≥ 44×44 CSS px on bottom-nav and primary action buttons - (WCAG 2.5.5). + (WCAG 2.5.5). Two idioms implement the floor, and they are deliberate: + - **Form controls** (`Input`, `NativeSelect`, `SelectTrigger` default + size): `h-11 sm:h-10` is baked into the primitives — 44 px on + mobile, back to the 40 px desktop tier. Never re-add `min-h-11` on + top of these; it forces 44 px on desktop and breaks the row rhythm. + - **Buttons on dense touch surfaces** (medication detail, dashboard + quick actions): `min-h-11 sm:min-h-9` — 44 px mobile floor, + compact `sm` tier on desktop. Plain `min-h-11` without the `sm:` + reset is legacy spelling; don't introduce new ones, and align a + button to its direct neighbours when editing one. - Focus is always visible: shadcn's `focus-visible:ring` is preserved on every customized component. - Every interactive element has an accessible name. Icon-only buttons diff --git a/messages/de.json b/messages/de.json index 06d7c9a49..0703401ba 100644 --- a/messages/de.json +++ b/messages/de.json @@ -643,7 +643,7 @@ "slotNone": "Kein bestimmter Slot", "takenAtLabel": "Datum und Uhrzeit", "takenAtHint": "Standard ist jetzt; wähle ein früheres Datum und eine frühere Uhrzeit, um eine vergangene Dosis zu erfassen.", - "skippedLabel": "Als ausgelassen erfassen", + "skippedLabel": "Als übersprungen erfassen", "noMedications": "Lege zuerst ein Medikament an, dann kannst du Einnahmen dafür erfassen.", "cancel": "Abbrechen", "submit": "Einnahme erfassen", diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index af8958252..99bf541ed 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -151,9 +151,9 @@ export default function LoginPage() { disabled={loading} > {loading && mode === "passkey" ? ( - + ) : ( - + )} {t("auth.loginWithPasskey")} @@ -173,7 +173,7 @@ export default function LoginPage() { size="lg" onClick={() => setMode("password")} > - + {t("auth.loginWithPassword")} ) : ( @@ -220,9 +220,9 @@ export default function LoginPage() { disabled={loading} > {loading && mode === "password" ? ( - + ) : ( - + )} {t("auth.login")} diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index 26150ab1f..417d5f5d3 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -191,9 +191,9 @@ export default function RegisterPage() { disabled={loading} > {loading ? ( - + ) : ( - + )} {t("auth.register")} diff --git a/src/app/bugreport/page.tsx b/src/app/bugreport/page.tsx index e00dd5930..0273dea5f 100644 --- a/src/app/bugreport/page.tsx +++ b/src/app/bugreport/page.tsx @@ -243,9 +243,9 @@ export default function BugReportPage() { `div.space-y-2 > Button > Loader-Circle`). */} {loading ? ( - + ) : ( - + )} {t("bugreport.submit")} diff --git a/src/app/insights/loading.tsx b/src/app/insights/loading.tsx index 5e2eabad1..1131ba611 100644 --- a/src/app/insights/loading.tsx +++ b/src/app/insights/loading.tsx @@ -8,29 +8,30 @@ * by less than a row. Pure server component — no client hooks, no data * fetch; Next.js renders it instantly while the segment streams. */ +import { Skeleton } from "@/components/ui/skeleton"; export default function InsightsLoading() { return (
-
-
+ +
{/* Chart card */}
-
-
+ +
{/* Assessment card */}
-
-
+ +
-
-
-
+ + +
); diff --git a/src/app/insights/scores/[metric]/page.tsx b/src/app/insights/scores/[metric]/page.tsx index 0aa922bf4..3e3c82932 100644 --- a/src/app/insights/scores/[metric]/page.tsx +++ b/src/app/insights/scores/[metric]/page.tsx @@ -96,7 +96,7 @@ export default function CompositeScorePage() { className="-ml-2 w-fit" > -