diff --git a/docs/pages/x/api/date-pickers/pickers-calendar-header.json b/docs/pages/x/api/date-pickers/pickers-calendar-header.json index 94aa338f027f..b2a50f1ad029 100644 --- a/docs/pages/x/api/date-pickers/pickers-calendar-header.json +++ b/docs/pages/x/api/date-pickers/pickers-calendar-header.json @@ -5,6 +5,7 @@ "type": { "name": "string" }, "default": "`${adapter.formats.month} ${adapter.formats.year}`" }, + "labelId": { "type": { "name": "string" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, diff --git a/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json b/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json index fbf2f5e605c2..d3c101a847ab 100644 --- a/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json +++ b/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json @@ -11,6 +11,7 @@ "type": { "name": "string" }, "default": "`${adapter.formats.month} ${adapter.formats.year}`" }, + "labelId": { "type": { "name": "string" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, diff --git a/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json b/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json index 6095d04c8b30..14b1549d337d 100644 --- a/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json +++ b/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json @@ -3,6 +3,9 @@ "propDescriptions": { "classes": { "description": "Override or extend the styles applied to the component." }, "format": { "description": "Format used to display the date." }, + "labelId": { + "description": "Id of the calendar text element. It is used to establish an aria-labelledby relationship with the calendar grid element." + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { diff --git a/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json b/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json index a152174c7d95..9affb9834e6d 100644 --- a/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json +++ b/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json @@ -4,6 +4,9 @@ "calendars": { "description": "The number of calendars rendered." }, "classes": { "description": "Override or extend the styles applied to the component." }, "format": { "description": "Format used to display the date." }, + "labelId": { + "description": "Id of the calendar text element. It is used to establish an aria-labelledby relationship with the calendar grid element." + }, "month": { "description": "Month used for this header." }, "monthIndex": { "description": "Index of the month used for this header." }, "slotProps": { "description": "The props used for each component slot." }, diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx index 1947567216c9..ffa81a05f51b 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx @@ -25,7 +25,7 @@ import { describeConformance } from 'test/utils/describeConformance'; import { RangePosition } from '../models'; const getPickerDay = (name: string, picker = 'January 2018') => - getByRole(screen.getByText(picker)?.parentElement?.parentElement!, 'gridcell', { name }); + getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); const dynamicShouldDisableDate = (date, position: RangePosition) => { if (position === 'end') { diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 1d8bbc4daa79..4d4e418bea87 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -5,7 +5,8 @@ import useEventCallback from '@mui/utils/useEventCallback'; import useMediaQuery from '@mui/material/useMediaQuery'; import { resolveComponentProps, useSlotProps } from '@mui/base/utils'; import { styled, useThemeProps } from '@mui/material/styles'; -import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import composeClasses from '@mui/utils/composeClasses'; +import useId from '@mui/utils/useId'; import { Watermark } from '@mui/x-license'; import { applyDefaultDate, @@ -231,6 +232,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< const utils = useUtils(); const now = useNow(timezone); + const id = useId(); const { rangePosition, onRangePositionChange } = useRangePosition({ rangePosition: rangePositionProp, @@ -548,10 +550,16 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< {calendarMonths.map((monthIndex) => { const month = visibleMonths[monthIndex]; + const labelId = `${id}-grid-${monthIndex}-label`; return ( - {...calendarHeaderProps} month={month} monthIndex={monthIndex} /> + + {...calendarHeaderProps} + month={month} + monthIndex={monthIndex} + labelId={labelId} + /> className={classes.dayCalendar} {...calendarState} @@ -575,6 +583,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< fixedWeekNumber={fixedWeekNumber} displayWeekNumber={displayWeekNumber} timezone={timezone} + gridLabelId={labelId} /> ); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 98032d863607..4c4476eda2eb 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -18,8 +18,8 @@ import { const isJSDOM = /jsdom/.test(window.navigator.userAgent); -const getPickerDay = (name: string, picker = 'January 2018'): HTMLButtonElement => - getByRole(screen.getByText(picker)?.parentElement?.parentElement!, 'gridcell', { name }); +const getPickerDay = (name: string, picker = 'January 2018') => + getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); describe('', () => { const { render, clock } = createPickerRenderer({ diff --git a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx index faf5ab4b228a..6657941e84d6 100644 --- a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx +++ b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx @@ -29,7 +29,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda const utils = useUtils(); const localeText = useLocaleText(); - const { calendars, month, monthIndex, ...other } = props; + const { calendars, month, monthIndex, labelId, ...other } = props; const { format, slots, @@ -56,7 +56,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda }); if (calendars === 1) { - return ; + return ; } const selectNextMonth = () => onMonthChange(utils.addMonths(currentMonth, 1), 'left'); @@ -76,6 +76,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda nextLabel={localeText.nextMonth} slots={slots} slotProps={slotProps} + labelId={labelId} > {utils.formatByString(month, format ?? `${utils.formats.month} ${utils.formats.year}`)} @@ -105,6 +106,10 @@ PickersRangeCalendarHeader.propTypes = { * @default `${adapter.formats.month} ${adapter.formats.year}` */ format: PropTypes.string, + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId: PropTypes.string, maxDate: PropTypes.object.isRequired, minDate: PropTypes.object.isRequired, diff --git a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx index a172fa35fee2..d49252d9c441 100644 --- a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx +++ b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx @@ -281,6 +281,10 @@ PickersCalendarHeader.propTypes = { * @default `${adapter.formats.month} ${adapter.formats.year}` */ format: PropTypes.string, + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId: PropTypes.string, maxDate: PropTypes.object.isRequired, minDate: PropTypes.object.isRequired, diff --git a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts index 06cbb152849a..a679a4fe9b6f 100644 --- a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts +++ b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts @@ -67,6 +67,10 @@ export interface PickersCalendarHeaderProps view: DateView; reduceAnimations: boolean; onViewChange?: (view: DateView) => void; + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId?: string; /** * Override or extend the styles applied to the component. diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx index 617a71eea6fd..2ecae8493f14 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx @@ -79,6 +79,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch isPreviousHidden, onGoToPrevious, previousLabel, + labelId, ...other } = props; @@ -169,7 +170,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch )} {children ? ( - + {children} ) : ( diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx index aaa5552a8c65..0998b6ded18b 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx @@ -38,6 +38,7 @@ export interface PickersArrowSwitcherProps isNextHidden?: boolean; onGoToNext: () => void; nextLabel: string; + labelId?: string; } export type PickersArrowSwitcherOwnerState = PickersArrowSwitcherProps;