Skip to content

Commit

Permalink
refactor(calendar): refactored cell button and added tests for aria l…
Browse files Browse the repository at this point in the history
…abel (#212)

* refactor(calendar): refactored cell button and added tests for arialabel

* chore: minor refactors
  • Loading branch information
anuraghazra authored Aug 11, 2021
1 parent 2d1156a commit ad8c77a
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 62 deletions.
26 changes: 13 additions & 13 deletions src/calendar/CalendarButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const useCalendarButton = createHook<
goto,
} = options;

const TYPES = {
const HANDLER_TYPES = {
nextMonth: {
handler: focusNextMonth,
ariaLabel: "Next Month",
Expand All @@ -47,8 +47,8 @@ export const useCalendarButton = createHook<
};

return {
"aria-label": TYPES[goto]?.ariaLabel,
onClick: callAllHandlers(htmlOnClick, TYPES[goto]?.handler),
"aria-label": HANDLER_TYPES[goto]?.ariaLabel,
onClick: callAllHandlers(htmlOnClick, HANDLER_TYPES[goto]?.handler),
...htmlProps,
};
},
Expand All @@ -60,16 +60,16 @@ export const CalendarButton = createComponent({
useHook: useCalendarButton,
});

export type CalendarButtonOptions = {
goto: CalendarGoto;
} & Pick<
CalendarStateReturn,
| "focusNextMonth"
| "focusPreviousMonth"
| "focusPreviousYear"
| "focusNextYear"
> &
ButtonOptions;
export type CalendarButtonOptions = ButtonOptions &
Pick<
CalendarStateReturn,
| "focusNextMonth"
| "focusPreviousMonth"
| "focusPreviousYear"
| "focusNextYear"
> & {
goto: CalendarGoto;
};

export type CalendarButtonHTMLProps = ButtonHTMLProps;

Expand Down
84 changes: 41 additions & 43 deletions src/calendar/CalendarCellButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,39 +92,37 @@ export const useCalendarCellButton = createHook<
});

// aria-label should be localize Day of week, Month, Day and Year without Time.
let ariaLabel = dateFormatter.format(date);
if (isToday) {
// If date is today, set appropriate string depending on selected state:
ariaLabel = isSelected
? `Today, ${ariaLabel} selected`
: `Today, ${ariaLabel}`;
} else if (isSelected) {
// If date is selected but not today:
ariaLabel = `${ariaLabel} selected`;
}

// When a cell is focused and this is a range calendar, add a prompt to help
// screenreader users know that they are in a range selection mode.
if (options.isRangeCalendar && isFocused && !isDisabled) {
let rangeSelectionPrompt = "";

// If selection has started add "click to finish selecting range"
if (anchorDate) {
rangeSelectionPrompt = "click to finish selecting range";
// Otherwise, add "click to start selecting range" prompt
} else {
rangeSelectionPrompt = "click to start selecting range";
function getAriaLabel() {
let ariaLabel = dateFormatter.format(date);
const isTodayLabel = isToday ? "Today, " : "";
const isSelctedLabel = isSelected ? " selected" : "";
ariaLabel = `${isTodayLabel}${ariaLabel}${isSelctedLabel}`;

// When a cell is focused and this is a range calendar, add a prompt to help
// screenreader users know that they are in a range selection mode.
if (options.isRangeCalendar && isFocused && !isDisabled) {
let rangeSelectionPrompt = "";

// If selection has started add "click to finish selecting range"
if (anchorDate) {
rangeSelectionPrompt = "click to finish selecting range";
// Otherwise, add "click to start selecting range" prompt
} else {
rangeSelectionPrompt = "click to start selecting range";
}

// Append to aria-label
if (rangeSelectionPrompt) {
ariaLabel = `${ariaLabel} (${rangeSelectionPrompt})`;
}
}

// Append to aria-label
if (rangeSelectionPrompt) {
ariaLabel = `${ariaLabel} (${rangeSelectionPrompt})`;
}
return ariaLabel;
}

return {
children: useDateFormatter({ day: "numeric" }).format(date),
"aria-label": ariaLabel,
"aria-label": getAriaLabel(),
tabIndex: !disabled ? (isSameDay(date, focusedDate) ? 0 : -1) : undefined,
ref: useForkRef(ref, htmlRef),
onClick: callAllHandlers(htmlOnClick, onClick),
Expand All @@ -140,23 +138,23 @@ export const CalendarCellButton = createComponent({
useHook: useCalendarCellButton,
});

export type CalendarCellButtonOptions = {
date: Date;
} & Pick<
CalendarStateReturn,
| "focusedDate"
| "selectDate"
| "setFocusedDate"
| "isDisabled"
| "month"
| "minDate"
| "maxDate"
| "dateValue"
| "isFocused"
| "isRangeCalendar"
> &
export type CalendarCellButtonOptions = ButtonOptions &
Partial<Pick<RangeCalendarStateReturn, "anchorDate">> &
ButtonOptions;
Pick<
CalendarStateReturn,
| "focusedDate"
| "selectDate"
| "setFocusedDate"
| "isDisabled"
| "month"
| "minDate"
| "maxDate"
| "dateValue"
| "isFocused"
| "isRangeCalendar"
> & {
date: Date;
};

export type CalendarCellButtonHTMLProps = ButtonHTMLProps;

Expand Down
8 changes: 3 additions & 5 deletions src/calendar/CalendarState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ export function useCalendarState(
defaultValue: parseDate(defaultValueProp),
onChange,
});
const monthFormatter = useDateFormatter({ month: "long", year: "numeric" });

const initialMonth = value ?? new Date();
const minValue = parseDate(minValueProp);
const maxValue = parseDate(maxValueProp);

const initialMonth = value ?? new Date();
const [currentMonth, setCurrentMonth] = React.useState(initialMonth);
const [focusedDate, setFocusedDate] = React.useState(initialMonth);
const [isFocused, setFocused] = React.useState(autoFocus);

const month = currentMonth.getMonth();
const year = currentMonth.getFullYear();
Expand Down Expand Up @@ -128,10 +130,6 @@ export function useCalendarState(
}
}

const [isFocused, setFocused] = React.useState(autoFocus);

const monthFormatter = useDateFormatter({ month: "long", year: "numeric" });

// Announce when the current month changes
useUpdateEffect(() => {
// announce the new month with a change from the Previous or Next button
Expand Down
19 changes: 19 additions & 0 deletions src/calendar/__tests__/Calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,25 @@ describe("Calendar", () => {
expect(label(/^saturday, october 31, 2020$/i)).toHaveFocus();
});

it("should have proper aria-label for calendar cell button", () => {
MockDate.set(new Date(2021, 7, 10));
render(<CalendarComp />);

screen.getByRole("button", {
name: /today, tuesday, august 10, 2021/i,
});

repeat(press.Tab, 5);
press.Enter();
screen.getByRole("button", {
name: /today, tuesday, august 10, 2021 selected/i,
});
press.ArrowRight();
expect(screen.getByLabelText(/wednesday, august 11, 2021/i)).toHaveFocus();

MockDate.reset();
});

test("Calendar renders with no a11y violations", async () => {
const { container } = render(<CalendarComp />);
const results = await axe(container);
Expand Down
2 changes: 1 addition & 1 deletion src/calendar/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useWeekDays(weekStart: number) {
const dateDay = setDay(Date.now(), (index + weekStart) % 7);
const day = dayFormatter.format(dateDay);
const dayLong = dayFormatterLong.format(dateDay);
return { title: dayLong, abbr: day };
return { title: dayLong, abbr: day } as const;
});
}

Expand Down

1 comment on commit ad8c77a

@vercel
Copy link

@vercel vercel bot commented on ad8c77a Aug 11, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.