Repository navigation
feat(react-headless-components-preview): add headless DatePicker - #36988
Draft
Dmytro Kirpa (dmytrokirpa) wants to merge 4 commits into
Draft
Dmytro Kirpa (dmytrokirpa) wants to merge 4 commits into
Dmytro Kirpa (dmytrokirpa) wants to merge 4 commits into
Conversation
Add a headless DatePicker that composes the headless Calendar in a non-modal popover="manual" dialog anchored to a button or text-input field. It owns the committed value, typed-date parsing and validation, popup lifecycle and focus management, and exposes data-open, data-disabled, data-invalid, data-editable and data-placeholder. Calendar: emit aria-current="date" on current month/year cells and add CalendarMonthHandle.showYearPicker() so the day heading can open the year-range grid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📊 Bundle size reportUnchanged fixtures
|
|
Pull request demo site: URL |
Dmytro Kirpa (dmytrokirpa)
marked this pull request as draft
October 23, 2026 17:12
… naming and controlled close requests correct - Cancel and stop the Escape that closes the calendar so a surrounding dialog or popover stays open. - Name the trigger with aria-labelledby inside a Field so the displayed date stays in its accessible name. - Guard only re-entrant open requests, so a controlled consumer that ignores a close request still receives later ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with roving grid navigation Native focusgroup grid support is only proposed in Chromium and the polyfill's grid support is unreleased, so Calendar grids had no arrow navigation and every available day was a tab stop. - Day, month and year grids keep a single roving tab stop that remembers the last focused cell. - Arrows, Home and End move focus and skip unavailable cells; day arrows wrap across weeks, load adjacent months and page past the visible days; month/year arrows stay within rows. - Remove focusgroup attributes and their prop types, enable the previously skipped keyboard suites, and add a DatePicker keyboard browser test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ith real Tab presses With one roving tab stop per Calendar grid, the browser test can tab through each grid and control and verify that leaving the calendar closes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Public control typing, popup initialization, and Calendar focus/navigation behavior have unresolved correctness defects.
6 open issues
Keep seven-day stride during vertical navigation fallback · New Focus the visible year picker when reopening it · New Correct DatePicker ref and event types for button controls · New Initialize focus and displayed date on all opening transitions · New Preserve overlay transitions when opening the month picker · New Preserve years below 100 when updating displayed dates · New
What changed in this PR
Adds an unstyled DatePicker that composes the headless Calendar, replacing the earlier modal approach with a non-modal popup. It also replaces Calendar’s unreleased focusgroup dependency with built-in keyboard navigation.
Changes:
- Adds DatePicker controls, date parsing and validation, popup positioning, and focus management.
- Introduces roving tab stops and arrow navigation across Calendar grids.
- Adds public exports, API reports, stories, unit/browser coverage, bundle fixtures, and release metadata.
Paths below are relative to packages/react-components/react-headless-components-preview/, except the root-level change file.
| File | Description |
|---|---|
stories/src/DatePicker/index.stories.tsx |
Registers DatePicker stories and documentation. |
stories/src/DatePicker/DatePickerDisabled.stories.tsx |
Demonstrates disabled state. |
stories/src/DatePicker/DatePickerDescription.md |
Documents composition, behavior, and accessibility. |
stories/src/DatePicker/DatePickerDefault.stories.tsx |
Demonstrates controlled date selection. |
stories/src/DatePicker/DatePickerDateBoundaries.stories.tsx |
Demonstrates date restrictions and required input. |
stories/src/DatePicker/datePickerAppearance.ts |
Shares story slot styling and icons. |
stories/src/DatePicker/DatePickerAllowTextInput.stories.tsx |
Demonstrates editable date input. |
stories/src/DatePicker/date-picker.module.css |
Styles examples, including contrast and reduced-motion modes. |
stories/src/Calendar/CalendarDescription.md |
Documents built-in grid navigation. |
library/src/date-picker.ts |
Exposes the DatePicker subpath API. |
library/src/components/DatePicker/useDatePickerDateState.ts |
Manages committed dates, draft text, and validation. |
library/src/components/DatePicker/useDatePicker.tsx |
Implements controls, popup lifecycle, and Calendar composition. |
library/src/components/DatePicker/renderDatePicker.tsx |
Renders controls, errors, and conditional popup. |
library/src/components/DatePicker/index.ts |
Exports DatePicker implementation and types. |
library/src/components/DatePicker/datePickerFormatting.ts |
Adds default date formatting and parsing. |
library/src/components/DatePicker/datePickerFormatting.test.ts |
Covers supported formats and invalid dates. |
library/src/components/DatePicker/DatePicker.types.ts |
Defines public props, slots, state, and events. |
library/src/components/DatePicker/DatePicker.tsx |
Composes the public component. |
library/src/components/DatePicker/DatePicker.test.tsx |
Covers selection, validation, focus, and accessibility semantics. |
library/src/components/DatePicker/DatePicker.cy.tsx |
Adds browser interaction and popup tests. |
library/src/components/Calendar/hooks/useCalendarGrid.ts |
Implements shared roving focus and navigation. |
library/src/components/Calendar/hooks/useCalendarGrid.test.ts |
Tests navigation targets and tab-stop properties. |
library/src/components/Calendar/CalendarYear/useCalendarYear.tsx |
Integrates year-grid roving focus. |
library/src/components/Calendar/CalendarYear/CalendarYear.types.ts |
Removes focusgroup-specific props and updates documentation. |
library/src/components/Calendar/CalendarYear/CalendarYear.test.tsx |
Enables and updates year navigation tests. |
library/src/components/Calendar/CalendarMonth/useCalendarMonth.tsx |
Adds roving focus and imperative year-picker opening. |
library/src/components/Calendar/CalendarMonth/CalendarMonth.types.ts |
Updates navigation types and imperative handle. |
library/src/components/Calendar/CalendarMonth/CalendarMonth.test.tsx |
Enables and updates month navigation tests. |
library/src/components/Calendar/CalendarDay/useCalendarDay.tsx |
Implements day-grid roving focus and arrow movement. |
library/src/components/Calendar/CalendarDay/CalendarDay.types.ts |
Removes focusgroup props and documents navigation. |
library/src/components/Calendar/CalendarDay/CalendarDay.test.tsx |
Enables keyboard tests and checks roving tab stops. |
library/src/components/Calendar/Calendar.test.tsx |
Updates integrated grid-focus assertions. |
library/src/components/Calendar/Calendar.cy.tsx |
Enables browser keyboard-navigation coverage. |
library/package.json |
Adds DatePicker import and require exports. |
library/etc/date-picker.api.md |
Records the new public DatePicker API. |
library/etc/calendar.api.md |
Records Calendar type and handle changes. |
library/bundle-size/DatePicker.fixture.js |
Adds a dedicated DatePicker bundle fixture. |
library/bundle-size/AllComponents.fixture.js |
Includes DatePicker in aggregate measurements. |
change/@fluentui-react-headless-components-preview-0fb1b4ca-99a7-4871-9749-705b1af0b70e.json (repository root) |
Records DatePicker and Calendar release changes. |
Review effort: Balanced
Comment on lines
+525
to
+527
| if (!target?.originalDate) { | ||
| if (ev.key !== Home && ev.key !== End) { | ||
| navigateOutsideVisibleDays(ev, day.originalDate!); |
Comment on lines
+105
to
+108
| const showYearPicker = (): void => { | ||
| focusOnUpdate.current = true; | ||
| setIsYearPickerVisible(true); | ||
| }; |
| * | ||
| * The field needs an accessible name from a Field label, `aria-labelledby` or `aria-label`. | ||
| */ | ||
| export const DatePicker: ForwardRefComponent<DatePickerProps> = /*#__PURE__*/ composeComponent( |
Comment on lines
+185
to
+187
| if (nextOpen) { | ||
| setDisplayedDate(getInitialDisplayedDate()); | ||
| focusOnOpen.current = true; |
|
|
||
| if (dayPickerProps !== null) { | ||
| calendar.dayPicker = { | ||
| onHeaderSelect: monthPickerProps === null ? undefined : onDayPickerHeaderSelect, |
| imperativeRef: mergedMonthPickerRef, | ||
| getCellProps: month => { | ||
| const overrides = getMonthCellProps?.(month); | ||
| const date = new Date(displayedDate.getFullYear(), month.index, 1); |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Previous Behavior
The headless package had Calendar (#36752) but no DatePicker. The earlier headless attempt (#36919) depended on the unmerged styled DatePicker preview base hook (#36920) and used a modal native dialog, which conflicts with the DatePicker spec (non-modal, no focus trap, Tab out closes).
Calendar grids delegated arrow navigation to native
focusgroup. Chromium lists Focusgroup V2 grid only as Proposed, and the polyfill's grid support (microsoft/polyfills#64) is merged but unreleased (latest@microsoft/focusgroup-polyfillis 1.6.0). In every shipping browser, arrows did nothing in the day, month and year grids and every available day was a tab stop (42 in Chrome).New Behavior
DatePicker
Adds
@fluentui/react-headless-components-preview/date-picker:DatePicker,useDatePicker,renderDatePicker,defaultFormatDate,defaultParseDateand public types. It composes the headless Calendar and has no dependency onreact-calendar-preview,react-datepicker-preview, Griffel or Portal. Date state and validation are adapted from #36920.Field
<button>trigger by default; native<input type="text">withallowTextInput. Native props and the forwarded ref target the field control;className/stylego to therootwrapper.aria-haspopup="dialog",aria-controlswhile open,aria-invalid+aria-describedbyfor typed-date errors, Field integration viauseFieldControlProps_unstable.aria-labelledby(label + displayed date or placeholder), including inside a Field.iconslot: decorative in the trigger; withallowTextInput, a click-only toggle with no tab stop.Popup
role="dialog"named by the visible month-year, shown in the top layer withpopover="manual"and anchored below the field (start-aligned, flips above) via the headless positioning hook.showGoToToday.openkeeps reporting close requests even when the consumer ignores one.Typed dates
YYYY-MM-DD, full/abbreviated English month + day + year (optional comma), andM/D/YYYY, as local calendar dates with round-trip validation (Feb 30 is rejected).formatDate,parseDateandformatErrorMessageallow localization.errorMessageuntil an accepted date, calendar selection, or optional clear; repeated blur does not re-report.State attributes on root:
data-open,data-disabled,data-invalid,data-editable,data-placeholder.Calendar
focusgroup. Day, month and year grids keep exactly onetabindex="0"cell, remembered until the grid shows a different date or column layout.allFocusable, consumer cancellation, and nested editors/grids/toolbars are respected.focusgroup*attributes and thefocusgroupstart/focusgrouprowcell/row prop types.aria-current="date".CalendarMonthHandle.showYearPicker()so the day heading can open the year-range grid.The Calendar changes are isolated in their own commits and can be split into a separate PR if preferred.
Intentional deviations from the design spec
aria-expandedon the editable input (unsupported on textbox;comboboxis excluded by the spec). The icon button exposes the expanded state.aria-requiredon the button trigger (unsupported on button); the Field conveys required.Validation
react-headless-components-previewand-stories: type-check, lint, test — passed (1858 tests, none skipped). The previously skipped focusgroup keyboard suites are enabled; a few assertions were updated for the current gridcell-wraps-button markup.Calendar.cy.tsx43/43 andDatePicker.cy.tsx11/11 — including real Tab order, roving arrows, month crossing, PageDown, Escape inside a modal<dialog>, focus return, and outside click.bundle-sizewas not run locally.Related Issue(s)
🤖 Generated with Claude Code