diff --git a/.changeset/date-picker-aria.md b/.changeset/date-picker-aria.md new file mode 100644 index 0000000000..b52aaa0883 --- /dev/null +++ b/.changeset/date-picker-aria.md @@ -0,0 +1,7 @@ +--- +'@leafygreen-ui/date-picker': patch +--- + +[LG-3879](https://jira.mongodb.org/browse/LG-3879) +Updates ARIA labels for DatePicker menu previous/next buttons, and year/month select elements. +Hides calendar cell text, so screen-readers only read the cell's `aria-value`. diff --git a/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx b/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx index 29e99ca5ef..fcabc963f2 100644 --- a/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx +++ b/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx @@ -114,12 +114,14 @@ export const renderDatePicker = ( const calendarGrid = withinElement(menuContainerEl)?.queryByRole('grid'); const calendarCells = withinElement(menuContainerEl)?.getAllByRole('gridcell'); - const leftChevron = - withinElement(menuContainerEl)?.queryByLabelText('Previous month') || - withinElement(menuContainerEl)?.queryByLabelText('Previous valid month'); - const rightChevron = - withinElement(menuContainerEl)?.queryByLabelText('Next month') || - withinElement(menuContainerEl)?.queryByLabelText('Next valid month'); + + // TODO: date-picker test harnesses https://jira.mongodb.org/browse/LG-4176 + const leftChevron = withinElement(menuContainerEl)?.queryByTestId( + 'lg-date_picker-menu-prev_month_button', + ); + const rightChevron = withinElement(menuContainerEl)?.queryByTestId( + 'lg-date_picker-menu-next_month_button', + ); const monthSelect = withinElement(menuContainerEl)?.queryByLabelText( 'Select month', { diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx index 47104dc0b0..d619d04801 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx @@ -123,16 +123,32 @@ describe('packages/date-picker/date-picker-menu', () => { expect(grid).toHaveAttribute('aria-label', 'September 2023'); }); test('chevrons have aria labels', () => { - const { getByLabelText } = renderDatePickerMenu(); - const leftChevron = getByLabelText('Previous month'); - const rightChevron = getByLabelText('Next month'); + const { getByTestId } = renderDatePickerMenu(); + const leftChevron = getByTestId('lg-date_picker-menu-prev_month_button'); + const rightChevron = getByTestId('lg-date_picker-menu-next_month_button'); expect(leftChevron).toBeInTheDocument(); expect(rightChevron).toBeInTheDocument(); + expect(leftChevron).toHaveAttribute( + 'aria-label', + expect.stringContaining('Previous month'), + ); + expect(rightChevron).toHaveAttribute( + 'aria-label', + expect.stringContaining('Next month'), + ); }); test('select menu triggers have aria labels', () => { const { monthSelect, yearSelect } = renderDatePickerMenu(); expect(monthSelect).toBeInTheDocument(); expect(yearSelect).toBeInTheDocument(); + expect(monthSelect).toHaveAttribute( + 'aria-label', + expect.stringContaining('Select month'), + ); + expect(yearSelect).toHaveAttribute( + 'aria-label', + expect.stringContaining('Select year'), + ); }); test('select menus have correct values', () => { const { monthSelect, yearSelect } = renderDatePickerMenu(); diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx index adccbffd94..fad629432c 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx @@ -1,9 +1,14 @@ import React, { forwardRef, MouseEventHandler } from 'react'; -import { isSameUTCMonth, setUTCMonth } from '@leafygreen-ui/date-utils'; +import { + getMonthName, + isSameUTCMonth, + setUTCMonth, +} from '@leafygreen-ui/date-utils'; import { SupportedLocales } from '@leafygreen-ui/date-utils'; -import Icon from '@leafygreen-ui/icon'; -import IconButton from '@leafygreen-ui/icon-button'; +import { Icon } from '@leafygreen-ui/icon'; +import { IconButton } from '@leafygreen-ui/icon-button'; +import { isDefined } from '@leafygreen-ui/lib'; import { useSharedDatePickerContext } from '../../../shared/context'; import { useDatePickerContext } from '../../DatePickerContext'; @@ -42,6 +47,12 @@ export const DatePickerMenuHeader = forwardRef< const isIsoFormat = locale === SupportedLocales.ISO_8601; + const formatMonth = (date: Date) => { + const monthName = getMonthName(date.getUTCMonth(), locale); + const year = date.getUTCFullYear().toString(); + return `${monthName.long} ${year}`; + }; + /** * If the month is not in range and is not the last valid month * e.g. @@ -63,6 +74,48 @@ export const DatePickerMenuHeader = forwardRef< return !isDateInRange && !isOnLastValidMonth; }; + /** + * Given a direction (left/right), computes the nearest valid adjacent month + * + * @example + * max: new Date(Date.UTC(2038, Month.January, 19)); + * current month date: new Date(Date.UTC(2038, Month.March, 19)); + * `left` chevron will change the month back to January 2038 + * + * @example + * min: new Date(Date.UTC(1970, Month.January, 1)); + * current month date: new Date(Date.UTC(1969, Month.November, 19)); + * "right" chevron will change the month back to January 1970 + */ + const getNewMonth = (dir: 'left' | 'right'): Date => { + if (isMonthInvalid(dir)) { + const closestValidDate = dir === 'left' ? max : min; + const newMonthIndex = closestValidDate.getUTCMonth(); + const newMonth = setUTCMonth(closestValidDate, newMonthIndex); + return newMonth; + } else { + const increment = dir === 'left' ? -1 : 1; + const newMonthIndex = month.getUTCMonth() + increment; + const newMonth = setUTCMonth(month, newMonthIndex); + return newMonth; + } + }; + + const getChevronButtonLabel = (dir: 'left' | 'right') => { + const dirLabel = dir === 'left' ? 'Previous' : 'Next'; + const isNewMonthInvalid = isMonthInvalid(dir); + const newMonth = getNewMonth(dir); + const newMonthString = formatMonth(newMonth); + return [ + dirLabel, + isNewMonthInvalid ? 'valid' : undefined, + 'month', + `(${newMonthString})`, + ] + .filter(isDefined) + .join(' '); + }; + /** * Calls the `updateMonth` helper with the appropriate month when a Chevron is clicked */ @@ -71,35 +124,16 @@ export const DatePickerMenuHeader = forwardRef< e => { e.stopPropagation(); e.preventDefault(); - - // e.g. - // max: new Date(Date.UTC(2038, Month.January, 19)); - // current month date: new Date(Date.UTC(2038, Month.March, 19)); - // left chevron will change the month back to January 2038 - // e.g. - // min: new Date(Date.UTC(1970, Month.January, 1)); - // current month date: new Date(Date.UTC(1969, Month.November, 19)); - // right chevron will change the month back to January 1970 - if (isMonthInvalid(dir)) { - const closestValidDate = dir === 'left' ? max : min; - const newMonthIndex = closestValidDate.getUTCMonth(); - const newMonth = setUTCMonth(closestValidDate, newMonthIndex); - updateMonth(newMonth); - } else { - const increment = dir === 'left' ? -1 : 1; - const newMonthIndex = month.getUTCMonth() + increment; - const newMonth = setUTCMonth(month, newMonthIndex); - updateMonth(newMonth); - } + const newMonth = getNewMonth(dir); + updateMonth(newMonth); }; return (
@@ -120,7 +154,8 @@ export const DatePickerMenuHeader = forwardRef<
diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx index 4732c23b68..8dbd1b9c27 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx @@ -1,6 +1,10 @@ import React, { useCallback } from 'react'; -import { getLocaleMonths, setUTCMonth } from '@leafygreen-ui/date-utils'; +import { + getLocaleMonths, + getMonthName, + setUTCMonth, +} from '@leafygreen-ui/date-utils'; import { cx } from '@leafygreen-ui/emotion'; import { Option, Select } from '@leafygreen-ui/select'; @@ -40,18 +44,18 @@ export const DatePickerMenuSelectMonth = ({ updateMonth(newMonth); }; + const monthString = getMonthName(month.getUTCMonth(), locale); + return (