Skip to content

feat(date-field): QuickBooks-style keyboard shortcuts#364

Open
interacsean wants to merge 9 commits into
mainfrom
claude/date-picker-shortcuts-pp-1372
Open

feat(date-field): QuickBooks-style keyboard shortcuts#364
interacsean wants to merge 9 commits into
mainfrom
claude/date-picker-shortcuts-pp-1372

Conversation

@interacsean

@interacsean interacsean commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What

Adds QuickBooks Online-style keyboard shortcuts to DateField / DatePicker. Fast-follow #1372 on the date-picker foundation (planning issue), which landed on main via #332.

Shortcuts

From a focused date segment and while the calendar popover is open (all case-insensitive):

Key Action
t Today
m / h Start / end of the entered month (current month when empty)
y / r Start / end of the year
w / k Start / end of the week (locale-aware)
- Previous day · = / + next day — both roll month and year boundaries over (+ needs no Shift)
/ Commit the current segment as-is and advance (a single 1 means January, not the start of 1x)
Alt+ Open the calendar popover (DatePicker)

A 1–2 digit year expands to the 2000s on blur (262026).

Video demo

date-keyboard-shortcuts.mp4

Invalid-date handling

  • minValue / maxValue — shortcut targets are clamped into range (field path and calendar path both), so a shortcut can never land out of bounds.
  • isDateUnavailable — in the field it behaves like typing (free entry; the consumer validates); in the open calendar an unavailable day can be highlighted but not confirmed.
  • With the popover open, shortcuts move the highlight like the arrow keys — Enter/click confirms.

Structure

The shortcut vocabulary (DateShortcut, DATE_SHORTCUT_KEYS, resolveDateShortcut) lives in a pure, React-free lib/date-shortcuts.ts that both the field and calendar engines import — no cross-component coupling, and the date math is unit-tested directly.

Testing

  • pnpm test — 1321 pass (incl. pure-resolver tests + field/calendar shortcut + clamp + popover-open coverage)
  • Type-check + lint clean
  • Manually verified in the vite-app /date-picker demo: today, month-start, min-clamp, and the popover-open highlight + Enter-to-confirm flow

🤖 Generated with Claude Code

interacsean and others added 3 commits July 4, 2026 10:44
Whole-date navigation from a focused date segment and while the calendar
popover is open (all case-insensitive):
- t today; m/h month start/end; y/r year start/end; w/k week start/end
- '-' / '=' / '+' step a day, rolling month and year boundaries over
- '/' advances to the next segment; Alt+down opens the calendar
- a 2-digit year expands to the 2000s on blur

Shortcut targets clamp to minValue/maxValue; in the open calendar they
move the highlight like the arrows (Enter confirms). The shared command
resolver lives in lib/date-shortcuts.ts so the field and calendar engines
stay decoupled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The main-merge combined the test's @internationalized/date import onto one
line; oxfmt wants it split. Fixes the failing fmt:check CI step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@interacsean interacsean marked this pull request as ready for review July 6, 2026 03:02
@interacsean interacsean requested a review from a team as a code owner July 6, 2026 03:02
Previously "26" → 2026 only ran on whole-group blur, but the calendar icon
lives inside the group — so tabbing year→icon left "26" showing until focus
escaped the field entirely. Fire the expansion when the year segment itself
loses focus (to a sibling, the icon, or outside). Idempotent, so the
commitOnBlur backstop (which also feeds the leap-year day-clamp) still runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
} else {
base = completeCalendarDate(fields) ?? ref;
}
let next = resolveDateShortcut(cmd, base, ref, locale);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never accepts a firstDayOfWeek option — so DatePicker passes firstDayOfWeek to useCalendarState (date-field.tsx:276) but not to useDateFieldState (date-field.tsx:248–263). resolveDateShortcut then falls through to startOfWeek(base, locale, undefined), i.e. the locale default.

i.e There's a <DatePicker locale="en-US" firstDayOfWeek="mon" />. Focus a segment and press w → field jumps to Sunday (en-US locale default). Open the calendar and press w → highlight jumps to Monday (use-calendar-state.ts:280, which does forward firstDayOfWeek).

DatePicker forwarded firstDayOfWeek to useCalendarState but not to
useDateFieldState, so `w`/`k` from a focused segment fell through to
resolveDateShortcut's locale default while the open-calendar path used the
prop — e.g. <DatePicker locale="en-US" firstDayOfWeek="mon" /> jumped the
field to Sunday but the calendar to Monday.

Thread firstDayOfWeek into useDateFieldState and pass it through to the
resolver. Centralize the FirstDayOfWeek type in lib/date-shortcuts.ts (the
resolver that consumes it) and re-export from use-calendar-state so existing
import sites are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@interacsean

Copy link
Copy Markdown
Contributor Author

Good catch — fixed in 1fd47f3. firstDayOfWeek now threads into useDateFieldState too, so the field path passes it to resolveDateShortcut instead of falling back to the locale default. Field and calendar w/k now agree.

Also moved the FirstDayOfWeek type down into lib/date-shortcuts.ts (where the resolver lives) and re-exported it from use-calendar-state, so import sites are unchanged. Added a regression test with your exact en-US + firstDayOfWeek="mon" case.

@IzumiSy

IzumiSy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

/review

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

API Design Review completed successfully!

API Design Review complete for PR #364. The prior High issue (firstDayOfWeek not forwarded to useDateFieldState in DatePicker) is resolved per commit 1fd47f3. Impact analysis confirms no breaking API changes and no consumer regressions. One Low suggestion posted inline (DateField missing firstDayOfWeek prop for w/k shortcuts). Verdict: Approve.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by API Design Review for issue #364 · 123.1 AIC · ⌖ 6.95 AIC · ⊞ 5.8K
Comment /review to run again

// Let the keyboard shortcuts clamp into range (the field has no calendar to
// enforce it otherwise).
minValue,
maxValue,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[1/1 — Low] DateField has w/k shortcuts but no firstDayOfWeek prop

DatePicker now correctly forwards firstDayOfWeek to both useDateFieldState and useCalendarState so the field and calendar agree (that was the fix in 1fd47f3). However DateField (no calendar) also gets the new w/k shortcuts but its public DateFieldProps doesn't include firstDayOfWeek — so the week-start in a standalone DateField is locked to the locale default.

The risk is lower than the field/calendar disagreement that was fixed (there's no paired calendar to conflict with), but a consumer embedding DateField in a non-default-week-start locale has no way to override it.

Suggested fix (optional, non-blocking): move firstDayOfWeek?: FirstDayOfWeek into DateBehaviorProps (or add it directly to DateFieldProps) and thread it through the useDateFieldState call the same way DatePicker now does.


Verdict: Approve
The previously raised High issue has been addressed. Only this Low-severity suggestion remains — it does not block merging.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@interacsean can you check this?

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.

3 participants