Skip to content
Permalink
Browse files

feat(aboutmodal,wizard): append component to any element in DOM

Signed-off-by: Boaz Shuster <boaz.shuster.github@gmail.com>
  • Loading branch information
boaz0 committed Oct 8, 2019
1 parent 293b23b commit cabea3a078f77107d414aff2bbb85e4752e7511c
@@ -6,7 +6,7 @@ import { KEY_CODES } from '../../helpers/constants';

const mockListener = jest.spyOn(ReactDOM, 'createPortal');
jest.spyOn(document, 'createElement');
jest.spyOn(document, 'addEventListener');
jest.spyOn(document.body, 'addEventListener');

mockListener.mockImplementation(node => node as React.ReactPortal);

@@ -34,15 +34,15 @@ test('About Modal closes with escape', () => {
Test About Modal
</AboutModal>
);
const [event, handler] = (document.addEventListener as any).mock.calls[0];
const [event, handler] = (document.body.addEventListener as any).mock.calls[0];
expect(event).toBe('keydown');
handler({ keyCode: KEY_CODES.ESCAPE_KEY });
expect(props.onClose).toBeCalled();
});

test('modal does not call onClose for esc key if it is not open', () => {
shallow(<AboutModal {...props} />);
const [event, handler] = (document.addEventListener as any).mock.calls[0];
const [event, handler] = (document.body.addEventListener as any).mock.calls[0];
expect(event).toBe('keydown');
handler({ keyCode: KEY_CODES.ESCAPE_KEY });
expect(props.onClose).not.toBeCalled();
@@ -27,6 +27,8 @@ export interface AboutModalProps {
backgroundImageSrc?: string;
/** Prevents the about modal from rendering content inside a container; allows for more flexible layouts */
noAboutModalBoxContentContainer?: boolean;
/** The parent container to append the modal to. Defaults to document.body */
appendTo?: HTMLElement | (() => HTMLElement);
}

interface ModalState {
@@ -46,7 +48,8 @@ export class AboutModal extends React.Component<AboutModalProps, ModalState> {
productName: '',
trademark: '',
backgroundImageSrc: '',
noAboutModalBoxContentContainer: false
noAboutModalBoxContentContainer: false,
appendTo: document.body,
};

constructor(props: AboutModalProps) {
@@ -76,34 +79,45 @@ export class AboutModal extends React.Component<AboutModalProps, ModalState> {
}
};

getElement = (appendTo: HTMLElement | (() => HTMLElement)) => {
if (typeof appendTo === 'function') {
return appendTo();
}
return appendTo;
};

componentDidMount() {
const container = document.createElement('div');
const target: HTMLElement = this.getElement(this.props.appendTo);
this.setState({ container });
document.body.appendChild(container);
document.addEventListener('keydown', this.handleEscKeyClick, false);
target.appendChild(container);
target.addEventListener('keydown', this.handleEscKeyClick, false);

if (this.props.isOpen) {
document.body.classList.add(css(styles.backdropOpen));
target.classList.add(css(styles.backdropOpen));
} else {
document.body.classList.remove(css(styles.backdropOpen));
target.classList.remove(css(styles.backdropOpen));
}
}

componentDidUpdate() {
const target: HTMLElement = this.getElement(this.props.appendTo);
if (this.props.isOpen) {
document.body.classList.add(css(styles.backdropOpen));
target.classList.add(css(styles.backdropOpen));
this.toggleSiblingsFromScreenReaders(true);
} else {
document.body.classList.remove(css(styles.backdropOpen));
target.classList.remove(css(styles.backdropOpen));
this.toggleSiblingsFromScreenReaders(false);
}
}

componentWillUnmount() {
const target: HTMLElement = this.getElement(this.props.appendTo);
if (this.state.container) {
document.body.removeChild(this.state.container);
target.removeChild(this.state.container);
}
document.removeEventListener('keydown', this.handleEscKeyClick, false);
target.removeEventListener('keydown', this.handleEscKeyClick, false);
target.classList.remove(css(styles.backdropOpen));
}

render() {
@@ -94,6 +94,8 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
cancelButtonText?: string;
/** (Unused if footer is controlled) aria-label for the close button */
ariaLabelCloseButton?: string;
/** The parent container to append the modal to. Defaults to document.body */
appendTo?: HTMLElement | (() => HTMLElement);
}

interface WizardState {
@@ -125,7 +127,8 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
width: null as string,
height: null as string,
footer: null as React.ReactNode,
onClose: () => undefined as any
onClose: () => undefined as any,
appendTo: document.body,
};
private container: HTMLDivElement;
private titleId: string;
@@ -150,6 +153,13 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
};
}

private getElement = (appendTo: HTMLElement | (() => HTMLElement)) => {
if (typeof appendTo === 'function') {
return appendTo();
}
return appendTo;
};

private handleKeyClicks = (event: KeyboardEvent): void => {
if (event.keyCode === KEY_CODES.ESCAPE_KEY) {
if (this.state.isNavOpen) {
@@ -161,7 +171,8 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
};

private toggleSiblingsFromScreenReaders = (hide: boolean): void => {
const bodyChildren = document.body.children;
const target: HTMLElement = this.getElement(this.props.appendTo);
const bodyChildren = target.children;
for (const child of Array.from(bodyChildren)) {
if (child !== this.container) {
hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
@@ -296,21 +307,25 @@ export class Wizard extends React.Component<WizardProps, WizardState> {

componentDidMount() {
if (this.isModal) {
const target: HTMLElement = this.getElement(this.props.appendTo);
this.container = document.createElement('div');
target.appendChild(this.container);
if (this.container) {
document.body.appendChild(this.container);
target.appendChild(this.container);
}
this.toggleSiblingsFromScreenReaders(true);
document.addEventListener('keydown', this.handleKeyClicks, false);
target.addEventListener('keydown', this.handleKeyClicks, false);
}
}

componentWillUnmount() {
if (this.isModal) {
const target: HTMLElement = this.getElement(this.props.appendTo);
if (this.container) {
document.body.removeChild(this.container);
target.removeChild(this.container);
}
this.toggleSiblingsFromScreenReaders(false);
document.removeEventListener('keydown', this.handleKeyClicks, false);
target.removeEventListener('keydown', this.handleKeyClicks, false);
}
}

0 comments on commit cabea3a

Please sign in to comment.
You can’t perform that action at this time.