mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(insights): unify the left reading edge across metric/score cards
Establish one inset system on the Insights surface. SubPageShell's flat px-6 header sat 8 px right of every card body on mobile — track the card slot inset (px-4 md:px-6) so the heading-vs-body step disappears. DeviceScoreTile and ResilienceTile hand-rolled a muted h-4 header row; route them through TileHeader size=sm (foreground icon, shared gutter), latest reading in the right slot. ScoreAnatomyView dropped its banned p-5 + centred uppercase title for a left-aligned TileHeader on p-4 md:p-6, so the ring hero stays a centred identity block while the header shares the reading edge with the stacked assessment card. SparklineDeltaTile swaps its hand-copied header row for the TileHeader primitive so it can't drift, and its contributor label rounds off the sub-xs size.
This commit is contained in:
1 parent
df78b29cfd
commit
de16da43fe
5 files changed
+84
-82
No files matched your search
@@ -10,6 +10,7 @@ import type {
|
||||
DerivedCoverage,
|
||||
DerivedProvenance,
|
||||
} from "@/lib/insights/derived/types";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { ScoreRing } from "./score-ring";
|
||||
import { CoverageMeter } from "./coverage-meter";
|
||||
import {
|
||||
@@ -126,20 +127,19 @@ export function ScoreAnatomyView({
|
||||
: undefined
|
||||
}
|
||||
className={cn(
|
||||
"flex flex-col gap-5 rounded-xl border p-5",
|
||||
"flex flex-col gap-4 rounded-xl border p-4 md:p-6",
|
||||
hue ? "wellness-detail-card" : "border-border bg-card",
|
||||
className,
|
||||
)}
|
||||
aria-label={typeof title === "string" ? title : undefined}
|
||||
>
|
||||
{/* Hero: title + ring + caption */}
|
||||
{/* Left-aligned canonical tile header — sits on the same reading edge as
|
||||
the sibling assessment card that stacks beneath it on a score
|
||||
sub-page, instead of the old centred uppercase title that floated
|
||||
toward the middle and shared no left line with any card. */}
|
||||
<TileHeader title={title} />
|
||||
{/* Hero: ring + caption — a centred identity block below the header. */}
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<h2
|
||||
data-slot="score-anatomy-title"
|
||||
className="text-foreground text-sm font-semibold tracking-wide uppercase"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{/* v1.15.12 F2 — vertical breathing room so the ring's bloom/glow is
|
||||
never clipped top/bottom by the hero container. */}
|
||||
<div className="py-2">
|
||||
@@ -176,7 +176,7 @@ export function ScoreAnatomyView({
|
||||
/>
|
||||
) : (
|
||||
<div data-slot="score-anatomy-contributors" className="space-y-3">
|
||||
<p className="text-muted-foreground text-[11px] font-semibold tracking-wide uppercase">
|
||||
<p className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
|
||||
{t("insights.derived.anatomy.contributorsLabel")}
|
||||
</p>
|
||||
<ul className="space-y-2.5">
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
sentimentColorClass,
|
||||
type TrendDirectionSentiment,
|
||||
} from "@/lib/insights/trend-sentiment";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
|
||||
/**
|
||||
* v1.10.0 — the Apple-Health-Highlights grid tile.
|
||||
@@ -138,27 +139,26 @@ export function SparklineDeltaTile({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* Header row matches the canonical TileHeader language: a leading
|
||||
foreground icon + a foreground `CardTitle`-weight heading — never
|
||||
the old muted-uppercase caption. Kept on one line for the dense
|
||||
grid; provenance trails to the right edge. */}
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Icon className="text-foreground h-5 w-5 shrink-0" aria-hidden="true" />
|
||||
<span
|
||||
className="text-foreground min-w-0 flex-1 truncate text-base leading-none font-semibold whitespace-nowrap"
|
||||
data-slot="sparkline-delta-tile-label"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
{provenance ? (
|
||||
<span
|
||||
data-slot="sparkline-delta-tile-provenance"
|
||||
className="ml-auto shrink-0"
|
||||
>
|
||||
{provenance}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{/* The canonical tile-header primitive (was a hand-rolled copy of it):
|
||||
leading foreground icon + foreground `CardTitle` heading, provenance
|
||||
in the `right` slot. Routing through the primitive keeps this dense
|
||||
grid tile from drifting out of the header contract. */}
|
||||
<TileHeader
|
||||
icon={Icon}
|
||||
title={label}
|
||||
className="min-w-0"
|
||||
titleClassName="min-w-0 truncate whitespace-nowrap"
|
||||
right={
|
||||
provenance ? (
|
||||
<span
|
||||
data-slot="sparkline-delta-tile-provenance"
|
||||
className="shrink-0"
|
||||
>
|
||||
{provenance}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="mt-2 flex flex-wrap items-baseline gap-x-1.5"
|
||||
|
||||
@@ -6,16 +6,11 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { useInsightsAnalytics } from "@/hooks/use-insights-analytics";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import type { DataSummary } from "@/lib/analytics/trends";
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { LearningGate } from "@/components/ui/learning-gate";
|
||||
import { HealthChartDynamicMini } from "@/components/charts/health-chart-dynamic";
|
||||
import { SectionHeading } from "@/components/insights/section-heading";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
|
||||
/**
|
||||
* v1.17.1 — calm, data-gated readout for a device-native score that has no
|
||||
@@ -100,26 +95,31 @@ export function DeviceScoreTile({
|
||||
className={className}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex min-w-0 items-center gap-2 text-sm">
|
||||
<Icon
|
||||
className="text-muted-foreground h-4 w-4 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="truncate">{title}</span>
|
||||
</CardTitle>
|
||||
{latest != null ? (
|
||||
<CardAction
|
||||
data-slot="device-score-latest"
|
||||
className="text-foreground self-baseline text-lg font-semibold tabular-nums"
|
||||
>
|
||||
{fmt(latest)}
|
||||
{unit ? (
|
||||
<span className="text-muted-foreground ml-1 text-xs font-normal">
|
||||
{unit}
|
||||
{/* Canonical compact tile header — foreground `h-4` icon + `text-sm`
|
||||
title on the shared inset; the latest reading rides the `right`
|
||||
slot so it stays pinned to the row's right edge. */}
|
||||
<TileHeader
|
||||
size="sm"
|
||||
icon={Icon}
|
||||
title={title}
|
||||
className="min-w-0"
|
||||
titleClassName="min-w-0 truncate"
|
||||
right={
|
||||
latest != null ? (
|
||||
<span
|
||||
data-slot="device-score-latest"
|
||||
className="text-foreground text-lg font-semibold tabular-nums"
|
||||
>
|
||||
{fmt(latest)}
|
||||
{unit ? (
|
||||
<span className="text-muted-foreground ml-1 text-xs font-normal">
|
||||
{unit}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</CardAction>
|
||||
) : null}
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{isLearning ? (
|
||||
|
||||
@@ -4,15 +4,10 @@ import { ShieldCheck } from "lucide-react";
|
||||
|
||||
import { useInsightsAnalytics } from "@/hooks/use-insights-analytics";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { LearningGate } from "@/components/ui/learning-gate";
|
||||
import { LearnMoreLink } from "@/components/ui/learn-more-link";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
|
||||
/**
|
||||
* v1.19.2 — calm readout for Oura's daily resilience level.
|
||||
@@ -96,21 +91,25 @@ export function ResilienceTile() {
|
||||
return (
|
||||
<Card data-slot="resilience-tile" data-metric="RESILIENCE">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex min-w-0 items-center gap-2 text-sm">
|
||||
<ShieldCheck
|
||||
className="text-muted-foreground h-4 w-4 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="truncate">{t("measurements.typeResilience")}</span>
|
||||
</CardTitle>
|
||||
{bandKey != null ? (
|
||||
<CardAction
|
||||
data-slot="resilience-band"
|
||||
className="text-foreground self-baseline text-lg font-semibold"
|
||||
>
|
||||
{t(bandKey)}
|
||||
</CardAction>
|
||||
) : null}
|
||||
{/* Canonical compact tile header — foreground `h-4` icon + `text-sm`
|
||||
title; the current band rides the `right` slot. */}
|
||||
<TileHeader
|
||||
size="sm"
|
||||
icon={ShieldCheck}
|
||||
title={t("measurements.typeResilience")}
|
||||
className="min-w-0"
|
||||
titleClassName="min-w-0 truncate"
|
||||
right={
|
||||
bandKey != null ? (
|
||||
<span
|
||||
data-slot="resilience-band"
|
||||
className="text-foreground text-lg font-semibold"
|
||||
>
|
||||
{t(bandKey)}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<p className="text-foreground text-sm leading-relaxed">
|
||||
|
||||
@@ -218,10 +218,13 @@ export function SubPageShell({
|
||||
<div data-slot="insights-subpage" className="space-y-3 md:space-y-4">
|
||||
{/* v1.8.7.1 — back-nav leads the page, above the heading. */}
|
||||
{backLink}
|
||||
{/* The heading + explainer indent to the cards' inner text edge
|
||||
(`px-6`, the Card content padding), so the page head reads as
|
||||
part of the card column instead of hanging outside it. */}
|
||||
<header className="space-y-1.5 px-6">
|
||||
{/* The heading + explainer indent to the cards' inner text edge so
|
||||
the page head reads as part of the card column instead of hanging
|
||||
outside it. Track the card slot inset EXACTLY (`px-4 md:px-6`) —
|
||||
never a flat `px-6`, which sat 8 px right of every card body on
|
||||
mobile and reopened the heading-vs-body step (insights left-edge
|
||||
rule: one reading edge across breakpoints). */}
|
||||
<header className="space-y-1.5 px-4 md:px-6">
|
||||
{/* v1.8.6 — the heading row pins the Coach icon top-right while the
|
||||
title / nudge / badge wrap together on the left.
|
||||
|
||||
|
||||
Reference in new issue
Block a user