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

chore: update modal accessibility improvements #1457

Closed
wants to merge 9 commits into from
4 changes: 2 additions & 2 deletions collections/forms/i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-02-12T14:58:56.792Z\n"
"PO-Revision-Date: 2024-02-12T14:58:56.792Z\n"
"POT-Creation-Date: 2024-03-19T11:50:22.655Z\n"
"PO-Revision-Date: 2024-03-19T11:50:22.655Z\n"

msgid "Upload file"
msgstr "Upload file"
Expand Down
3 changes: 3 additions & 0 deletions components/modal/src/modal/close-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import { colors, theme } from '@dhis2/ui-constants'
import { IconCross16 } from '@dhis2/ui-icons'
import PropTypes from 'prop-types'
import React from 'react'
import i18n from '../locales/index.js'

const createClickHandler = (onClick) => (event) => {
onClick({}, event)
}

export const CloseButton = ({ onClick }) => (
<button
title={i18n.t('Close modal dialog')}
data-test="dhis2-modal-close-button"
onClick={createClickHandler(onClick)}
aria-label={i18n.t('Close modal dialog')}
>
<IconCross16 />
<style jsx>{`
Expand Down
23 changes: 22 additions & 1 deletion components/modal/src/modal/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Layer } from '@dhis2-ui/layer'
import { spacers, spacersNum, sharedPropTypes } from '@dhis2/ui-constants'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import React, { useEffect } from 'react'
import { resolve } from 'styled-jsx/css'
import { CloseButton } from './close-button.js'

Expand All @@ -28,6 +28,27 @@ export const Modal = ({
small,
}) => {
const layerStyles = resolveLayerStyles(hide)

useEffect(() => {
if (hide) {
return
}

const handleKeyDown = (event) => {
event.preventDefault()
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if preventDefault is necessary, I believe it's just for some HTML-components. Maybe @ismay has some input in regards to this?

Copy link
Member Author

Choose a reason for hiding this comment

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

without the prevent default, keys like enter and space bar work as well, what I want is for escape key only to be able to close the modal.

if (event.key === 'Escape' && onClose) {
event.stopPropagation()
onClose()
}
}

document.addEventListener('keydown', handleKeyDown)
Birkbjo marked this conversation as resolved.
Show resolved Hide resolved

return () => {
document.removeEventListener('keydown', handleKeyDown)
}
}, [hide, onClose])

return (
<Layer
onBackdropClick={onClose}
Expand Down
42 changes: 41 additions & 1 deletion components/modal/src/modal/modal.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
import { render, screen } from '@testing-library/react'
import { render, screen, fireEvent } from '@testing-library/react'
import React from 'react'
import { CloseButton } from './close-button.js'
import { Modal } from './modal.js'

describe('Modal', () => {
describe('Modal Accessibility', () => {
it('closes when ESC key is pressed', () => {
const onCloseMock = jest.fn()
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned in the Split Button-PR I think it would make sense to add some test-cases with button component that also handle "ESC keys". Eg. Selects. And test that the modal is not closed if esc is pressed when that is open.

render(<Modal onClose={onCloseMock} />)

const modalElement = screen.getByRole('dialog')
fireEvent.keyDown(modalElement, { key: 'Escape', code: 'Escape' })
expect(onCloseMock).toHaveBeenCalled()
})

it('does not close when "Enter" is pressed', () => {
const onCloseMock = jest.fn()
render(<Modal onClose={onCloseMock} />)

const modalElement = screen.getByRole('dialog')
fireEvent.keyDown(modalElement, { key: 'Enter', code: 'Enter' })
expect(onCloseMock).not.toHaveBeenCalled()
})

it('does not close when "SpaceBar" is pressed', () => {
const onCloseMock = jest.fn()
render(<Modal onClose={onCloseMock} />)

const modalElement = screen.getByRole('dialog')
fireEvent.keyDown(modalElement, { key: ' ', code: ' ' })
expect(onCloseMock).not.toHaveBeenCalled()
})

it('has a close button with proper accessibility attributes', async () => {
render(<CloseButton />)
const closeButton = await screen.findByLabelText(
'Close modal dialog'
)

expect(closeButton).toBeInTheDocument()
expect(closeButton.tagName).toBe('BUTTON')
})
})

describe('Regular dimensions', () => {
it('has the correct dimension styles in its default state', () => {
render(<Modal />)
Expand Down
Loading
Loading