mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(ui): elevate the capture FAB and unify selectable-control and export-card styling
Lift the bottom-bar center capture action into a proper floating-action button: a 56px target raised out of the bar with a card collar ring and a stronger shadow, so it reads as the primary CTA rather than a fifth flush tab. Desktop sidebar nav is unaffected. Unify the selectable controls across the mood logging surface onto one design language: the 5 mood faces, the tag tiles, the rated-factor steps and the GLP-1 chips now share a single selected-state treatment (border-primary, bg-primary/15, primary text, single border) and a consistent rounding family. Match the More-hub row height to the capture tiles so equivalent tappable rows share one min-height at the same tier. Give the demoted doctor-report export card a primary-tinted icon to match its peer cards — the demotion comes from position and size, not a muted icon. Drop the doubled heading atop the mood sheet by demoting the hero question to a field label so the sheet title is the single primary heading. Reword the capture mood option description to describe the action instead of echoing the hero question. (cherry picked from commit 1296f151f1c9a7c95ff917e3d69a5c0d369ba210)
This commit is contained in:
1 parent
61573301c3
commit
6657e3da6f
11 files changed
+54
-19
No files matched your search
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Medikament",
|
||||
"medicationDescription": "Eine Dosis als genommen markieren",
|
||||
"mood": "Stimmung",
|
||||
"moodDescription": "Wie geht es dir?"
|
||||
"moodDescription": "Stimmung und Tags erfassen"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Medication",
|
||||
"medicationDescription": "Mark a dose as taken",
|
||||
"mood": "Mood",
|
||||
"moodDescription": "How are you feeling?"
|
||||
"moodDescription": "Log your mood and tags"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Medicamento",
|
||||
"medicationDescription": "Marcar una dosis como tomada",
|
||||
"mood": "Estado de ánimo",
|
||||
"moodDescription": "¿Cómo te sientes?"
|
||||
"moodDescription": "Registrar estado de ánimo y etiquetas"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Médicament",
|
||||
"medicationDescription": "Marquer une dose comme prise",
|
||||
"mood": "Humeur",
|
||||
"moodDescription": "Comment vous sentez-vous ?"
|
||||
"moodDescription": "Enregistrer l'humeur et les tags"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Farmaco",
|
||||
"medicationDescription": "Segna una dose come assunta",
|
||||
"mood": "Umore",
|
||||
"moodDescription": "Come ti senti?"
|
||||
"moodDescription": "Registra umore e tag"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
"medication": "Lek",
|
||||
"medicationDescription": "Oznacz dawkę jako przyjętą",
|
||||
"mood": "Nastrój",
|
||||
"moodDescription": "Jak się czujesz?"
|
||||
"moodDescription": "Zapisz nastrój i tagi"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@@ -75,11 +75,24 @@ describe("<BottomNav> iOS-parity layout", () => {
|
||||
|
||||
it("each strip entry meets the 44 px tap-target floor (min-h-11 min-w-11)", () => {
|
||||
const html = render();
|
||||
// Five flanking strip slots (Home, Meds, capture button, Insights,
|
||||
// More) all carry `min-h-11 min-w-11`. We don't hard-pin the count
|
||||
// because a future entry would also satisfy the contract.
|
||||
// The four flanking strip slots (Home, Meds, Insights, More) carry
|
||||
// `min-h-11 min-w-11`. We don't hard-pin the count because a future
|
||||
// entry would also satisfy the contract.
|
||||
const matches = html.match(/min-h-11 min-w-11/g) ?? [];
|
||||
expect(matches.length).toBeGreaterThanOrEqual(5);
|
||||
expect(matches.length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
it("the center capture FAB is a larger, elevated tap target", () => {
|
||||
const html = render();
|
||||
// The FAB clears the 44px floor with a 56px (h-14 w-14) target and
|
||||
// is lifted out of the bar (negative top margin + card ring + raised
|
||||
// shadow) so it reads as the primary CTA, not a flush fifth tab.
|
||||
const capture = html.match(/<button[^>]*bottom-nav-capture[^>]*>/);
|
||||
expect(capture).not.toBeNull();
|
||||
expect(capture![0]).toMatch(/h-14 w-14/);
|
||||
expect(capture![0]).toMatch(/-mt-5/);
|
||||
expect(capture![0]).toMatch(/ring-4/);
|
||||
expect(capture![0]).toMatch(/shadow-lg/);
|
||||
});
|
||||
|
||||
it("no longer lists the deprecated /targets entry (v1.8.6)", () => {
|
||||
|
||||
@@ -128,8 +128,13 @@ export function BottomNav() {
|
||||
{PRIMARY_LEFT.map(renderPrimary)}
|
||||
|
||||
{/* Center capture action — a labeled button (NOT a link) that
|
||||
opens the capture picker. Visually elevated so it reads as
|
||||
the primary CTA. */}
|
||||
opens the capture picker. Elevated as a Floating-Action-
|
||||
Button: it sits larger than the flanking tabs (h-14 = 56px,
|
||||
well above the 44px tap-target floor), lifts above the bar
|
||||
with a negative top margin, and carries a filled-primary
|
||||
collar (a `bg-card` ring that punches it out of the bar) plus
|
||||
a stronger shadow so it reads as the bar's primary CTA rather
|
||||
than a fifth flush tab. */}
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
@@ -138,9 +143,9 @@ export function BottomNav() {
|
||||
aria-expanded={captureOpen}
|
||||
onClick={() => setCaptureOpen(true)}
|
||||
data-testid="bottom-nav-capture"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring/50 flex min-h-11 min-w-11 items-center justify-center rounded-full shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring/70 ring-card -mt-5 flex h-14 w-14 items-center justify-center rounded-full shadow-lg ring-4 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||
>
|
||||
<Plus className="h-6 w-6" />
|
||||
<Plus className="h-7 w-7" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -193,8 +198,11 @@ export function BottomNav() {
|
||||
href={item.href}
|
||||
onClick={() => setMoreOpen(false)}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
// v1.12 — `min-h-14` matches the capture-picker tiles so
|
||||
// the equivalent tappable rows share one height at this
|
||||
// tier (and the larger row is a more comfortable target).
|
||||
className={cn(
|
||||
"border-border flex min-h-11 items-center gap-3 rounded-lg border px-4 py-3 transition-colors",
|
||||
"border-border flex min-h-14 items-center gap-3 rounded-lg border px-4 py-3 transition-colors",
|
||||
isActive
|
||||
? "text-primary bg-primary/5"
|
||||
: "text-foreground hover:bg-accent/40",
|
||||
|
||||
@@ -275,7 +275,14 @@ export function MoodForm({ onSuccess, onCancel, footerSlot }: MoodFormProps) {
|
||||
radiogroup (best-on-the-left order); tapping one picks the mood
|
||||
and reveals the annotate panel below. */}
|
||||
<div className="space-y-2" data-slot="mood-face-hero">
|
||||
<Label id="mood-level-label" className="text-base">
|
||||
{/* v1.12 — the sheet title ("Add") is the single primary heading;
|
||||
the hero question stays as the face row's field label rather
|
||||
than a second heading-weight line, so the sheet header isn't a
|
||||
doubled heading. */}
|
||||
<Label
|
||||
id="mood-level-label"
|
||||
className="text-muted-foreground text-sm font-normal"
|
||||
>
|
||||
{t("mood.heroQuestion")}
|
||||
</Label>
|
||||
<div
|
||||
@@ -297,9 +304,13 @@ export function MoodForm({ onSuccess, onCancel, footerSlot }: MoodFormProps) {
|
||||
data-mood={level.value}
|
||||
onClick={() => setMood(level.value)}
|
||||
{...getMoodRadioProps(index)}
|
||||
// v1.12 — selected-state unified with the tag tiles and
|
||||
// factor steps: `border-primary bg-primary/15 text-primary`
|
||||
// on a single 1px border (no `border-2` jump) so the
|
||||
// selectable controls read as one design language.
|
||||
className={`flex flex-col items-center gap-1.5 rounded-xl border p-2 text-center transition-colors sm:p-3 ${
|
||||
isSelected
|
||||
? "border-primary bg-primary/10 text-primary border-2"
|
||||
? "border-primary bg-primary/15 text-primary"
|
||||
: "border-border text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -201,7 +201,10 @@ export function MoodTagPicker({
|
||||
onClick={() =>
|
||||
onRateFactor(tag.key, isActive ? null : value)
|
||||
}
|
||||
className={`flex h-8 items-center justify-center rounded-md border px-2 text-xs tabular-nums transition-colors ${
|
||||
// v1.12 — `rounded-lg` aligns the step
|
||||
// radius with the tag tiles and mood faces;
|
||||
// selected-state matches them too.
|
||||
className={`flex h-8 items-center justify-center rounded-lg border px-2 text-xs tabular-nums transition-colors ${
|
||||
isBinaryScale ? "min-w-12" : "min-w-8"
|
||||
} ${
|
||||
isActive
|
||||
|
||||
@@ -112,7 +112,7 @@ export function ArztberichtHeroCard() {
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Stethoscope
|
||||
className="text-muted-foreground h-5 w-5 shrink-0"
|
||||
className="text-primary h-5 w-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h2
|
||||
|
||||
Reference in new issue
Block a user