131 lines
3.7 KiB
TypeScript
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();
|
|
}
|
|
};
|
|
}
|