Skip to content

feat(react-headless-components-preview): add headless Calendar - #36752

Draft
Dmytro Kirpa (dmytrokirpa) wants to merge 1 commit into
calendar-preview-releasefrom
calendar-preview/headless
Draft

Dmytro Kirpa (dmytrokirpa) wants to merge 1 commit into
calendar-preview-releasefrom
calendar-preview/headless

Conversation

@dmytrokirpa

@dmytrokirpa Dmytro Kirpa (dmytrokirpa) commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the headless Calendar wrappers and exports for the stable Calendar primitives, with component tests, API report, bundle fixture, and Storybook documentation.

Stack

Depends on #36751, which promotes the underlying Calendar package.

Validation

  • yarn nx test react-calendar-preview --runInBand

Supersedes

Replaces closed #36630 after the Calendar implementation was split into focused review layers.

Comment on lines +29 to +32
dayPicker: slot.always(props.dayPicker, {
defaultProps: { ...state.dayPicker, ref: state.dayPickerRef },
elementType: CalendarDay,
}),

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.

Could we merge the consumer’s picker ref with the internal ref here?
Supplying dayPicker={{ ref: consumerRef }} currently overrides the internal ref in defaultProps, which the Calendar needs for focus restoration after keyboard navigation.
The styled implementation already merges these refs—could we use the same approach for the day, month, and nested year pickers?

Comment on lines +20 to +29
dayPicker: NonNullable<Slot<typeof CalendarDay>>;

/**
* The month picker. Only rendered while `isMonthPickerVisible` is set.
*/
monthPicker: NonNullable<Slot<typeof CalendarMonth>>;
};

export type CalendarProps = CalendarBaseProps &
ComponentProps<Partial<Pick<CalendarSlots, 'dayPicker' | 'monthPicker'>>>;

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.

Could we preserve null in these picker-slot types?
The underlying Calendar supports dayPicker={null} for month-only selection and monthPicker={null} for a day-only calendar.
NonNullable currently prevents TypeScript consumers from using those configurations.
Would keeping that contract in the headless types make sense?

@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

* @param ref - reference to the root element of Calendar
*/
export const useCalendar = (props: CalendarProps, ref: React.Ref<HTMLDivElement>): CalendarState => {
const state = useCalendarBase_unstable(props, ref);

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.

Screen.Recording.2026-09-17.at.13.51.11.mov

Could we make the inherited focus tracking work without the styled picker classes?

I reproduced this in the PR’s headless Calendar preview: at 1000px wide, focus March in the month picker without activating it, then resize to 400px. The month picker unmounts and document.activeElement becomes BODY, rather than a cell in the remaining day grid.

The base hook tracks picker focus through .fui-CalendarDay and .fui-CalendarMonth, which the headless wrappers omit. As a diagnostic control, temporarily adding the month-picker class before focusing March made the same resize correctly restore focus to the selected day.

Could we use class-independent focus tracking and add a regression test for this transition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we should've switched to data attributes in on of the previous iterations, let me double check.

@dmytrokirpa
Dmytro Kirpa (dmytrokirpa) marked this pull request as draft September 17, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants