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

fix: accessibility issue modal #2076

Conversation

shrilakshmishastry
Copy link
Contributor

@shrilakshmishastry shrilakshmishastry commented Apr 14, 2024

Description

Accessibility issue in Modal

Ally pattern of modal:
Modal is a window overlaid on either the primary window or another dialog window. Windows under a modal dialog is inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close.

read more

Issue:
When the modal is open windows under a modal dialog are accessible by the keyboard.

Link to issue description

Fix:
Use dialog tag and call showModal() method on the mount of Modal component

Screen recording

then now
https://github.com/usebruno/bruno/assets/29778698/20ac9709-7967-4ccb-a580-578fd9ece1fc https://github.com/usebruno/bruno/assets/29778698/00c0b4e0-c5f9-4889-8c4c-9f3001ed946d

Contribution Checklist:

  • The pull request only addresses one issue or adds one feature.
  • [] The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

@PushpenderSaini0
Copy link
Contributor

Can you remove the package-lock.json changes from this PR !

@@ -97,6 +97,11 @@ const Modal = ({
if (hideFooter) {
classes += ' modal-footer-none';
}

useEffect(() => {
document.getElementsByTagName('dialog')[0].showModal();
Copy link
Member

Choose a reason for hiding this comment

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

I think useRef should be used here instead of accessing the DOM directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -97,6 +97,11 @@ const Modal = ({
if (hideFooter) {
classes += ' modal-footer-none';
}

useEffect(() => {
document.getElementsByTagName('dialog')[0].showModal();
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid document.getElementsByTagName it's generally not a good practice to access dom directly in react.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done here d467d83

@shrilakshmishastry
Copy link
Contributor Author

Can you remove the package-lock.json changes from this PR !

I tried here but it didn't get updated 😬

@PushpenderSaini0
Copy link
Contributor

Can you remove the package-lock.json changes from this PR !

I tried here but it didn't get updated 😬

The file is changed in 2 commits this one will be a bit tricky !

1 ) Checkout the file from the main branch
2) Commit the file (this version is from main branch)
3) (Optional) Squash the changes to make the history clean !

git checkout main -- package-lock.json
git commit -a -m "revert package-lock.json"
git rebase -i HEAD~4

Copy link

@ad1992 ad1992 left a comment

Choose a reason for hiding this comment

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

Hi @shrilakshmishastry as far as understand the issue (from description and screen recordings) is that focus is going out of the active modal/dialog when navigating through the keyboard and hence you are trying to make sure that the focus stays in active dialog. Please let me know if my understanding is correct.

However the solution of triggering showModal when mounted isn't clear to me and how is it fixing this issue ?
We should instead have a keydown handler for modal component which makes sure that on keyboard navigation the focus always keeps looping (in order) inside the active dialog unless esc is pressed ?
A small suggestion regarding the screen recording - you can directly upload the .mov files so reviewers don't have to download the files to view the screen-recordings :)

For reference - I came across this PR in https://twitter.com/shrilakshmihg/status/1779420061721035153

@shrilakshmishastry
Copy link
Contributor Author

Hi @shrilakshmishastry as far as understand the issue (from description and screen recordings) is that focus is going out of the active modal/dialog when navigating through the keyboard and hence you are trying to make sure that the focus stays in active dialog. Please let me know if my understanding is correct.

However the solution of triggering showModal when mounted isn't clear to me and how is it fixing this issue ? We should instead have a keydown handler for modal component which makes sure that on keyboard navigation the focus always keeps looping (in order) inside the active dialog unless esc is pressed ? A small suggestion regarding the screen recording - you can directly upload the .mov files so reviewers don't have to download the files to view the screen-recordings :)

For reference - I came across this PR in https://twitter.com/shrilakshmihg/status/1779420061721035153

Hey @ad1992 thanks for the suggestion!!

yes, the focus is going out of the modal, hence the fix.

calling showModal in useEffect is to set dialog open. It's a way around not using JS to handle focus the one you mentioned throw key-down event.

The intention of using the dialog tag is its ability to handle focus traps in an HTML declarative way not by JS.

you can read more about it here https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

The doc says you need to call showModal method in the caller component. Since we are handling showing Modal via js (mounting it to DOM) on button click there is no straight way we can access dialog. Hence calling showModal in useEffect so dialog provides the expected behavior when it's in open state and act as Modal.

Copy link

@ad1992 ad1992 left a comment

Choose a reason for hiding this comment

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

suggested a minor change otherwise looks good @shrilakshmishastry

I am not sure if there is a way to test the changes directly in the browser so I haven't tested it however the code looks good!

Additionally, I think you probably might be targeting the rest of the clean-up in separate PR (eg closing the modal via dialog close and removing ESC handler etc)

Comment on lines 1 to 3
import React, { useEffect, useState } from 'react';
import StyledWrapper from './StyledWrapper';
import { useRef } from 'react';
Copy link

Choose a reason for hiding this comment

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

Suggested change
import React, { useEffect, useState } from 'react';
import StyledWrapper from './StyledWrapper';
import { useRef } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import StyledWrapper from './StyledWrapper';

Since there are react imports already so better to club with it

@shrilakshmishastry
Copy link
Contributor Author

suggested a minor change otherwise looks good @shrilakshmishastry

I am not sure if there is a way to test the changes directly in the browser so I haven't tested it however the code looks good!

Additionally, I think you probably might be targeting the rest of the clean-up in separate PR (eg closing the modal via dialog close and removing ESC handler etc)

closing modal is taken care in closeModal() method and esc handling is already present in the existing code

@ad1992
Copy link

ad1992 commented Apr 16, 2024

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

@shrilakshmishastry as per the docs, I think you can remove the ESC handling as it shouldn't be needed anymore and taken care of by the dialog itself.

@shrilakshmishastry
Copy link
Contributor Author

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

@shrilakshmishastry as per the docs, I think you can remove the ESC handling as it shouldn't be needed anymore and taken care of by the dialog itself.

We need it, as there are cases where we needn't close the modal on the click of esc key. There is one prop the modal accepts and based on that action on the esc key-down is taken care

https://github.com/usebruno/bruno/blob/d490b1b5fc2cea0d8c0aa60d4ab07210f4d647cc/packages/bruno-app/src/components/Modal/index.js#L67C2-L67C20

@@ -1,6 +1,6 @@
import styled from 'styled-components';

const Wrapper = styled.div`
const Wrapper = styled.dialog`
color: ${(props) => props.theme.text};
Copy link
Member

Choose a reason for hiding this comment

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

Could you also add:

  max-height: unset;
  max-width: unset;

To remove the weird gap around the backdrop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

could you please elaborate more

Copy link
Member

Choose a reason for hiding this comment

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

Dialogs now have a "weird" gap between the window border and the backdrop:
image

This is because the dialog Element has some weird default CSS. From the chrome source:

dialog:-internal-dialog-in-top-layer {
    /* https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3 */
    position: fixed;
    overflow: auto;
    inset-block-start: 0;
    inset-block-end: 0;
    max-width: calc(100% - 6px - 2em);
    max-height: calc(100% - 6px - 2em);
    /* https://github.com/w3c/csswg-drafts/issues/6939#issuecomment-1016679588 */
    user-select: text;
    visibility: visible;
}

Unsetting the default max-width and max-height will remove the gap.

Suggested change
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.text};
max-height: unset;
max-width: unset;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks !!

fixed it now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Its-treason can you check now

Copy link
Member

@Its-treason Its-treason left a comment

Choose a reason for hiding this comment

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

image

I found that the "toasts" are now in the background, behind the dialog/backdrop instead of the foreground. This is probably due to how the dialog element works: https://stackoverflow.com/a/41816764

@shrilakshmishastry
Copy link
Contributor Author

image

I found that the "toasts" are now in the background, behind the dialog/backdrop instead of the foreground. This is probably due to how the dialog element works: https://stackoverflow.com/a/41816764

Seems intersting, I will look into this. Thanks

@shrilakshmishastry
Copy link
Contributor Author

image
I found that the "toasts" are now in the background, behind the dialog/backdrop instead of the foreground. This is probably due to how the dialog element works: https://stackoverflow.com/a/41816764

Seems intersting, I will look into this. Thanks

The toast needs to be attached to or decendent of top layer element when triggered from them. I have rised the issue in react-hot-toast. Not sure whether it should be fixed from them or here. I will look into this furthur
timolins/react-hot-toast#355

@helloanoop
Copy link
Contributor

@anusreesubash Please review this?

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.

5 participants