mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
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.
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import * as React from "react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* `<StatTile>` — the one label/value mini tile in the Admin console.
|
|
*
|
|
* Four hand-rolled variants of this box existed side by side: `StatusItem`
|
|
* (`bg-muted/50 p-3`), the encryption section's local `Stat`
|
|
* (`bg-muted/40 px-3 py-2`), the user-management inline panels
|
|
* (`bg-muted/80 p-4`), and the backups upload box (`bg-muted/30 p-3`). Four
|
|
* fills, three paddings, one role. This is the role.
|
|
*
|
|
* `icon` is optional — a tile with a glyph and one without still share the
|
|
* frame, so a grid can mix them without a step in tile height.
|
|
*/
|
|
export function StatTile({
|
|
icon: Icon,
|
|
label,
|
|
value,
|
|
valueClassName,
|
|
className,
|
|
}: {
|
|
icon?: React.ComponentType<{ className?: string }>;
|
|
label: React.ReactNode;
|
|
value: React.ReactNode;
|
|
/** Extra classes on the value line — a status colour, or `font-mono`. */
|
|
valueClassName?: string;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div
|
|
data-slot="stat-tile"
|
|
className={cn("bg-muted/50 space-y-1 rounded-lg p-3", className)}
|
|
>
|
|
<div className="text-muted-foreground flex items-center gap-1.5 text-xs">
|
|
{Icon ? <Icon className="size-3.5 shrink-0" /> : null}
|
|
{label}
|
|
</div>
|
|
<p className={cn("text-sm font-semibold", valueClassName)}>{value}</p>
|
|
</div>
|
|
);
|
|
}
|