Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve accessibility for DatePicker #793

Merged
merged 27 commits into from
Aug 24, 2023
Merged

Improve accessibility for DatePicker #793

merged 27 commits into from
Aug 24, 2023

Conversation

heisand
Copy link
Contributor

@heisand heisand commented Aug 23, 2023

Background

There are some accessibility issues with the DatePicker (and consequently the DateRangePicker):

  • One cannot tab inside the calendars, which makes it impossible for keyboard users to change months/years using the arrow buttons.
  • One cannot navigate inside the calendars with screen reader at all (using ctrl+opt+arrow) in, e.g., Chrome.
  • When closing the calendars, the focus ends up in a totally different place than what is logical.
  • There is no visual focus when moving focus to today's date.
  • The language codes are not totally correct, which causes some screen reader text to be read in English while other in Norwegian.

Other issues:

  • It appears a little buggy that one cannot close the calendar using the button, which is possible in React Aria's example.

Solution

  • Make it possible to navigate with keyboard in the calendar:

    First, study React Aria's example to see if there are any props we are missing:
    - hidden={isOutsideVisibleRange} helps navigate in the calendar with tab.

    Secondly, study Entur's DatePicker, who also use the same library as us:
    - To be able to tab easily inside the DatePicker, we can use a FocusLock around the Calendar, since we here really do not want to end up outside of the calendar unexpectedly.

  • Raise this issue in the react-spectrum repo. They made a workaround we await release on, while the original bug is reported to Chrome.

  • Make sure focus is reset to the calendar button when closed. This way, the next focusable element will be the next logical element. The way this is achieved, is wrapping the CalendarTriggerButton in a PopoverTrigger, which handles this for us. When using a PopoverTrigger, the focusable child must have a forwarded ref. The button then also needs the date picker ref instead of the input.

  • Add visual focus to today's date in the calendar when it is focused.

  • Correct the language codes used in React Aria's useState so that everything announced by the screen reader is in the same and correct language.

  • For the bugginess around closing the calendar with the button:
    This is a little hard nut because we here are using the Popover from Chakra and not from React Aria. If we simply had used the Popover from React Aria, the open-close would handle itself the correct way. We use this Popover because we use the PopoverAnchor, PopoverPopoverArrow, an so on from Chakra, which must be wrapped in the Chakra Popover.

    Then, we basically have two states, one for the Popover and another for the DatePicker (via useDatePicker's state). The Popover listens to outside clicks, escape clicks and so on, and the Popover can fire onOpen and onClose callbacks based on this. Here, we update the DatePicker's state to open and closed. If we did not do it this way, the first focusable element within the calendar is not set correctly (the first one should be today's date).

    The reason why the calendar closes and opens right away when clicking the trigger button while the calendar is open, is then because the Popover notices we press outside the calendar and closes the state, while the trigger button then opens it again since the state just got closed.

    This is also fixed by using the PopoverTrigger. The other props, returnFocusOnClose etc., specified for the Popover is really then not needed since they did not work as intended anyway.

  • In addition to this:
    Since we had problems with portals for the ComboBox, add withPortal as an optional prop so that using a Portal or not is optional.

Will then also solve #731.

@changeset-bot
Copy link

changeset-bot bot commented Aug 23, 2023

🦋 Changeset detected

Latest commit: 7f99fd8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@vygruppen/spor-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@heisand heisand requested review from a team, selbekk and emildohlenhansen and removed request for a team August 23, 2023 09:47
const calendarAriaLabel = calendarProps["aria-label"];

const ariaLabel =
t(texts.calendar) + (calendarAriaLabel ? ` ${calendarAriaLabel}` : "");
Copy link
Contributor Author

@heisand heisand Aug 23, 2023

Choose a reason for hiding this comment

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

Add a slightly better aria-label than just, e.g, "august 2023". The result will be "Calendar august 2023". The August 2023 part comes from the React Aria calendarProps

) {
// Don't submit the form
e.stopPropagation();
state.setOpen(true);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I struggled finding that this code is really doing anything, other than creating confusion... I tried using Enter within the input field, but the calendar does not open...

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm that's unfortunate. Without this code, there isn't really any way a screen reader can trigger the calendar.

Not much of a difference since it didn't work anyways, but we should probably find a way to trigger the calendar for the simple variant, right?

Copy link
Contributor Author

@heisand heisand Aug 24, 2023

Choose a reason for hiding this comment

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

The normal way to open stuff when navigating with a screen reader is in fact by using space, not enter - and that works in the simple variant when I focus at the label. But keyboard users will not be able to focus on this element.. I do not know what has been discussed about the simple variant, but maybe rather it should open automatically when receiving focus as there is no actual indication that the user should press either space or enter in the input field to open it.

But I will leave it as is until now and this can be discussed separately 👍

@heisand heisand marked this pull request as ready for review August 23, 2023 09:50
@@ -38,7 +38,6 @@ export const DateTimeSegment = forwardRef<HTMLDivElement, DateTimeSegmentProps>(
...segmentProps.style,
fontVariantNumeric: "tabular-nums",
boxSizing: "content-box",
color: colors.darkGrey,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This unfortunately overwrote the focused style as well, so moved it to dateTimeSegment: instead

{...fieldProps}
/>
</StyledField>
</PopoverAnchor>
{hasTrigger && <CalendarTriggerButton {...buttonProps} />}
{hasTrigger && (
<PopoverTrigger>
Copy link
Contributor

Choose a reason for hiding this comment

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

🙌

Copy link
Contributor

@AnamaP AnamaP left a comment

Choose a reason for hiding this comment

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

I have to say that this is really an impressive piece of work and not least the effort you have put in to solve the challenges 💪🌟 Well described and comprehensive summary!

Copy link
Contributor

@selbekk selbekk left a comment

Choose a reason for hiding this comment

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

+1 on what Ana Maria said. Great job here Heidi! 🙌

) {
// Don't submit the form
e.stopPropagation();
state.setOpen(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm that's unfortunate. Without this code, there isn't really any way a screen reader can trigger the calendar.

Not much of a difference since it didn't work anyways, but we should probably find a way to trigger the calendar for the simple variant, right?

packages/spor-react/src/datepicker/utils.ts Outdated Show resolved Hide resolved
packages/spor-react/src/theme/components/datepicker.ts Outdated Show resolved Hide resolved
@heisand heisand merged commit 160c056 into main Aug 24, 2023
1 check passed
@heisand heisand deleted the hmm-popover branch August 24, 2023 06:54
@heisand heisand mentioned this pull request Aug 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants