fix: uniform cell heights in park month calendar
All checks were successful
Build and Deploy / Build & Push (push) Successful in 51s

Root cause: each week row was its own CSS Grid container, so rows
with open-day pills (hours + separate timezone line) grew taller than
closed rows, making the calendar column lines look staggered/slanted.

- Flatten all day cells into a single grid with gridAutoRows: 76
  so every row is exactly the same fixed height
- All cells get overflow: hidden so content can never push height
- Compact the status pill to a single line (hours + tz inline,
  truncated with ellipsis) — the stacked two-line pill was the
  primary height expander on narrow mobile columns
- Row/column border logic moves from week-wrapper divs to individual
  cell borderRight / borderBottom properties
- Nav link touch targets: padding 6×14 → 10×16, minWidth: 44px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Josh Wright
2026-04-05 09:20:36 -04:00
parent b276cc9948
commit a31dda4e9e

View File

@@ -118,103 +118,108 @@ export function ParkMonthCalendar({ parkId, year, month, monthData, today, timez
))} ))}
</div> </div>
{/* Weeks */} {/*
{weeks.map((week, wi) => ( All day cells in ONE flat grid so every row shares the same
<div key={wi} style={{ fixed height — no per-week wrapper divs that could produce
display: "grid", rows of different heights on mobile.
gridTemplateColumns: "repeat(7, 1fr)", */}
borderBottom: wi < weeks.length - 1 ? "1px solid var(--color-border-subtle)" : "none", <div style={{
}}> display: "grid",
{week.map((cell, ci) => { gridTemplateColumns: "repeat(7, 1fr)",
if (!cell.day || !cell.iso) { gridAutoRows: 76,
return ( }}>
<div key={ci} style={{ {cells.map((cell, idx) => {
minHeight: 96, const ci = idx % 7;
background: ci === 0 || ci === 6 ? "var(--color-weekend-header)" : "transparent", const isLastRow = idx >= cells.length - 7;
borderRight: ci < 6 ? "1px solid var(--color-border-subtle)" : "none", const borderBottom = !isLastRow ? "1px solid var(--color-border-subtle)" : "none";
}} /> const borderRight = ci < 6 ? "1px solid var(--color-border-subtle)" : "none";
);
}
const dayData = monthData[cell.iso];
const isToday = cell.iso === today;
const isWeekend = ci === 0 || ci === 6;
const isOpen = dayData?.isOpen && dayData?.hoursLabel;
const isPH = dayData?.specialType === "passholder_preview";
const bg = isToday
? "var(--color-today-bg)"
: isWeekend
? "var(--color-weekend-header)"
: "transparent";
if (!cell.day || !cell.iso) {
return ( return (
<div key={ci} style={{ <div key={idx} style={{
minHeight: 96, background: ci === 0 || ci === 6 ? "var(--color-weekend-header)" : "transparent",
padding: "10px 12px", borderRight,
background: bg, borderBottom,
borderRight: ci < 6 ? "1px solid var(--color-border-subtle)" : "none", }} />
borderLeft: isToday ? "2px solid var(--color-today-border)" : "none",
display: "flex",
flexDirection: "column",
gap: 5,
}}>
{/* Date number */}
<span style={{
fontSize: "0.95rem",
fontWeight: isToday ? 700 : isWeekend ? 600 : 400,
color: isToday
? "var(--color-today-text)"
: isWeekend
? "var(--color-text)"
: "var(--color-text-muted)",
lineHeight: 1,
}}>
{cell.day}
</span>
{/* Status */}
{!dayData ? (
<span style={{ fontSize: "0.75rem", color: "var(--color-text-dim)" }}></span>
) : isPH && isOpen ? (
<div style={{
background: "var(--color-ph-bg)",
border: "1px solid var(--color-ph-border)",
borderRadius: 5,
padding: "3px 6px",
}}>
<div style={{ fontSize: "0.6rem", fontWeight: 700, color: "var(--color-ph-label)", textTransform: "uppercase", letterSpacing: "0.05em" }}>
Passholder
</div>
<div style={{ fontSize: "0.65rem", color: "var(--color-ph-hours)", marginTop: 2 }}>
{dayData.hoursLabel}
</div>
<div style={{ fontSize: "0.58rem", color: "var(--color-ph-label)", opacity: 0.75, marginTop: 1, letterSpacing: "0.04em" }}>
{tzAbbr}
</div>
</div>
) : isOpen ? (
<div style={{
background: "var(--color-open-bg)",
border: "1px solid var(--color-open-border)",
borderRadius: 5,
padding: "3px 6px",
}}>
<div style={{ fontSize: "0.65rem", color: "var(--color-open-hours)" }}>
{dayData.hoursLabel}
</div>
<div style={{ fontSize: "0.58rem", color: "var(--color-open-hours)", opacity: 0.6, marginTop: 1, letterSpacing: "0.04em" }}>
{tzAbbr}
</div>
</div>
) : (
<span style={{ fontSize: "1rem", color: "var(--color-text-dim)", lineHeight: 1 }}>·</span>
)}
</div>
); );
})} }
</div>
))} const dayData = monthData[cell.iso];
const isToday = cell.iso === today;
const isWeekend = ci === 0 || ci === 6;
const isOpen = dayData?.isOpen && dayData?.hoursLabel;
const isPH = dayData?.specialType === "passholder_preview";
const bg = isToday
? "var(--color-today-bg)"
: isWeekend
? "var(--color-weekend-header)"
: "transparent";
return (
<div key={idx} style={{
padding: "8px 8px",
overflow: "hidden",
background: bg,
borderRight,
borderBottom,
borderLeft: isToday ? "2px solid var(--color-today-border)" : "none",
display: "flex",
flexDirection: "column",
gap: 4,
}}>
{/* Date number */}
<span style={{
fontSize: "0.88rem",
fontWeight: isToday ? 700 : isWeekend ? 600 : 400,
color: isToday
? "var(--color-today-text)"
: isWeekend
? "var(--color-text)"
: "var(--color-text-muted)",
lineHeight: 1,
flexShrink: 0,
}}>
{cell.day}
</span>
{/* Status — single-line pill so all cells stay uniform height */}
{!dayData ? (
<span style={{ fontSize: "0.75rem", color: "var(--color-text-dim)" }}></span>
) : isPH && isOpen ? (
<div style={{
background: "var(--color-ph-bg)",
border: "1px solid var(--color-ph-border)",
borderRadius: 4,
padding: "2px 4px",
overflow: "hidden",
}}>
<div style={{ fontSize: "0.58rem", fontWeight: 700, color: "var(--color-ph-label)", textTransform: "uppercase", letterSpacing: "0.04em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
Passholder
</div>
<div style={{ fontSize: "0.6rem", color: "var(--color-ph-hours)", marginTop: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{dayData.hoursLabel} <span style={{ opacity: 0.6 }}>{tzAbbr}</span>
</div>
</div>
) : isOpen ? (
<div style={{
background: "var(--color-open-bg)",
border: "1px solid var(--color-open-border)",
borderRadius: 4,
padding: "2px 4px",
overflow: "hidden",
}}>
<div style={{ fontSize: "0.6rem", color: "var(--color-open-hours)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{dayData.hoursLabel} <span style={{ opacity: 0.6 }}>{tzAbbr}</span>
</div>
</div>
) : (
<span style={{ fontSize: "1rem", color: "var(--color-text-dim)", lineHeight: 1 }}>·</span>
)}
</div>
);
})}
</div>
</div> </div>
</div> </div>
); );
@@ -224,12 +229,13 @@ const navLinkStyle: React.CSSProperties = {
display: "inline-flex", display: "inline-flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
padding: "6px 14px", padding: "10px 16px",
borderRadius: 6, borderRadius: 8,
border: "1px solid var(--color-border)", border: "1px solid var(--color-border)",
background: "var(--color-surface)", background: "var(--color-surface)",
color: "var(--color-text-muted)", color: "var(--color-text-muted)",
fontSize: "1rem", fontSize: "1rem",
lineHeight: 1, lineHeight: 1,
textDecoration: "none", textDecoration: "none",
minWidth: 44,
}; };