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

feat(Modal): Convert Modal component to typescript #1942

Merged
merged 4 commits into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions packages/patternfly-4/react-core/src/components/Modal/Modal.d.ts

This file was deleted.

113 changes: 0 additions & 113 deletions packages/patternfly-4/react-core/src/components/Modal/Modal.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: 'Modal'
cssPrefix: 'pf-c-modal-box'
typescript: true
---

import { Modal, Button } from '@patternfly/react-core';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Modal from './Modal';
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import { KEY_CODES } from '../../helpers/constants';
import { css } from '../../../../react-styles/dist/js';
import styles from '@patternfly/patternfly/components/Backdrop/backdrop.css';

import { Modal } from './Modal';

jest.spyOn(document, 'createElement');
jest.spyOn(document, 'addEventListener');

Expand All @@ -24,15 +25,17 @@ test('Modal creates a container element once for div', () => {

test('modal closes with escape', () => {
shallow(<Modal {...props} isOpen />);
const [event, handler] = document.addEventListener.mock.calls[0];
const mock: any = (document.addEventListener as any).mock;
const [event, handler] = 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(<Modal {...props} />);
const [event, handler] = document.addEventListener.mock.calls[0];
const mock: any = (document.addEventListener as any).mock;
const [event, handler] = mock.calls[0];
expect(event).toBe('keydown');
handler({ keyCode: KEY_CODES.ESCAPE_KEY });
expect(props.onClose).not.toBeCalled();
Expand All @@ -41,7 +44,7 @@ test('modal does not call onClose for esc key if it is not open', () => {
test('Each modal is given a new id', () => {
const first = shallow(<Modal {...props} />);
const second = shallow(<Modal {...props} />);
expect(first.instance().id).not.toBe(second.instance().id);
expect((first.instance() as any).id).not.toBe((second.instance() as any).id);
});

test('modal removes body backdropOpen class when removed', () => {
Expand Down
126 changes: 126 additions & 0 deletions packages/patternfly-4/react-core/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { canUseDOM } from 'exenv';

import { css } from '@patternfly/react-styles';
import styles from '@patternfly/patternfly/components/Backdrop/backdrop.css';

import { KEY_CODES } from '../../helpers/constants';
import { ModalContent } from './ModalContent';

export interface ModalProps extends React.HTMLProps<HTMLDivElement> {
/** Content rendered inside the Modal. */
children: React.ReactNode;
/** Additional classes added to the Modal */
className?: string;
/** Flag to show the modal */
isOpen?: boolean;
/** Content of the Modal Header */
title: string;
/** Flag to hide the title */
hideTitle?: boolean;
/** Id to use for Modal Box description */
ariaDescribedById?: string;
/** Action buttons to put in the Modal Footer */
actions?: any,
/** A callback for when the close button is clicked */
onClose?: () => void;
/** Default width of the Modal. */
width?: number | string;
/** Creates a large version of the Modal */
isLarge?: boolean;
/** Creates a small version of the Modal */
isSmall?: boolean;
}

interface ModalState {
container: HTMLElement;
}

export class Modal extends React.Component<ModalProps, ModalState> {
static currentId = 0;
id = '';
container?: HTMLDivElement = undefined;

static defaultProps = {
className: '',
isOpen: false,
hideTitle: false,
ariaDescribedById: '',
actions: [] as any[],
onClose: () => undefined as any,
isLarge: false,
isSmall: false
};

constructor(props: ModalProps) {
super(props);
const newId = Modal.currentId++;
this.id = `pf-modal-${newId}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

For AboutModal, I added this to state for lifecycle purposes instead of just this. Perhaps we should be consistent?

Copy link
Member Author

Choose a reason for hiding this comment

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

Personally I prefer it to just be an instance variable than on state. To me, the id is not stateful.


this.state = {
container: undefined
};
}


handleEscKeyClick = (event: KeyboardEvent): void => {
if (event.keyCode === KEY_CODES.ESCAPE_KEY && this.props.isOpen) {
this.props.onClose();
}
};

toggleSiblingsFromScreenReaders = (hide: boolean) => {
const bodyChildren = document.body.children;
for (const child of Array.from(bodyChildren)) {
if (child !== this.container) {
hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
}
}
};

componentDidMount() {
const container = document.createElement('div');
this.setState({ container });
document.body.appendChild(container);
document.addEventListener('keydown', this.handleEscKeyClick, false);

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

componentDidUpdate() {
if (this.props.isOpen) {
document.body.classList.add(css(styles.backdropOpen));
this.toggleSiblingsFromScreenReaders(true);
} else {
document.body.classList.remove(css(styles.backdropOpen));
this.toggleSiblingsFromScreenReaders(false);
}
}

componentWillUnmount() {
if (this.container) {
document.body.removeChild(this.container);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the default export all the way at the bottom.

document.removeEventListener('keydown', this.handleEscKeyClick, false);
document.body.classList.remove(css(styles.backdropOpen));
}

render() {
const { ...props } = this.props;
const { container } = this.state;

if (!canUseDOM || !container) {
return null;
}

return ReactDOM.createPortal(
<ModalContent {...props} title={this.props.title} id={this.id} ariaDescribedById={this.props.ariaDescribedById}/>,
container
);
}
}

This file was deleted.

44 changes: 0 additions & 44 deletions packages/patternfly-4/react-core/src/components/Modal/ModalBox.js

This file was deleted.

Loading