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>
{/* Weeks */}
{weeks.map((week, wi) => (
<div key={wi} style={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
borderBottom: wi < weeks.length - 1 ? "1px solid var(--color-border-subtle)" : "none",
}}>
{week.map((cell, ci) => {
if (!cell.day || !cell.iso) {
return (
<div key={ci} style={{
minHeight: 96,
background: ci === 0 || ci === 6 ? "var(--color-weekend-header)" : "transparent",
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";
{/*
All day cells in ONE flat grid so every row shares the same
fixed height — no per-week wrapper divs that could produce
rows of different heights on mobile.
*/}
<div style={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
gridAutoRows: 76,
}}>
{cells.map((cell, idx) => {
const ci = idx % 7;
const isLastRow = idx >= cells.length - 7;
const borderBottom = !isLastRow ? "1px solid var(--color-border-subtle)" : "none";
const borderRight = ci < 6 ? "1px solid var(--color-border-subtle)" : "none";
if (!cell.day || !cell.iso) {
return (
<div key={ci} style={{
minHeight: 96,
padding: "10px 12px",
background: bg,
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 key={idx} style={{
background: ci === 0 || ci === 6 ? "var(--color-weekend-header)" : "transparent",
borderRight,
borderBottom,
}} />
);
})}
</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>
);
@@ -224,12 +229,13 @@ const navLinkStyle: React.CSSProperties = {
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
padding: "6px 14px",
borderRadius: 6,
padding: "10px 16px",
borderRadius: 8,
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
color: "var(--color-text-muted)",
fontSize: "1rem",
lineHeight: 1,
textDecoration: "none",
minWidth: 44,
};