fix(dashboard): scope h-full to xl + tighter mobile sizing on KPIs

The Alerts rail and Reminders rail were using h-full unconditionally, which
worked at xl: where the dashboard grid pairs them with a sibling chart
column, but produced weirdly stretched empty cards in the single-column
mobile stack (no fixed-height context to fill).

  alert-rail / my-reminders-rail: h-full -> xl:h-full

KPI tiles + skeleton rendered the same desktop padding (p-5) and font sizes
on phone, leaving the value cramped against a wide white frame. Tighter
mobile defaults that scale up at sm:

  KPITile      p-3 sm:p-5, label text-[10px] sm:text-xs,
               value mt-1 text-lg sm:mt-2 sm:text-2xl, value truncates
  KpiTileSkeleton: matching p-3 sm:p-5 + smaller skeleton bars on mobile

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-03 16:15:20 +02:00
parent cf1c8b66db
commit a32f41b91d
4 changed files with 17 additions and 9 deletions

View File

@@ -22,7 +22,11 @@ export function AlertRail() {
<section <section
data-testid="alert-rail" data-testid="alert-rail"
aria-label="Active alerts" aria-label="Active alerts"
className="flex h-full flex-col gap-3" // `h-full` is intentional only at xl: where the parent dashboard grid
// gives this rail a sibling column whose height it should match. On
// mobile (single-column stack) there's no fixed-height context, so
// forcing 100% height makes the section overflow / look stretched.
className="flex flex-col gap-3 xl:h-full"
> >
<div className="flex items-baseline justify-between"> <div className="flex items-baseline justify-between">
<h2 className="text-sm font-semibold tracking-tight">Alerts</h2> <h2 className="text-sm font-semibold tracking-tight">Alerts</h2>

View File

@@ -28,11 +28,10 @@ function formatPercent(value: number): string {
function KpiTileSkeleton() { function KpiTileSkeleton() {
return ( return (
<div className="relative overflow-hidden rounded-xl border border-border bg-card p-5 shadow-sm"> <div className="relative overflow-hidden rounded-xl border border-border bg-card p-3 shadow-sm sm:p-5">
<div className="absolute inset-x-0 top-0 h-1 bg-muted" aria-hidden /> <div className="absolute inset-x-0 top-0 h-1 bg-muted" aria-hidden />
<Skeleton className="h-3 w-24" /> <Skeleton className="h-3 w-20" />
<Skeleton className="mt-3 h-7 w-32" /> <Skeleton className="mt-2 h-6 w-24 sm:mt-3 sm:h-7" />
<Skeleton className="mt-2 h-3 w-12" />
</div> </div>
); );
} }

View File

@@ -67,8 +67,11 @@ export function MyRemindersRail() {
return `/${portSlug}/reminders`; return `/${portSlug}/reminders`;
} }
// `h-full` only at xl: where the dashboard grid pairs this rail with
// a sibling chart column. On mobile (stacked) it produced a weirdly
// tall empty card.
return ( return (
<Card className="h-full"> <Card className="xl:h-full">
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-3"> <CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-3">
<div className="space-y-0.5"> <div className="space-y-0.5">
<CardTitle className="flex items-center gap-1.5 text-base"> <CardTitle className="flex items-center gap-1.5 text-base">

View File

@@ -45,7 +45,7 @@ export function KPITile({
<div <div
data-testid="kpi-tile" data-testid="kpi-tile"
className={cn( className={cn(
'group relative overflow-hidden rounded-xl border border-border bg-gradient-brand-soft p-5 shadow-sm transition-all duration-base ease-smooth hover:shadow-md', 'group relative overflow-hidden rounded-xl border border-border bg-gradient-brand-soft p-3 shadow-sm transition-all duration-base ease-smooth hover:shadow-md sm:p-5',
className, className,
)} )}
{...props} {...props}
@@ -53,10 +53,12 @@ export function KPITile({
<div className={cn('absolute inset-x-0 top-0 h-1', ACCENT_STRIPES[accent])} aria-hidden /> <div className={cn('absolute inset-x-0 top-0 h-1', ACCENT_STRIPES[accent])} aria-hidden />
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
<div className="min-w-0"> <div className="min-w-0">
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground"> <div className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs">
{title} {title}
</div> </div>
<div className="mt-2 text-2xl font-semibold tabular-nums text-foreground">{value}</div> <div className="mt-1 truncate text-lg font-semibold tabular-nums text-foreground sm:mt-2 sm:text-2xl">
{value}
</div>
{typeof delta === 'number' ? ( {typeof delta === 'number' ? (
<div className={cn('mt-1 text-xs font-medium', deltaClass)}> <div className={cn('mt-1 text-xs font-medium', deltaClass)}>
{deltaPrefix} {deltaPrefix}