docbrown/app/javascript/crayons/Modal/Modal.jsx
Suzanne Aitchison 5bed8f56d4
improve keyboard accessibility of modals 12427 10610 (#12511)
* adds focusTrap wrapper to preact Modal component

* add view specific code for focus trap in admin add nav link

* add script to return a focustrap toggle, use in add nav link modal partial

* add trap to edit nav link modal

* add handlers for sign up modal

* update modal controller for admin section, update nav link modals to use

* update other admin modals with new data values for trap

* remove unneeded erb script file

* remove unneeded target

* refactor to remove extra unneeded param

* remove duplicate code, store getFocusTrapToggle in window

* trap focus in comment and bookmark showModal instances for not logged in user

* remove need for activator id

* clean up id refs no longer needed

* remove custom code and re-use focsu-trap lib

* update storybook docs

* update default export in focusTrap

* prevent close button click triggering a modal toggle twice

* ensure if user navigates from a modal the trap is deactivated

* add jsdoc comments and add dynamic import

* ensure admin controller modal traps are cleaned up on disconnect

* update sign up modal to use crayons

* update modal controller and admin nav links modals to use preact modal

* update profile fields modals for new controller

* tweak styling of sign up and admin modals to match previous

* update listings modal to use crayons modal, adapt focus trap to work with click outside

* memoize deactivate callback to ensure modal can be presented on first page load

* add missed focustrap changes

* fix focus trap issues in onboarding flow

* refactor onboarding focus trap, remove getFocusTrapToggle

* tweaks for styling and article modal toggle

* add click outside tests to modal

* add cypress tests for the login modal

* update liquid tag tests affected by change

* refactors to address review comments

* fix issue with login modal presented twice on comment add

* change ids to selectors in admin modals

* small pr comment refactors

* add listings e2e tests

* add nav link modal tests

* fix issue with help modal

* tweak to fix ui bug from merge

* remove context from showLoginModal

* rename toggleModal

* rename state property for clarity

Co-authored-by: Nick Taylor <nick@dev.to>
2021-02-24 16:01:10 +00:00

105 lines
2.6 KiB
JavaScript

import { h } from 'preact';
import PropTypes from 'prop-types';
import { FocusTrap } from '../../shared/components/focusTrap';
import { defaultChildrenPropTypes } from '../../common-prop-types';
import { Button } from '@crayons';
function getAdditionalClassNames({ size, className }) {
let additionalClassNames = '';
if (size && size.length > 0 && size !== 'default') {
additionalClassNames += ` crayons-modal--${size}`;
}
if (className && className.length > 0) {
additionalClassNames += ` ${className}`;
}
return additionalClassNames;
}
const CloseIcon = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
className="crayons-icon"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="714d29e78a3867c79b07f310e075e824"
>
<title id="714d29e78a3867c79b07f310e075e824">Close</title>
<path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636l4.95 4.95z" />
</svg>
);
export const Modal = ({
children,
size = 'default',
className,
title,
overlay,
onClose,
closeOnClickOutside = false,
}) => {
return (
<FocusTrap
onDeactivate={onClose}
clickOutsideDeactivates={closeOnClickOutside}
>
<div
data-testid="modal-container"
className={`crayons-modal${getAdditionalClassNames({
size,
className,
})}`}
>
<div
role="dialog"
aria-modal="true"
aria-label="modal"
className="crayons-modal__box"
>
{title && (
<div className="crayons-modal__box__header">
<h2>{title}</h2>
<Button
icon={CloseIcon}
variant="ghost"
contentType="icon"
aria-label="Close"
onClick={onClose}
/>
</div>
)}
<div className="crayons-modal__box__body">{children}</div>
</div>
{overlay && (
<div
data-testid="modal-overlay"
className={`crayons-modal__overlay ${
closeOnClickOutside ? 'background-clickable' : ''
}`}
/>
)}
</div>
</FocusTrap>
);
};
Modal.displayName = 'Modal';
Modal.defaultProps = {
className: undefined,
overlay: true,
onClose: undefined,
};
Modal.propTypes = {
children: defaultChildrenPropTypes.isRequired,
className: PropTypes.string,
title: PropTypes.string.isRequired,
overlay: PropTypes.bool,
onClose: PropTypes.func,
size: PropTypes.oneOf(['default', 's', 'm']).isRequired,
};