Skip to content

fix(calendar): keep keyboard navigation alive after picking a date - #90

Merged
askides merged 1 commit into
mainfrom
fix/calendar-nested-components
Aug 5, 2026
Merged

fix(calendar): keep keyboard navigation alive after picking a date#90
askides merged 1 commit into
mainfrom
fix/calendar-nested-components

Conversation

@askides

@askides askides commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Started as "silence the ten oxlint warnings in calendar.tsx". Turned out
they were pointing at a real bug.

The bug

The four components overrides were arrow functions written inside
Calendar's render, which is where shadcn's generator puts them. That makes
them a new component type on every render, and DayPicker keys its subtree
on those identities — so any re-render unmounted and remounted the entire grid
rather than updating it.

CalendarDayButton holds a ref and an effect that focuses the day matching
modifiers.focused. The remount threw away the focus that effect had just
placed. Since selecting a start date re-renders the range picker, the arrow
keys stopped working after the first click.

Verified in a browser, both ways

Same steps against a running dashboard — open Custom, click Aug 2, press Right
twice:

Result
main focus ring stays on 2 — keyboard navigation dead
this branch focus ring moves 2 → 4

The fix

Root, Chevron and WeekNumber closed over nothing, so hoisting them to
module scope is a straight move. DayButton existed only to inject locale,
so it now reads the same value back out of DayPicker's own context
(useDayPicker().dayPickerProps.locale) rather than closing over Calendar's
prop — identical value, since Calendar is what passes it to DayPicker in
the first place.

Also

Clears the ten warnings the file emitted on every CI run — four
no-unstable-nested-components, six no-shadow. They were the symptom.

pnpm lint, pnpm typecheck, pnpm format:check clean; 626 tests pass. None
of the suites cover this component, which is why it was checked by hand.

The four `components` overrides were arrow functions written inside
`Calendar`'s render, which is where shadcn's generator puts them. That makes
them a new component type on every pass, and DayPicker keys its subtree on
those identities: any re-render unmounted and remounted the whole grid
instead of updating it.

`CalendarDayButton` holds a ref and an effect that focuses the day matching
`modifiers.focused`, so the remount threw away the focus that effect had just
placed. Selecting a start date re-renders the range picker, which meant that
after the first click the arrow keys did nothing at all. Confirmed against a
running dashboard — click Aug 2, press Right twice, and the focus ring stays
on 2. With the components hoisted it moves to 4, which is what it always
should have done.

Hoisting three of them is a straight move; they closed over nothing. The
DayButton wrapper existed only to inject `locale`, so it now reads the same
value back out of DayPicker's own context instead of closing over
`Calendar`'s prop.

This also clears the ten oxlint warnings the file was emitting on every CI
run, four no-unstable-nested-components and six no-shadow, but those were the
symptom rather than the reason.
@askides
askides merged commit fd3823f into main Aug 5, 2026
7 checks passed
@askides
askides deleted the fix/calendar-nested-components branch August 5, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant