mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
refactor(ui): give Settings and Admin one card rhythm and one page header
The two consoles used the same primitives and still read as two systems, because everything without a primitive had drifted: the header-to-body step existed in three competing techniques at once (`mt-4` on the first body child, `space-y-4` on the card, `mb-4` on the header), the body started either at the card edge or 28 px in, and both shells hand-rolled the page title instead of calling `PageHeader`. Move the decisions into the primitives so a call site cannot re-open them: - `SettingsCard` is gap-based like `Card` (`flex flex-col gap-4`). The header-to-body step is the container's, not a margin somebody remembers. A dense card still overrides the gap on the card itself. - The body starts at the card edge. `pl-7` is gone — off the spacing scale, and it mis-aligned action rows against their own forms wherever two neighbouring cards disagreed about it. - The card header's description slot loses its `space-y-1`. That was the licence call sites used to stack explainer paragraphs into a muted `text-xs` slot; the slot holds one sentence. - New `SettingsCardActions`: one action row, trailing edge, last thing in the card. `align="start"` spells the one exception, a card whose only actions are links out. - New `StatTile` replaces four hand-rolled label/value boxes in Admin. - `SectionHeading` moves from `insights/` to `ui/`. It is the heading above a group of cards, which is what Settings needs; Settings had been using a card header with no card, which reads as a card that failed to paint. Both shells now render their heading through `PageHeader`, which gained `headingAs="div"` for the second copy each shell paints (mobile above the nav strip, desktop inside the grid) so only one is a real `<h1>`. The page explainer is muted everywhere: the primitive said foreground, the standards and every hand-rolled copy said muted, so pages disagreed with each other. Medications, Vorsorge, the labs / custom-metric / illness detail pages and the clinician view drop their hand-rolled title blocks too, including the mid-word truncation on the medications subtitle. Tests pin the parts that can be checked exactly: the card's gap, the description slot's missing rhythm, the action row's two alignments, the `<h1>`-free shells, and a sweep asserting `pl-7` stays gone.
This commit is contained in:
1 parent
00e8d05235
commit
5445bfc1f1
121 files changed
+832
-620
No files matched your search
@@ -16,6 +16,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { PageHeader } from "@/components/ui/page-header";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
|
||||
@@ -67,13 +68,11 @@ export function IllnessEpisodeDetail({ episodeId }: { episodeId: string }) {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h1 className="truncate text-2xl font-bold tracking-tight">
|
||||
{episode?.label ?? <Skeleton className="h-7 w-40" />}
|
||||
</h1>
|
||||
{episode ? (
|
||||
<div className="mt-1.5 flex flex-wrap items-center gap-2">
|
||||
<PageHeader
|
||||
title={episode?.label ?? <Skeleton className="h-7 w-40" />}
|
||||
description={
|
||||
episode ? (
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary">
|
||||
{t(`illness.type.${episode.type}`)}
|
||||
</Badge>
|
||||
@@ -94,32 +93,34 @@ export function IllnessEpisodeDetail({ episodeId }: { episodeId: string }) {
|
||||
})}`
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{canManage && (
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<Button
|
||||
onClick={() => setLogOpen(true)}
|
||||
className="min-h-11 sm:min-h-9"
|
||||
>
|
||||
{t("illness.logDay")}
|
||||
</Button>
|
||||
{episode ? (
|
||||
<EpisodeMenu
|
||||
episode={episode}
|
||||
onEdit={() => setEditOpen(true)}
|
||||
onResolve={
|
||||
active && !isChronic
|
||||
? () => resolve.mutate(episode.id)
|
||||
: undefined
|
||||
}
|
||||
resolving={resolve.isPending}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
actions={
|
||||
canManage ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setLogOpen(true)}
|
||||
className="min-h-11 sm:min-h-9"
|
||||
>
|
||||
{t("illness.logDay")}
|
||||
</Button>
|
||||
{episode ? (
|
||||
<EpisodeMenu
|
||||
episode={episode}
|
||||
onEdit={() => setEditOpen(true)}
|
||||
onResolve={
|
||||
active && !isChronic
|
||||
? () => resolve.mutate(episode.id)
|
||||
: undefined
|
||||
}
|
||||
resolving={resolve.isPending}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{episode?.note ? (
|
||||
<Card>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function IllnessSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<SettingsCard id="illness-view" className="scroll-mt-28 space-y-4">
|
||||
<SettingsCard id="illness-view" className="scroll-mt-28">
|
||||
<SettingsCardHeader
|
||||
icon={Eye}
|
||||
title={t("moduleList.viewHeading")}
|
||||
@@ -57,7 +57,7 @@ export function IllnessSettings() {
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard id="illness-order" className="scroll-mt-28 space-y-4">
|
||||
<SettingsCard id="illness-order" className="scroll-mt-28">
|
||||
<SettingsCardHeader
|
||||
icon={ListOrdered}
|
||||
title={t("moduleList.reorder.heading")}
|
||||
|
||||
Reference in new issue
Block a user