diff --git a/packages/patternfly-4/react-core/src/components/Modal/Modal.d.ts b/packages/patternfly-4/react-core/src/components/Modal/Modal.d.ts deleted file mode 100644 index df67285dc8d..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/Modal.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalProps extends HTMLProps { - actions?: any, - children: ReactNode; - className?: string; - hideTitle?: boolean; - ariaDescribedById?: string; - isLarge?: boolean; - isSmall?: boolean; - isOpen?: boolean; - onClose?: Function; - title: string; -} - -declare const Modal: FunctionComponent; - -export default Modal; diff --git a/packages/patternfly-4/react-core/src/components/Modal/Modal.js b/packages/patternfly-4/react-core/src/components/Modal/Modal.js deleted file mode 100644 index 1e334c0e48e..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/Modal.js +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import ModalContent from './ModalContent'; -import { canUseDOM } from 'exenv'; -import { KEY_CODES } from '../../helpers/constants'; -import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/patternfly/components/Backdrop/backdrop.css'; - -const propTypes = { - /** content rendered inside the Modal. */ - children: PropTypes.node.isRequired, - /** additional classes added to the Modal */ - className: PropTypes.string, - /** Flag to show the modal */ - isOpen: PropTypes.bool, - /** Content of the Modal Header */ - title: PropTypes.string.isRequired, - /** Flag to show the title */ - hideTitle: PropTypes.bool, - /** id to use for Modal Box description */ - ariaDescribedById: PropTypes.string, - /** Action buttons to put in the Modal Footer */ - actions: PropTypes.any, - /** A callback for when the close button is clicked */ - onClose: PropTypes.func, - /** Default width of the Modal. */ - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - /** Creates a large version of the Modal */ - isLarge: PropTypes.bool, - /** Creates a small version of the Modal */ - isSmall: PropTypes.bool, - /** Additional props are passed and spread in the Modal body container
*/ - '': PropTypes.any -}; - -const defaultProps = { - width: null, - className: '', - isOpen: false, - hideTitle: false, - ariaDescribedById: '', - actions: [], - onClose: () => undefined, - isLarge: false, - isSmall: false -}; - -let currentId = 0; - -class Modal extends React.Component { - static propTypes = propTypes; - static defaultProps = defaultProps; - - id = `pf-modal-${currentId++}`; - - handleEscKeyClick = event => { - if (event.keyCode === KEY_CODES.ESCAPE_KEY && this.props.isOpen) { - this.props.onClose(); - } - }; - - toggleSiblingsFromScreenReaders = hide => { - const bodyChildren = document.body.children; - for (const child of bodyChildren) { - if (child !== this.container) { - hide ? child.setAttribute('aria-hidden', hide) : child.removeAttribute('aria-hidden'); - } - } - }; - - componentDidMount() { - document.body.appendChild(this.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() { - document.body.removeChild(this.container); - document.removeEventListener('keydown', this.handleEscKeyClick, false); - document.body.classList.remove(css(styles.backdropOpen)); - } - - render() { - const { ...props } = this.props; - - if (!canUseDOM) { - return null; - } - - if (!this.container) { - this.container = document.createElement('div'); - } - - return ReactDOM.createPortal(, this.container); - } -} - -export default Modal; diff --git a/packages/patternfly-4/react-core/src/components/Modal/Modal.md b/packages/patternfly-4/react-core/src/components/Modal/Modal.md index a108a001d10..541d80bc182 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/Modal.md +++ b/packages/patternfly-4/react-core/src/components/Modal/Modal.md @@ -1,6 +1,7 @@ --- title: 'Modal' cssPrefix: 'pf-c-modal-box' +typescript: true --- import { Modal, Button } from '@patternfly/react-core'; diff --git a/packages/patternfly-4/react-core/src/components/Modal/Modal.test.js b/packages/patternfly-4/react-core/src/components/Modal/Modal.test.tsx similarity index 81% rename from packages/patternfly-4/react-core/src/components/Modal/Modal.test.js rename to packages/patternfly-4/react-core/src/components/Modal/Modal.test.tsx index aee8c6ea051..1a9ab0fc512 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/Modal.test.js +++ b/packages/patternfly-4/react-core/src/components/Modal/Modal.test.tsx @@ -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'); @@ -24,7 +25,8 @@ test('Modal creates a container element once for div', () => { test('modal closes with escape', () => { shallow(); - 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(); @@ -32,7 +34,8 @@ test('modal closes with escape', () => { test('modal does not call onClose for esc key if it is not open', () => { shallow(); - 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(); @@ -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(); const second = shallow(); - 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', () => { diff --git a/packages/patternfly-4/react-core/src/components/Modal/Modal.tsx b/packages/patternfly-4/react-core/src/components/Modal/Modal.tsx new file mode 100644 index 00000000000..1f8044ec37f --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/Modal.tsx @@ -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 { + /** 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 { + 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}`; + + 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); + } + 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( + , + container + ); + } +} diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.d.ts deleted file mode 100644 index e530226ecfa..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalBoxProps extends HTMLProps { - children: ReactNode; - className?: string; - isLarge?: boolean; - isSmall?:boolean; - title: string; - id: string; -} - -declare const ModalBox: FunctionComponent; - -export default ModalBox; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.js deleted file mode 100644 index f4f325bf9bf..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import PropTypes from 'prop-types'; -import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; - -const propTypes = { - /** content rendered inside the ModalBox. */ - children: PropTypes.node.isRequired, - /** additional classes added to the ModalBox */ - className: PropTypes.string, - /** Creates a large version of the ModalBox */ - isLarge: PropTypes.bool, - /** Creates a small version of the ModalBox. */ - isSmall: PropTypes.bool, - /** string to use for Modal Box label */ - title: PropTypes.string.isRequired, - /** id to use for Modal Box description */ - id: PropTypes.string.isRequired, - /** Additional props are spread to the container
*/ - '': PropTypes.any -}; - -const defaultProps = { - className: '', - isLarge: false, - isSmall: false -}; - -const ModalBox = ({ children, className, isLarge, isSmall, title, id, ...props }) => ( -
- {children} -
-); -ModalBox.propTypes = propTypes; -ModalBox.defaultProps = defaultProps; - -export default ModalBox; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.tsx similarity index 58% rename from packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.js rename to packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.tsx index ba28d670681..1482af99cf2 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.js +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.test.tsx @@ -1,10 +1,11 @@ -import ModalBox from './ModalBox'; -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; +import { ModalBox } from './ModalBox'; + test('ModalBox Test', () => { const view = shallow( - + This is a ModalBox ); @@ -13,16 +14,16 @@ test('ModalBox Test', () => { test('ModalBox Test isLarge', () => { const view = shallow( - + This is a ModalBox ); expect(view).toMatchSnapshot(); }); -test('ModalBox Test isOpen', () => { +test('ModalBox Test isSmall', () => { const view = shallow( - + This is a ModalBox ); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBox.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.tsx new file mode 100644 index 00000000000..77dd28737b0 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBox.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; + +export interface ModalBoxProps extends React.HTMLProps { + /** Content rendered inside the ModalBox. */ + children: React.ReactNode; + /** Additional classes added to the ModalBox */ + className?: string; + /** Creates a large version of the ModalBox */ + isLarge?: boolean; + /** Creates a small version of the ModalBox. */ + isSmall?: boolean; + /** String to use for Modal Box aria-label */ + title: string; + /** Id to use for Modal Box description */ + id: string; +} + +export const ModalBox: React.FunctionComponent = ({ + children, + className = '', + isLarge = false, + isSmall = false, + title, + id, + ...props +}: ModalBoxProps) => ( +
+ {children} +
+); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.d.ts deleted file mode 100644 index d20298d5d10..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalBoxBodyProps extends HTMLProps { - children?: ReactNode; - className?: string; - id?: string; -} - -declare const ModalBoxBody: FunctionComponent; - -export default ModalBoxBody; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.js deleted file mode 100644 index d12b7fac3c2..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import PropTypes from 'prop-types'; -import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; - -const propTypes = { - /** content rendered inside the ModalBoxBody */ - children: PropTypes.node, - /** additional classes added to the ModalBoxBody */ - className: PropTypes.string, - /** id of the ModalBoxBody */ - id: PropTypes.string.isRequired, - /** Additional props are spread to the container
*/ - '': PropTypes.any -}; - -const defaultProps = { - children: null, - className: '' -}; - -const ModalBoxBody = ({ children, className, id, ...props }) => ( -
- {children} -
-); - -ModalBoxBody.propTypes = propTypes; -ModalBoxBody.defaultProps = defaultProps; - -export default ModalBoxBody; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.js deleted file mode 100644 index 957af49b8aa..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import ModalBoxBody from './ModalBoxBody'; -import React from 'react'; -import { shallow } from 'enzyme'; - -test('ModalBoxBody Test', () => { - const view = shallow(This is a ModalBox header); - expect(view).toMatchSnapshot(); -}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.tsx new file mode 100644 index 00000000000..dfc6363197f --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.test.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import { shallow } from 'enzyme'; + +import { ModalBoxBody } from './ModalBoxBody'; + +test('ModalBoxBody Test', () => { + const view = shallow(This is a ModalBox header); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.tsx new file mode 100644 index 00000000000..ff46c57a751 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxBody.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; + +export interface ModalBoxBodyProps extends React.HTMLProps { + /** Content rendered inside the ModalBoxBody */ + children?: React.ReactNode; + /** Additional classes added to the ModalBoxBody */ + className?: string; +} + +export const ModalBoxBody: React.FunctionComponent = ({ + children = null, + className = '', + ...props +}: ModalBoxBodyProps) => ( +
+ {children} +
+); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.d.ts deleted file mode 100644 index 8fd5bfbef67..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FunctionComponent, HTMLProps } from 'react'; - -export interface ModalBoxCloseButtonProps extends HTMLProps { - className?: string; - onClose?: Function; -} - -declare const ModalBoxCloseButton: FunctionComponent; - -export default ModalBoxCloseButton; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.js deleted file mode 100644 index 74189d052ab..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Button } from '../Button'; -import { TimesIcon } from '@patternfly/react-icons'; - -const propTypes = { - /** additional classes added to the close button */ - className: PropTypes.string, - /** A callback for when the close button is clicked */ - onClose: PropTypes.func, - /** Additional props are spread to the container
*/ - '': PropTypes.any -}; - -const defaultProps = { - className: '', - onClose: () => undefined -}; - -const ModalBoxCloseButton = ({ className, onClose, ...props }) => ( - - - -); - -ModalBoxCloseButton.propTypes = propTypes; -ModalBoxCloseButton.defaultProps = defaultProps; - -export default ModalBoxCloseButton; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.js deleted file mode 100644 index cc3908e8f2a..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import ModalBoxCloseButton from './ModalBoxCloseButton'; - -test('ModalBoxCloseButton Test', () => { - const view = shallow(); - expect(view).toMatchSnapshot(); -}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.tsx new file mode 100644 index 00000000000..401a27cbb78 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.test.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { ModalBoxCloseButton } from './ModalBoxCloseButton'; + +test('ModalBoxCloseButton Test', () => { + const mockfn = jest.fn(); + const view = shallow(); + expect(view).toMatchSnapshot(); + view + .find('.test-box-close-button-class') + .at(0) + .simulate('click'); + expect(mockfn.mock.calls).toHaveLength(1); +}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.tsx new file mode 100644 index 00000000000..9bbfec8d900 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxCloseButton.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { Button } from '../Button'; +import { TimesIcon } from '@patternfly/react-icons'; + +export interface ModalBoxCloseButtonProps { + /** Additional classes added to the close button */ + className?: string; + /** A callback for when the close button is clicked */ + onClose?: () => void; +} + +export const ModalBoxCloseButton: React.FunctionComponent = ({ + className = '', + onClose = () => undefined as any, + ...props +}: ModalBoxCloseButtonProps) => ( + + + +); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.d.ts deleted file mode 100644 index ec57955891f..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalBoxFooterProps extends HTMLProps { - children?: ReactNode; - className?: string; -} - -declare const ModalBoxFooter: FunctionComponent; - -export default ModalBoxFooter; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.js deleted file mode 100644 index 28523773b76..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import PropTypes from 'prop-types'; -import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; -// const styles = {}; -const propTypes = { - /** content rendered inside the Footer */ - children: PropTypes.node, - /** additional classes added to the Footer */ - className: PropTypes.string, - /** Additional props are spread to the container
*/ - '': PropTypes.any -}; - -const defaultProps = { - children: null, - className: '' -}; - -const ModalBoxFooter = ({ children, className, ...props }) => ( -
- {children} -
-); - -ModalBoxFooter.propTypes = propTypes; -ModalBoxFooter.defaultProps = defaultProps; - -export default ModalBoxFooter; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.js deleted file mode 100644 index 06b54a383a0..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import ModalBoxFooter from './ModalBoxFooter'; - -test('ModalBoxFooter Test', () => { - const view = shallow(This is a ModalBox Footer); - expect(view).toMatchSnapshot(); -}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.tsx new file mode 100644 index 00000000000..9302a0e5e30 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.test.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { ModalBoxFooter } from './ModalBoxFooter'; + +test('ModalBoxFooter Test', () => { + const view = shallow(This is a ModalBox Footer); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.tsx new file mode 100644 index 00000000000..16a804c38ee --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxFooter.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/patternfly/components/ModalBox/modal-box.css'; + +export interface ModalBoxFooterProps { + /** Content rendered inside the Footer */ + children?: React.ReactNode; + /** Additional classes added to the Footer */ + className?: string; +} + +export const ModalBoxFooter: React.FunctionComponent = ({ + children = null, + className = '', + ...props +}: ModalBoxFooterProps) => ( +
+ {children} +
+); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.d.ts deleted file mode 100644 index 705ba39c693..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalBoxHeaderProps extends HTMLProps { - children?: ReactNode; - className?: string; - hideTitle: boolean; -} - -declare const ModalBoxHeader: FunctionComponent; - -export default ModalBoxHeader; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.js deleted file mode 100644 index f19910f07a5..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Title } from '../Title'; -import accessibleStyles from '@patternfly/patternfly/utilities/Accessibility/accessibility.css'; -import { css } from '@patternfly/react-styles'; - -const propTypes = { - /** content rendered inside the Header */ - children: PropTypes.node, - /** additional classes added to the button */ - className: PropTypes.string, - /** Flag to show the title */ - hideTitle: PropTypes.bool.isRequired, - /** Additional props are spread to the container
*/ - '': PropTypes.any -}; - -const defaultProps = { - children: null, - className: '' -}; - -const ModalBoxHeader = ({ hideTitle, children, className, ...props }) => { - const hidden = hideTitle ? css(accessibleStyles.screenReader) : ''; - - return - - {children} - - ; -}; - -ModalBoxHeader.propTypes = propTypes; -ModalBoxHeader.defaultProps = defaultProps; - -export default ModalBoxHeader; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.js deleted file mode 100644 index de34a583334..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import ModalBoxHeader from './ModalBoxHeader'; -import React from 'react'; -import { shallow } from 'enzyme'; - -test('ModalBoxHeader Test', () => { - const view = shallow(This is a ModalBox header); - expect(view).toMatchSnapshot(); -}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.tsx new file mode 100644 index 00000000000..6910780229f --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.test.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { shallow } from 'enzyme'; + +import {TitleLevel} from '../Title'; +import { ModalBoxHeader } from './ModalBoxHeader'; + +test('ModalBoxHeader Test', () => { + const view = shallow(This is a ModalBox header); + expect(view).toMatchSnapshot(); +}); + +test('ModalBoxHeader Test with H3', () => { + const view = shallow(This is a ModalBox header); + expect(view).toMatchSnapshot(); +}); + +test('ModalBoxHeader Test hideTitle', () => { + const view = shallow(This is a ModalBox header); + expect(view).toMatchSnapshot(); +}); diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.tsx new file mode 100644 index 00000000000..4b49783d4ab --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalBoxHeader.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import accessibleStyles from '@patternfly/patternfly/utilities/Accessibility/accessibility.css'; +import { css } from '@patternfly/react-styles'; + +import { Title, TitleLevel } from '../Title'; + +export interface ModalBoxHeaderProps { + /** Content rendered inside the Header */ + children?:React.ReactNode; + /** Additional classes added to the button */ + className?: string; + /** Flag to hide the title */ + hideTitle?: boolean; + /** The heading level to use */ + headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; +} + +export const ModalBoxHeader: React.FunctionComponent = ({ + children = null, + className = '', + hideTitle = false, + headingLevel = TitleLevel.h1, + ...props +}: ModalBoxHeaderProps) => { + const hidden = hideTitle ? css(accessibleStyles.screenReader) : ''; + + return ( + + + {children} + + + ); +}; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.d.ts b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.d.ts deleted file mode 100644 index 8af7a769307..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { FunctionComponent, HTMLProps, ReactNode } from 'react'; - -export interface ModalContentProps extends HTMLProps { - children: ReactNode; - className?: string; - isLarge?: boolean; - isSmall?: boolean; - isOpen?: boolean; - title: string; - hideTitle?: boolean; - actions?: any, - onClose?: Function; - ariaDescribedById?: string; - id: string; -} - -declare const ModalContent: FunctionComponent; - -export default ModalContent; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.js b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.js deleted file mode 100644 index cc0db775152..00000000000 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.js +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import FocusTrap from 'focus-trap-react'; -import ModalBoxBody from './ModalBoxBody'; -import ModalBoxHeader from './ModalBoxHeader'; -import ModalBoxHCloseButton from './ModalBoxCloseButton'; -import ModalBox from './ModalBox'; -import ModalBoxFooter from './ModalBoxFooter'; -import Backdrop from '../Backdrop/Backdrop'; -import bullseyeStyle from '@patternfly/patternfly/layouts/Bullseye/bullseye.css'; -import { css } from '@patternfly/react-styles'; - -const propTypes = { - /** content rendered inside the Modal. */ - children: PropTypes.node.isRequired, - /** additional classes added to the button */ - className: PropTypes.string, - /** Flag to show the modal */ - isOpen: PropTypes.bool, - /** Content of the Modal Header */ - title: PropTypes.string.isRequired, - /** Flag to show the title */ - hideTitle: PropTypes.bool, - /** Content of the Modal Footer */ - actions: PropTypes.any, - /** A callback for when the close button is clicked */ - onClose: PropTypes.func, - /** Default width of the Modal. */ - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - /** Creates a large version of the Modal */ - isLarge: PropTypes.bool, - /** Creates a small version of the Modal */ - isSmall: PropTypes.bool, - /** id to use for Modal Box description */ - ariaDescribedById: PropTypes.string, - /** id of the ModalBoxBody */ - id: PropTypes.string.isRequired, - /** Additional props are spread to the ModalBoxBody component */ - '': PropTypes.any -}; - -const defaultProps = { - width: null, - className: '', - isOpen: false, - hideTitle: false, - actions: [], - onClose: () => undefined, - isLarge: false, - isSmall: false, - ariaDescribedById: '' -}; - -const ModalContent = ({ - children, - className, - isOpen, - title, - hideTitle, - actions, - onClose, - isLarge, - isSmall, - width, - ariaDescribedById, - id, - ...props -}) => { - const modalBoxHeader = {title} ; - const modalBoxFooter = actions.length > 0 && {actions} ; - if (!isOpen) { - return null; - } - return ( - - - - - {modalBoxHeader} - - {children} - - {modalBoxFooter} - - - - ); -}; - -ModalContent.propTypes = propTypes; -ModalContent.defaultProps = defaultProps; - -export default ModalContent; diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.js b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.tsx similarity index 65% rename from packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.js rename to packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.tsx index cc875ba96c3..b342bbb9cd8 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.js +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import* as React from 'react'; import { shallow } from 'enzyme'; -import ModalContent from './ModalContent'; + +import { ModalContent } from './ModalContent'; test('Modal Content Test only body', () => { const view = shallow( @@ -20,15 +21,6 @@ test('Modal Content Test isOpen', () => { expect(view).toMatchSnapshot(); }); -test('Modal Content Test with header', () => { - const view = shallow( - - This is a ModalBox header - - ); - expect(view).toMatchSnapshot(); -}); - test('Modal Content Test with footer', () => { const view = shallow( @@ -47,23 +39,13 @@ test('Modal Content test without footer', () => { expect(view).toMatchSnapshot(); }); -test('Modal Content Test with header and footer', () => { - const view = shallow( - - This is a ModalBox header - - ); - expect(view).toMatchSnapshot(); -}); - test('Modal Content Test with onclose', () => { const view = shallow( undefined} + onClose={() => undefined} id="id" isOpen > diff --git a/packages/patternfly-4/react-core/src/components/Modal/ModalContent.tsx b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.tsx new file mode 100644 index 00000000000..3818d9773b2 --- /dev/null +++ b/packages/patternfly-4/react-core/src/components/Modal/ModalContent.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; + +// Can't use ES6 imports :( +// The types for it are also wrong, we should probably ditch this dependency. +// tslint:disable-next-line +const FocusTrap: any = require('focus-trap-react'); + +import styles from '@patternfly/patternfly/layouts/Bullseye/bullseye.css'; +import { css } from '@patternfly/react-styles'; + +import Backdrop from '../Backdrop/Backdrop'; +import { ModalBoxBody } from './ModalBoxBody'; +import { ModalBoxHeader } from './ModalBoxHeader'; +import { ModalBoxCloseButton } from './ModalBoxCloseButton'; +import { ModalBox } from './ModalBox'; +import { ModalBoxFooter } from './ModalBoxFooter'; + +export interface ModalContentProps { + /** Content rendered inside the Modal. */ + children: React.ReactNode; + /** Additional classes added to the button */ + className?: string; + /** Creates a large version of the Modal */ + isLarge?: boolean; + /** Creates a small version of the Modal */ + isSmall?: boolean; + /** Flag to show the modal */ + isOpen?: boolean; + /** Content of the Modal Header */ + title: string; + /** Flag to show the title */ + hideTitle?: boolean; + /** Default width of the content. */ + width?: number | string; + /** Content of the Modal Footer */ + actions?: any, + /** A callback for when the close button is clicked */ + onClose?: () => void; + /** Id to use for Modal Box description */ + ariaDescribedById?: string; + /** Id of the ModalBoxBody */ + id: string; +} + +export const ModalContent: React.FunctionComponent = ({ + children, + className = '', + isOpen = false, + title, + hideTitle = false, + actions = [], + onClose = () => undefined as any, + isLarge = false, + isSmall = false, + width = -1, + ariaDescribedById = '', + id = '', + ...props +}) => { + const modalBoxHeader = {title} ; + const modalBoxFooter = actions.length > 0 && {actions} ; + if (!isOpen) { + return null; + } + + const boxStyle = width === -1 ? {} : { width }; + + return ( + + + + + {modalBoxHeader} + + {children} + + {modalBoxFooter} + + + + ); +}; diff --git a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.js.snap b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.tsx.snap similarity index 62% rename from packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.js.snap rename to packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.tsx.snap index 63c66c3aace..3bfd0883f55 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.js.snap +++ b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBox.test.tsx.snap @@ -2,11 +2,11 @@ exports[`ModalBox Test 1`] = `
This is a ModalBox
@@ -14,24 +14,23 @@ exports[`ModalBox Test 1`] = ` exports[`ModalBox Test isLarge 1`] = `
This is a ModalBox
`; -exports[`ModalBox Test isOpen 1`] = ` +exports[`ModalBox Test isSmall 1`] = `
This is a ModalBox
diff --git a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.js.snap b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.tsx.snap similarity index 72% rename from packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.js.snap rename to packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.tsx.snap index 3649bb4c14c..7281fa890ad 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.js.snap +++ b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxBody.test.tsx.snap @@ -2,7 +2,7 @@ exports[`ModalBoxBody Test 1`] = `
This is a ModalBox header diff --git a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.js.snap b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.tsx.snap similarity index 85% rename from packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.js.snap rename to packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.tsx.snap index 8b0d28b35a9..66ee86ad65a 100644 --- a/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.js.snap +++ b/packages/patternfly-4/react-core/src/components/Modal/__snapshots__/ModalBoxCloseButton.test.tsx.snap @@ -4,7 +4,7 @@ exports[`ModalBoxCloseButton Test 1`] = ` , + + ]} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + ); + } + + renderSmallModal() { + const {isSmallModalOpen} = this.state; + + return ( + + Cancel + , + + ]} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + ); + } + + renderLargeModal() { + const {isLargeModalOpen} = this.state; + + return ( + + Cancel + , + + ]} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + ); + } + + renderHalfWidthModal() { + const {isHalfWidthModalOpen} = this.state; + + return ( + + Cancel + , + + ]} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. + + ); + } + + renderNoHeaderModal() { + const {isNoHeaderModalOpen} = this.state; + + return ( + + Close + + ]} + > + + When static text describing the modal is available, it can be wrapped with an ID referring to the modal's + aria-describedby value. + {' '} + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + + ); + } + + render() { + + const buttonStyle = { + marginRight: 20, + marginBottom: 20 + }; + + return ( + +
+ + + + + +
+ {this.renderModal()} + {this.renderSmallModal()} + {this.renderLargeModal()} + {this.renderHalfWidthModal()} + {this.renderNoHeaderModal()} +
+ ); + } +} diff --git a/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/index.ts b/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/index.ts index 1686c765b48..cc6b29494d2 100644 --- a/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/index.ts +++ b/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/index.ts @@ -3,6 +3,7 @@ export * from './AvatarDemo/AvatarDemo'; export * from './BrandDemo/BrandDemo'; export * from './LabelDemo/LabelDemo'; export * from './LoginPageDemo/LoginPageDemo'; +export * from './ModalDemo/ModalDemo'; export * from './NavDemo/NavDemo'; export * from './PopoverDemo/PopoverDemo' export * from './TabsDemo/TabsDemo';