mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
feat(ui): add shared ListRow primitive and adopt it for mini-cards
The bordered list-row "mini-card" appeared three times with three shapes: the Vorsorge dashboard row (`rounded-md p-2.5`), the daily- briefing rows (`rounded-md p-3`), and the measurement-list mobile row (bespoke `rounded-lg p-3` div). Introduce `ListRow` (`rounded-lg border p-3`, `asChild` per the shadcn convention) and migrate all three to it. The primitive owns only the box shape; each caller keeps its layout, colour, and interaction classes, so this is structural normalization with no content or behaviour change.
This commit is contained in:
1 parent
d2be72d57a
commit
54a2d19833
4 files changed
+64
-16
No files matched your search
@@ -22,6 +22,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { ListRow } from "@/components/ui/list-row";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { SectionHeading } from "@/components/insights/section-heading";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -230,28 +231,28 @@ function BriefingRow({
|
||||
);
|
||||
if (href) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
<ListRow
|
||||
asChild
|
||||
data-slot={dataSlot}
|
||||
data-metric={sourceMetric}
|
||||
className={cn(
|
||||
"border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3",
|
||||
"border-border/60 bg-card/40 relative flex items-start gap-3",
|
||||
"hover:bg-accent/40 transition-colors",
|
||||
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
|
||||
)}
|
||||
>
|
||||
{rowContent}
|
||||
</Link>
|
||||
<Link href={href}>{rowContent}</Link>
|
||||
</ListRow>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
<ListRow
|
||||
data-slot={dataSlot}
|
||||
data-metric={sourceMetric}
|
||||
className="border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3"
|
||||
className="border-border/60 bg-card/40 relative flex items-start gap-3"
|
||||
>
|
||||
{rowContent}
|
||||
</div>
|
||||
</ListRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { ListRow } from "@/components/ui/list-row";
|
||||
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { MeasurementForm } from "@/components/measurements/measurement-form";
|
||||
@@ -119,10 +120,12 @@ export function VorsorgeDashboardCard() {
|
||||
const due = relativeDueKey(reminder.nextDueAt, now);
|
||||
const isLinked = reminder.measurementType != null;
|
||||
return (
|
||||
<li
|
||||
<ListRow
|
||||
key={reminder.id}
|
||||
className="flex items-center justify-between gap-3 rounded-md border p-2.5"
|
||||
asChild
|
||||
className="flex items-center justify-between gap-3"
|
||||
>
|
||||
<li>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="truncate text-sm font-medium">
|
||||
{resolveLabel(reminder, t)}
|
||||
@@ -139,8 +142,8 @@ export function VorsorgeDashboardCard() {
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
size="default"
|
||||
className="min-h-11 shrink-0 sm:min-h-9"
|
||||
onClick={() => onPrimaryAction(reminder)}
|
||||
disabled={satisfy.isPending}
|
||||
>
|
||||
@@ -156,7 +159,8 @@ export function VorsorgeDashboardCard() {
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</li>
|
||||
</li>
|
||||
</ListRow>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { ListRow } from "@/components/ui/list-row";
|
||||
import {
|
||||
FilterBar,
|
||||
FilterBarDateRange,
|
||||
@@ -1080,10 +1081,10 @@ export function MeasurementList({
|
||||
const drilldownId = `drilldown-mobile-${m.dayKey ?? m.id}`;
|
||||
const isSelected = selectedIds.has(m.id);
|
||||
return (
|
||||
<div
|
||||
<ListRow
|
||||
key={m.id}
|
||||
data-state={isSelected ? "selected" : undefined}
|
||||
className="bg-card border-border data-[state=selected]:border-dracula-purple/60 data-[state=selected]:bg-dracula-purple/5 rounded-lg border p-3"
|
||||
className="bg-card border-border data-[state=selected]:border-dracula-purple/60 data-[state=selected]:bg-dracula-purple/5"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
@@ -1243,7 +1244,7 @@ export function MeasurementList({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ListRow>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "radix-ui";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* v1.18.7 — shared list-row primitive.
|
||||
*
|
||||
* Before this, every list-row "mini-card" (a bordered row sitting inside
|
||||
* a section or a `<Card>`) invented its own padding + radius: the
|
||||
* Vorsorge dashboard card used `rounded-md p-2.5`, the daily-briefing
|
||||
* rows `rounded-md p-3`, the measurement-list mobile row a bespoke
|
||||
* `rounded-lg p-3` div. Three surfaces, three shapes for one visual
|
||||
* concept. `ListRow` pins the canonical shape — `rounded-lg border p-3`
|
||||
* — so the rows read as one language across the app.
|
||||
*
|
||||
* It owns ONLY the box shape (radius + border + padding). Layout
|
||||
* (flex direction, alignment, gap), colour overrides, and interaction
|
||||
* state stay with the caller via `className`, so migrating a bespoke row
|
||||
* is purely dropping the radius/border/padding literals and keeping the
|
||||
* rest.
|
||||
*
|
||||
* `asChild` renders the row as its single child (e.g. a `<Link>` or
|
||||
* `<li>`) while still applying the shared classes, matching the shadcn
|
||||
* primitive convention used elsewhere in `ui/`.
|
||||
*/
|
||||
function ListRow({
|
||||
className,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "div";
|
||||
return (
|
||||
<Comp
|
||||
data-slot="list-row"
|
||||
className={cn("rounded-lg border p-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { ListRow };
|
||||
Reference in new issue
Block a user