refactor(medications): collapse detail-page chrome to one heading scale

Lift the section-header chrome on /medications/[id] to one canonical
shape so the page reads as one document. Every section header now uses
text-base font-semibold leading-6 tracking-tight via the shared
<MedicationDetailSection> primitive (which keeps the surrounding
border-border/60 + px-3 py-2.5 + border-t contract). DrugLevelChart's
standalone header migrates to <h2> with the same classes.

Sweep micro labels from text-[10px] / text-[11px] to the canonical
text-xs scale across SchedulingSection, TitrationSection,
SideEffectsSection, and the SideEffects timeline row. The detail page
now lives on three sizes: one heading (text-base font-semibold), one
body (text-sm), one micro (text-xs).

Closes FB-F3 and FB-F4. Restores the FB-F2 row-layout adjustment
(fixed-width date slot + line-clamped notes) that an adjacent commit
had reverted.
This commit is contained in:
Marc-André Bombeck committed 2026-05-16 01:53:56 +02:00
1 parent 8f3bfc37a0
commit 5109e93097
5 files changed
+74 -44

No files matched your search

+13 -4
View File
@@ -245,12 +245,21 @@ export function DrugLevelChart({
} as const;
const body = (
<>
{/* v1.4.28 FB-F3 / F4 — align the standalone chart header to the
canonical `<MedicationDetailSection>` heading scale so the
full `/medications/[id]` surface reads on one heading shape.
The Activity glyph + drug INN line stay as header chrome on
the right; the heading element is now `<h2>` + canonical
classes to match the surrounding wave-4b sections. */}
{!compact && (
<header className="mb-3 flex flex-wrap items-center gap-2">
<Activity className="text-dracula-purple h-4 w-4 shrink-0" />
<h3 id="drug-level-chart-title" className="text-sm font-semibold">
<h2
id="drug-level-chart-title"
className="text-foreground text-base font-semibold leading-6 tracking-tight"
>
{t("medications.researchMode.chart.title")}
</h3>
</h2>
{drugId && (
<span className="text-muted-foreground text-xs">
· {t(`medications.glp1.drug.${drugId}.name`)}
@@ -259,9 +268,9 @@ export function DrugLevelChart({
</header>
)}
{compact && (
<h3 id="drug-level-chart-title" className="sr-only">
<h2 id="drug-level-chart-title" className="sr-only">
{t("medications.researchMode.chart.title")}
</h3>
</h2>
)}
{/* Decision tree:
@@ -183,7 +183,7 @@ export function SchedulingSection({
{/* 30-day timeline */}
<div className="space-y-1.5">
<p className="text-muted-foreground text-[11px] uppercase tracking-wide">
<p className="text-muted-foreground text-xs uppercase tracking-wide">
{t("medications.scheduling.cadenceViz.title")}
</p>
{data.timeline.length === 0 ? (
@@ -232,7 +232,7 @@ export function SchedulingSection({
);
})}
</div>
<div className="text-muted-foreground flex flex-wrap gap-x-3 gap-y-1 pt-1 text-[10px]">
<div className="text-muted-foreground flex flex-wrap gap-x-3 gap-y-1 pt-1 text-xs">
{(["taken", "skipped", "missed", "upcoming"] as const).map(
(s) => (
<span key={s} className="inline-flex items-center gap-1">
@@ -254,7 +254,7 @@ export function SchedulingSection({
{/* Compliance chips */}
<div className="space-y-1.5">
<p className="text-muted-foreground text-[11px] uppercase tracking-wide">
<p className="text-muted-foreground text-xs uppercase tracking-wide">
{t("medications.scheduling.compliance.title")}
</p>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
@@ -313,7 +313,7 @@ function ComplianceChip({ label, value, tooltip }: ComplianceChipProps) {
title={tooltip}
aria-label={`${label}: ${value}. ${tooltip}`}
>
<p className="text-muted-foreground text-[10px] uppercase tracking-wide">
<p className="text-muted-foreground text-xs uppercase tracking-wide">
{label}
</p>
<p className="text-foreground text-sm font-medium">{value}</p>
@@ -269,7 +269,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) {
{!isLoading && items.length > 0 && (
<div className="space-y-2">
<p className="text-muted-foreground text-[11px] uppercase tracking-wide">
<p className="text-muted-foreground text-xs uppercase tracking-wide">
{t("medications.sideEffects.recentTitle")}
</p>
<ul className="space-y-1.5">
@@ -279,28 +279,41 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) {
className="bg-muted/30 flex items-start justify-between gap-2 rounded-md px-2.5 py-2"
>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex flex-wrap items-center gap-1.5">
<Badge variant="outline" className="text-[10px]">
{t(
`medications.sideEffects.categories.${categoryI18nKey(row.category)}`,
)}
</Badge>
<span className="text-foreground font-medium">
{t(
`medications.sideEffects.entries.${entryI18nKey(row.entry)}`,
)}
</span>
<Badge variant="secondary" className="text-[10px]">
{t(
`medications.sideEffects.severity.${severityLikertLabel(row.severity as 1 | 2 | 3 | 4 | 5)}`,
)}
</Badge>
{/* v1.4.28 FB-F2 — fixed-width date slot pinned to
the right of the row meta so the date stamp keeps
its column across rows. The category badge +
entry label + severity chip share the left-most
slot; the date claims a 4ch-wide right column. */}
<div className="flex items-start gap-2">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
<Badge variant="outline" className="text-xs">
{t(
`medications.sideEffects.categories.${categoryI18nKey(row.category)}`,
)}
</Badge>
<span className="text-foreground truncate text-sm font-medium">
{t(
`medications.sideEffects.entries.${entryI18nKey(row.entry)}`,
)}
</span>
<Badge variant="secondary" className="text-xs">
{t(
`medications.sideEffects.severity.${severityLikertLabel(row.severity as 1 | 2 | 3 | 4 | 5)}`,
)}
</Badge>
</div>
<p
className="text-muted-foreground w-[5.5rem] shrink-0 text-right text-xs tabular-nums"
data-slot="side-effect-row-date"
>
{fmt.dateShort(new Date(row.occurredAt))}
</p>
</div>
<p className="text-muted-foreground text-[11px]">
{fmt.dateShort(new Date(row.occurredAt))}
</p>
{row.notes && (
<p className="text-foreground/80 whitespace-pre-wrap break-words">
<p
className="text-foreground/80 line-clamp-2 text-sm break-words"
data-slot="side-effect-row-notes"
>
{row.notes}
</p>
)}
@@ -104,7 +104,7 @@ export function TitrationSection({ medicationId }: TitrationSectionProps) {
const headerExtras = data ? (
<span
className="text-muted-foreground text-[10px] uppercase tracking-wide"
className="text-muted-foreground text-xs uppercase tracking-wide"
data-slot="titration-drug-inn"
>
{data.drugInn}
@@ -133,7 +133,7 @@ export function TitrationSection({ medicationId }: TitrationSectionProps) {
{data && !isLoading && (
<div className="space-y-3">
<p className="text-muted-foreground text-[11px] uppercase tracking-wide">
<p className="text-muted-foreground text-xs uppercase tracking-wide">
{t("medications.titration.drugLadderHeader")}
</p>
@@ -170,7 +170,7 @@ export function TitrationSection({ medicationId }: TitrationSectionProps) {
isCurrent ? "current" : isPast ? "past" : "upcoming"
}
>
<span className="text-muted-foreground text-[10px] uppercase tracking-wide">
<span className="text-muted-foreground text-xs uppercase tracking-wide">
{t("medications.titration.stepLabel", {
n: step.stepIndex + 1,
})}
@@ -178,13 +178,13 @@ export function TitrationSection({ medicationId }: TitrationSectionProps) {
<span className="text-foreground text-sm font-medium">
{step.doseMg} {t("medications.titration.doseUnitMg")}
</span>
<span className="text-muted-foreground text-[10px]">
<span className="text-muted-foreground text-xs">
{t("medications.titration.typicalWeeksOnStep", {
weeks: step.typicalWeeks,
})}
</span>
{isCurrent && (
<span className="text-primary mt-1 text-[10px] font-medium uppercase tracking-wide">
<span className="text-primary mt-1 text-xs font-medium uppercase tracking-wide">
{t("medications.titration.youAreHere")}
</span>
)}
@@ -224,14 +224,14 @@ export function TitrationSection({ medicationId }: TitrationSectionProps) {
)}
<div className="border-border/60 border-t pt-2">
<p className="text-muted-foreground text-[10px]">
<p className="text-muted-foreground text-xs">
{t("medications.titration.disclaimer")}
</p>
<a
href={data.sourceEMA}
target="_blank"
rel="noopener noreferrer"
className="text-foreground/80 hover:text-foreground mt-1 inline-flex items-center gap-1 text-[10px] underline-offset-2 hover:underline"
className="text-foreground/80 hover:text-foreground mt-1 inline-flex items-center gap-1 text-xs underline-offset-2 hover:underline"
>
{t("medications.titration.emaSourceCta")}
<ExternalLink className="h-2.5 w-2.5" aria-hidden="true" />
@@ -3,16 +3,24 @@
import { type ReactNode } from "react";
/**
* v1.4.25 W21 Fix-N — section wrapper for the three medication-detail
* panels (Titration, Scheduling, SideEffects). The four wave-4b
* v1.4.25 W21 Fix-N — section wrapper for the medication-detail panels
* (Titration, Scheduling, SideEffects, DrugLevelChart). The wave-4b
* sections previously hand-rolled the same chrome — border + header
* row + dotted divider + body padding — and drifted on padding (px-3
* vs px-3 py-2.5 vs px-3 py-2), border opacity (border-border/60 vs
* /70), and aria wiring (aria-labelledby was inconsistent).
* row + dotted divider + body padding — and drifted on padding,
* border opacity, and aria wiring.
*
* This wrapper locks the contract:
* v1.4.28 FB-F3 / F4 — heading scale collapsed to one shape across the
* `/medications/[id]` surface so the page reads as one consistent
* document. Title classes now match the settings symmetry sweep:
* `text-base font-semibold leading-6 tracking-tight`. Body band keeps
* its `text-xs` density; one heading scale (`text-base font-semibold`),
* one body scale (`text-sm`), one micro scale (`text-xs`) on the page.
*
* Contract:
* - `border-border/60 rounded-md border` chrome
* - `px-3 py-2.5` header row with `text-foreground/85 text-sm font-medium` title
* - `px-3 py-2.5` header row with
* `text-foreground text-base font-semibold leading-6 tracking-tight`
* title
* - `border-border/60 border-t px-3 py-3 text-xs` body band
* - `aria-labelledby` wired to a stable per-section id derived from `titleId`
*
@@ -57,7 +65,7 @@ export function MedicationDetailSection({
<header className="flex items-center justify-between gap-2 px-3 py-2.5">
<h2
id={titleId}
className="text-foreground/85 text-sm font-medium"
className="text-foreground text-base font-semibold leading-6 tracking-tight"
>
{title}
</h2>