fix(day): head the since-last-visit card with TileHeader at the compact density

The card hand-built an uppercase h3 and ran a density of its own. It
now takes TileHeader size="sm" (which gains an h3 heading for a card
under a section that owns the h2) and the compact card rhythm.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 04:23:32 +02:00
1 parent b7e48e933c
commit ad9c9cd0cd
3 files changed
+42 -20

No files matched your search

+18 -14
View File
@@ -17,7 +17,8 @@ import {
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { QueryErrorRow } from "@/components/ui/query-error-row";
import type { EncounterDTO } from "@/lib/encounters/dto";
import { apiGet } from "@/lib/api/api-fetch";
@@ -35,7 +36,7 @@ import {
import { getUnitForType } from "@/lib/measurements/unit-map";
import { queryKeys } from "@/lib/query-keys";
import { DAY_SECTION_LABEL, dayTitle, useNotableText } from "./day-sections";
import { dayTitle, useNotableText } from "./day-sections";
import { DayLink } from "./day-link";
import { dateKeyOfInstant } from "./day-url";
import { tileKeyOf } from "./day-values-model";
@@ -212,24 +213,27 @@ export function SinceLastVisit({
const nextDay = dateKeyOfInstant(next.occurredAt, timeZone);
return (
<Card className="gap-3 py-3 md:gap-3 md:py-4">
<Card className="gap-2 py-3 md:py-4">
<CardHeader>
<TileHeader
size="sm"
icon={Stethoscope}
id={`since-last-visit-${next.id}`}
titleAs="h3"
title={t("day.sinceLastVisit.title", { date: lastLabel })}
right={
<span className="text-muted-foreground text-xs">
{tCount("day.sinceLastVisit.count", rows.length)}
</span>
}
/>
</CardHeader>
<CardContent>
<section
data-slot="since-last-visit"
aria-labelledby={`since-last-visit-${next.id}`}
className="space-y-2"
>
<div className="flex items-baseline justify-between gap-3">
<h3
id={`since-last-visit-${next.id}`}
className={DAY_SECTION_LABEL}
>
{t("day.sinceLastVisit.title", { date: lastLabel })}
</h3>
<span className="text-muted-foreground shrink-0 text-xs">
{tCount("day.sinceLastVisit.count", rows.length)}
</span>
</div>
<ul className="border-border divide-border divide-y rounded-lg border px-3.5">
{visible.map((row) => (
<li
@@ -297,5 +297,20 @@ describe("<VisitsSection> since the last visit", () => {
expect(html).toContain('data-slot="since-last-visit"');
expect(html).toContain('data-slot="since-last-visit-row"');
expect(upcomingCards(html)).toBe(upcomingCards(baseline) + 1);
// The card heads like every compact card: TileHeader at the small size,
// a real heading under the section's own, at the compact density.
const card = html.slice(
html.lastIndexOf(
'<div data-slot="card"',
html.indexOf("since-last-visit-"),
),
);
expect(card).toMatch(
/^<div data-slot="card" class="[^"]*gap-2 py-3 md:py-4/,
);
expect(card).toContain('data-slot="tile-header"');
expect(card).toMatch(
/<h3 id="since-last-visit-[^"]+" data-slot="card-title" class="[^"]*text-sm/,
);
});
});
+9 -6
View File
@@ -58,9 +58,11 @@ interface TileHeaderProps {
* owning block (the recovery blocks) or a grid of sibling tiles (dashboard
* tiles, insights overview grids) both nest correctly as `h1 → h2, h2, …`;
* just don't nest a `titleAs="h2"` tile inside another heading's own
* subsection. Default (unset) keeps every other tile a plain CardTitle.
* subsection. `"h3"` is the same for a card that sits inside a section
* which already owns an `h2` (the since-last-visit card under the visits
* section). Default (unset) keeps every other tile a plain CardTitle.
*/
titleAs?: "h2";
titleAs?: "h2" | "h3";
}
export function TileHeader({
@@ -73,6 +75,7 @@ export function TileHeader({
id,
titleAs,
}: TileHeaderProps) {
const Heading = titleAs ?? "h2";
const titleClassNames = cn(
size === "sm" ? "text-sm" : "text-base",
titleClassName,
@@ -93,16 +96,16 @@ export function TileHeader({
aria-hidden="true"
/>
) : null}
{titleAs === "h2" ? (
{titleAs ? (
// Heading semantics with the exact CardTitle visual treatment so the
// tile can carry a real `<h2>` where it owns the subpage's heading.
<h2
// tile can carry a real heading where it owns one.
<Heading
id={id}
data-slot="card-title"
className={cn("leading-none font-semibold", titleClassNames)}
>
{title}
</h2>
</Heading>
) : (
<CardTitle id={id} className={titleClassNames}>
{title}