Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type {
CalendarMonthBaseProps,
CalendarMonthBaseState,
CalendarMonthCell,
CalendarMonthContextValue,
CalendarMonthContextValues,
CalendarMonthHandle,
CalendarMonthHeaderSelectData,
CalendarMonthNavigateData,
CalendarMonthProps,
CalendarMonthSelectData,
CalendarMonthSlots,
CalendarMonthState,
} from './components/CalendarMonth/index';
export {
CalendarMonth,
calendarMonthClassNames,
renderCalendarMonth_unstable,
useCalendarMonthBase_unstable,
useCalendarMonthContextValues_unstable,
useCalendarMonth_unstable,
useCalendarMonthStyles_unstable,
} from './components/CalendarMonth/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type {
CalendarMonthGridCellProps,
CalendarMonthGridCellSlots,
CalendarMonthGridCellState,
} from './components/CalendarMonthGridCell/index';
export {
CalendarMonthGridCell,
calendarMonthGridCellClassNames,
renderCalendarMonthGridCell_unstable,
useCalendarMonthGridCellStyles_unstable,
useCalendarMonthGridCell_unstable,
} from './components/CalendarMonthGridCell/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type {
CalendarMonthGridRowProps,
CalendarMonthGridRowSlots,
CalendarMonthGridRowState,
} from './components/CalendarMonthGridRow/index';
export {
CalendarMonthGridRow,
calendarMonthGridRowClassNames,
renderCalendarMonthGridRow_unstable,
useCalendarMonthGridRowBase_unstable,
useCalendarMonthGridRowStyles_unstable,
useCalendarMonthGridRow_unstable,
} from './components/CalendarMonthGridRow/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type {
CalendarYearBaseProps,
CalendarYearBaseState,
CalendarYearCell,
CalendarYearContextValue,
CalendarYearContextValues,
CalendarYearHandle,
CalendarYearHeaderSelectData,
CalendarYearNavigateData,
CalendarYearProps,
CalendarYearRange,
CalendarYearSlots,
CalendarYearState,
CalendarYearSelectData,
} from './components/CalendarYear/index';
export {
CalendarYear,
calendarYearClassNames,
renderCalendarYear_unstable,
useCalendarYearBase_unstable,
useCalendarYearContextValues_unstable,
useCalendarYear_unstable,
useCalendarYearStyles_unstable,
} from './components/CalendarYear/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type {
CalendarYearGridCellProps,
CalendarYearGridCellSlots,
CalendarYearGridCellState,
} from './components/CalendarYearGridCell/index';
export {
CalendarYearGridCell,
calendarYearGridCellClassNames,
renderCalendarYearGridCell_unstable,
useCalendarYearGridCellStyles_unstable,
useCalendarYearGridCell_unstable,
} from './components/CalendarYearGridCell/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type {
CalendarYearGridRowProps,
CalendarYearGridRowSlots,
CalendarYearGridRowState,
} from './components/CalendarYearGridRow/index';
export {
CalendarYearGridRow,
calendarYearGridRowClassNames,
renderCalendarYearGridRow_unstable,
useCalendarYearGridRowBase_unstable,
useCalendarYearGridRowStyles_unstable,
useCalendarYearGridRow_unstable,
} from './components/CalendarYearGridRow/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as React from 'react';
import { fireEvent, render as testingRender } from '@testing-library/react';
import { CalendarMonth } from './CalendarMonth';
import { CalendarProvider, calendarContextDefaultValue } from '../../contexts/calendarContext';
import { calendarFormatters } from '../../utils';
import type { CalendarContextValue } from '../../contexts/calendarContext';
import type { CalendarMonthProps } from './CalendarMonth.types';

const defaultProps: CalendarMonthProps = {
navigatedDate: new Date(2025, 0, 15),
onNavigateDate: jest.fn(),
};

const render = (element: React.ReactElement, contextValue: Partial<CalendarContextValue> = {}) =>
testingRender(element, {
wrapper: ({ children }) => (
<CalendarProvider value={{ ...calendarContextDefaultValue, value: new Date(2025, 0, 15), ...contextValue }}>
{children}
</CalendarProvider>
),
});

type FormatDateTime = typeof calendarFormatters.dateTime;

describe('CalendarMonth', () => {
it('should render without crashing', () => {
expect(() => render(<CalendarMonth {...defaultProps} />)).not.toThrow();
});

it('keeps unavailable months focusable but not selectable with allFocusable', () => {
const onSelectDate = jest.fn();
const { getByRole } = render(<CalendarMonth {...defaultProps} onSelectDate={onSelectDate} />, {
allFocusable: true,
minDate: new Date(2025, 1, 1),
});
const january = getByRole('gridcell', { name: 'January' });

expect(january).not.toBeDisabled();
expect(january).toHaveAttribute('aria-disabled', 'true');
expect(january).toHaveAttribute('tabindex', '0');
fireEvent.click(january);
fireEvent.keyDown(january, { key: 'Enter' });
expect(onSelectDate).not.toHaveBeenCalled();
});

it('uses localized strings for the header and year navigation buttons', () => {
const dateTime: FormatDateTime = ({ date, format }) => `Localized ${calendarFormatters.dateTime({ date, format })}`;
const { getByRole } = render(<CalendarMonth {...defaultProps} yearPickerHidden />, {
formatters: {
...calendarFormatters,
dateTime,
monthPickerHeaderLabel: data => `Change the displayed year: ${data.formattedDate}`,
previousYearLabel: data => `Go to prior year ${data.formattedDate}`,
nextYearLabel: data => `Go to following year ${data.formattedDate}`,
},
});

expect(getByRole('button', { name: 'Change the displayed year: Localized 2025' })).toBeTruthy();
expect(getByRole('button', { name: 'Go to prior year Localized 2024' })).toHaveAttribute(
'title',
'Go to prior year Localized 2024',
);
expect(getByRole('button', { name: 'Go to following year Localized 2026' })).toHaveAttribute(
'title',
'Go to following year Localized 2026',
);
expect(getByRole('gridcell', { name: 'Localized January' })).toHaveTextContent('Localized Jan');
});

it('passes localized range strings to the year picker', () => {
const { getAllByRole, getByRole } = render(<CalendarMonth {...defaultProps} />, {
formatters: {
...calendarFormatters,
monthPickerHeaderLabel: data => `Change the displayed year: ${data.formattedDate}`,
yearRangePickerHeaderLabel: data => `Change the displayed year: ${data.formattedRange}`,
previousYearRangeLabel: data => `Earlier years ${data.formattedRange}`,
nextYearRangeLabel: data => `Later years ${data.formattedRange}`,
},
});

fireEvent.click(getByRole('button', { name: 'Change the displayed year: 2025' }));

const buttons = getAllByRole('button');
expect(getByRole('button', { name: 'Change the displayed year: 2025 - 2036' })).toBeTruthy();
expect(buttons[1]).toHaveAttribute('title', 'Earlier years 2013 - 2024');
expect(buttons[buttons.length - 1]).toHaveAttribute('title', 'Later years 2037 - 2048');
});

describe('motion wrappers preserve grid structure', () => {
it('renders all month rows under the grid with role="row"', () => {
const { getByRole, getAllByRole } = render(<CalendarMonth {...defaultProps} />);
const grid = getByRole('grid');
const rows = getAllByRole('row');
// 12 months laid out 4 per row → 3 rows
expect(rows.length).toBe(3);
rows.forEach(row => {
expect(grid.contains(row)).toBe(true);
});
});

it('renders month buttons as gridcells inside rows', () => {
const { getAllByRole } = render(<CalendarMonth {...defaultProps} />);
const cells = getAllByRole('gridcell');
expect(cells.length).toBe(12);
cells.forEach(cell => {
expect(cell.parentElement?.getAttribute('role')).toBe('row');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import * as React from 'react';
import { useCalendarMonth_unstable } from './useCalendarMonth';
import { useCalendarMonthContextValues_unstable } from './useCalendarMonthContextValues';
import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles';
import { renderCalendarMonth_unstable } from './renderCalendarMonth';
import type { CalendarMonthHandle, CalendarMonthProps } from './CalendarMonth.types';

/**
* The month picker: a year header with navigation controls above a grid of months. Swaps itself
* for CalendarYear while the year picker is open.
*/
export const CalendarMonth = React.forwardRef<CalendarMonthHandle, CalendarMonthProps>((props, ref) => {
const state = useCalendarMonth_unstable(props, ref);
const contextValues = useCalendarMonthContextValues_unstable(state);

useCalendarMonthStyles_unstable(state);

return renderCalendarMonth_unstable(state, contextValues);
});

CalendarMonth.displayName = 'CalendarMonth';
Loading
Loading