MyCrypto/common/components/ui/Modal/ModalBody.tsx

131 lines
3.7 KiB
TypeScript

import React, { CSSProperties } from 'react';
import closeIcon from 'assets/images/close.svg';
import { IButton } from 'components/ui/Modal';
interface Props {
title?: React.ReactNode;
children: React.ReactNode;
modalStyle?: CSSProperties;
hasButtons?: number;
buttons?: IButton[];
disableButtons?: boolean;
hideButtons?: boolean;
handleClose(): void;
}
export default class ModalBody extends React.Component<Props> {
public firstTabStop: HTMLElement;
private modal: HTMLElement;
private modalContent: HTMLElement;
private focusedElementBeforeModal: HTMLElement;
private lastTabStop: HTMLElement;
public componentDidMount() {
this.focusedElementBeforeModal = document.activeElement as HTMLElement;
// Find all focusable children
const focusableElementsString =
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
const focusableElements = Array.prototype.slice.call(
this.modal.querySelectorAll(focusableElementsString)
);
// Convert NodeList to Array
this.firstTabStop = focusableElements[0];
this.lastTabStop = focusableElements[focusableElements.length - 1];
this.modal.addEventListener('keydown', this.keyDownListener);
}
public componentWillUnmount() {
document.removeEventListener('keydown', this.keyDownListener);
}
public scrollContentToTop = () => {
this.modalContent.scrollTop = 0;
};
public render() {
const { title, children, modalStyle, hasButtons, hideButtons, handleClose } = this.props;
return (
<div
className="Modal"
style={modalStyle}
role="dialog"
aria-labelledby="Modal-header-title"
ref={div => {
this.modal = div as HTMLElement;
}}
>
{title && (
<div className="Modal-header flex-wrapper">
<h2 className="Modal-header-title">{title}</h2>
<div className="flex-spacer" />
<button className="Modal-header-close" aria-label="Close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} alt="Close" />
</button>
</div>
)}
<div className="Modal-content" ref={div => (this.modalContent = div as HTMLElement)}>
{children}
<div className={`Modal-fade ${!hasButtons || hideButtons ? 'has-no-footer' : ''}`} />
</div>
{hasButtons && !hideButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>
);
}
private renderButtons = () => {
const { disableButtons, buttons } = this.props;
if (!buttons || !buttons.length) {
return;
}
return buttons.map((btn, idx: number) => {
let btnClass = 'Modal-footer-btn btn';
if (btn.type) {
btnClass += ` btn-${btn.type}`;
}
return (
<button
className={btnClass}
onClick={btn.onClick}
key={idx}
disabled={disableButtons || btn.disabled}
>
{btn.text}
</button>
);
});
};
private keyDownListener = (e: KeyboardEvent) => {
// Check for TAB key press
if (e.keyCode === 9) {
// SHIFT + TAB
if (e.shiftKey) {
if (document.activeElement === this.firstTabStop) {
e.preventDefault();
this.lastTabStop.focus();
}
// TAB
} else {
if (document.activeElement === this.lastTabStop) {
e.preventDefault();
this.firstTabStop.focus();
}
}
}
// Check for ESC key press
if (e.keyCode === 27) {
this.focusedElementBeforeModal.focus();
this.props.handleClose();
}
};
}