fix: address second Copilot review round on Calendar app

- MonthGrid: focusKey preferred selectedDate even when it fell outside
  the currently rendered grid (selectedDate survives month navigation
  so the agenda keeps showing it, so after Prev/Next/PageUp/PageDown
  it commonly points at a day no longer on screen) — every cell got
  tabIndex=-1 again, reopening the keyboard trap fixed last round.
  Now only trusted when it's actually one of the rendered cells.
- MonthGrid: the weekday header row sat as a sibling before the
  role="grid" element instead of inside it, so assistive tech
  couldn't associate the columnheaders with the grid. Grid role now
  wraps both the header row and the week rows.
- MonthGrid: the loading skeleton always rendered 35 cells (5 weeks)
  even for 6-week months, causing a layout jump once the real 42-cell
  grid replaced it. Now sized from the same buildWeeks() count used
  for the real grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto
This commit is contained in:
2026-09-06 21:44:29 +02:00
parent 84a9781a03
commit bbc0fbd102

View File

@@ -49,11 +49,19 @@ export function MonthGrid({
const cellRefs = useRef(new Map<string, HTMLButtonElement>());
// Exactly one cell must be tab-focusable, or a keyboard user who tabs away
// and back can never re-enter the grid. Prefer the selected day, then
// today if it's in the displayed month, and only otherwise fall back to
// the 1st — e.g. after PageUp/PageDown lands on a month with neither.
// and back can never re-enter the grid. Prefer the selected day — but only
// when it's actually one of the rendered cells: `selectedDate` survives
// month navigation (so the agenda can keep showing it), so after Prev/Next/
// PageUp/PageDown it commonly points outside the newly displayed grid.
// Otherwise prefer today if it's in the displayed month, and only
// otherwise fall back to the 1st.
const selectedInView = selectedDate ? weeks.some((week) => week.some((date) => localDateKey(date) === selectedDate)) : false;
const isTodayInMonth = today.getFullYear() === monthAnchor.getFullYear() && today.getMonth() === monthIndex;
const focusKey = selectedDate ?? (isTodayInMonth ? todayKey : localDateKey(new Date(monthAnchor.getFullYear(), monthIndex, 1)));
const focusKey = selectedInView
? selectedDate!
: isTodayInMonth
? todayKey
: localDateKey(new Date(monthAnchor.getFullYear(), monthIndex, 1));
const focusDate = (date: Date) => {
const key = localDateKey(date);
@@ -101,21 +109,20 @@ export function MonthGrid({
};
return (
<div className="flex h-full min-h-0 flex-col" aria-hidden={isLoading || undefined}>
<div className="grid grid-cols-7 border-b border-border text-center text-[11px] font-medium text-muted-foreground" role="row">
<div role="grid" aria-label="Month" className="flex h-full min-h-0 flex-col" aria-hidden={isLoading || undefined}>
<div
role="row"
className="grid grid-cols-7 border-b border-border text-center text-[11px] font-medium text-muted-foreground"
>
{WEEKDAY_LABELS.map((label) => (
<div key={label} role="columnheader" className="py-1.5" aria-label={label}>
<span aria-hidden>{label}</span>
</div>
))}
</div>
<div
role="grid"
aria-label="Month"
className="grid flex-1 auto-rows-fr grid-cols-7 divide-x divide-y divide-border"
>
<div className="grid flex-1 auto-rows-fr grid-cols-7 divide-x divide-y divide-border">
{isLoading
? Array.from({ length: 35 }).map((_, index) => (
? Array.from({ length: weeks.length * 7 }).map((_, index) => (
<div key={index} className="p-1.5">
<Skeleton className="h-full w-full" />
</div>