Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/date-picker-aria.md
Original file line number Diff line number Diff line change
@@ -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`.
14 changes: 8 additions & 6 deletions packages/date-picker/src/DatePicker/DatePicker.testutils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { isDefined } from '@leafygreen-ui/lib';

import { useSharedDatePickerContext } from '../../../shared/context';
import { useDatePickerContext } from '../../DatePickerContext';
Expand Down Expand Up @@ -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.
Expand All @@ -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
*/
Expand All @@ -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 (
<div ref={fwdRef} className={menuHeaderStyles} {...rest}>
<IconButton
ref={refs.chevronButtonRefs.left}
aria-label={
isMonthInvalid('left') ? 'Previous valid month' : 'Previous month'
}
data-testid="lg-date_picker-menu-prev_month_button"
aria-label={getChevronButtonLabel('left')}
disabled={shouldChevronBeDisabled('left', month, min)}
onClick={handleChevronClick('left')}
>
Expand All @@ -120,7 +154,8 @@ export const DatePickerMenuHeader = forwardRef<
</div>
<IconButton
ref={refs.chevronButtonRefs.right}
aria-label={isMonthInvalid('right') ? 'Next valid month' : 'Next month'}
data-testid="lg-date_picker-menu-next_month_button"
aria-label={getChevronButtonLabel('right')}
disabled={shouldChevronBeDisabled('right', month, max)}
onClick={handleChevronClick('right')}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -40,16 +44,18 @@ export const DatePickerMenuSelectMonth = ({
updateMonth(newMonth);
};

const monthString = getMonthName(month.getUTCMonth(), locale);

return (
<Select
{...selectElementProps}
aria-label="select month"
aria-label={`Select month (${monthString.long} selected)`}
value={month.getUTCMonth().toString()}
onChange={handleMonthOnChange}
className={cx(selectTruncateStyles, selectInputWidthStyles)}
onEntered={() => setIsSelectOpen(true)}
onExited={() => setIsSelectOpen(false)}
placeholder={monthOptions[month.getUTCMonth()].short}
placeholder={monthString.short}
>
{monthOptions.map((m, i) => (
<Option
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ export const DatePickerMenuSelectYear = ({
updateMonth(newMonth);
};

const yearString = month.getUTCFullYear().toString();

return (
<Select
{...selectElementProps}
aria-label="select year"
value={month.getUTCFullYear().toString()}
aria-label={`Select year (${yearString} selected)`}
value={yearString}
onChange={handleYearOnChange}
className={cx(selectTruncateStyles, selectInputWidthStyles)}
onEntered={() => setIsSelectOpen(true)}
onExited={() => setIsSelectOpen(false)}
placeholder={month.getUTCFullYear().toString()}
placeholder={yearString}
>
{yearOptions.map(y => (
<Option value={y.toString()} key={y} aria-label={y.toString()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const CalendarCell = React.forwardRef<
isHighlighted,
className,
onClick,
'aria-label': ariaLabel,
...rest
}: CalendarCellProps,
fwdRef,
Expand Down Expand Up @@ -86,6 +87,7 @@ export const CalendarCell = React.forwardRef<
role="gridcell"
data-testid="lg-date_picker-calendar_cell"
data-highlighted={isHighlighted}
aria-label={ariaLabel}
aria-current={isCurrent}
aria-selected={isActive}
aria-disabled={state === CalendarCellState.Disabled}
Expand All @@ -109,6 +111,7 @@ export const CalendarCell = React.forwardRef<
>
<div className={cx(indicatorBaseStyles, indicatorClassName)}></div>
<span
aria-hidden={true} // hidden, since the `td` announces the value via `aria-label`
className={cx(cellTextStyles, {
[cellTextCurrentStyles]: isCurrent,
})}
Expand Down
Loading