diff --git a/packages/react-core/src/components/Modal/Modal.tsx b/packages/react-core/src/components/Modal/Modal.tsx index c88e3562625..00427055c2e 100644 --- a/packages/react-core/src/components/Modal/Modal.tsx +++ b/packages/react-core/src/components/Modal/Modal.tsx @@ -44,6 +44,8 @@ export interface ModalProps extends React.HTMLProps { appendTo?: HTMLElement | (() => HTMLElement); /** Flag to disable focus trap */ disableFocusTrap?: boolean; + /** Description of the modal */ + description?: React.ReactNode; } interface ModalState { diff --git a/packages/react-core/src/components/Modal/ModalContent.tsx b/packages/react-core/src/components/Modal/ModalContent.tsx index d6412558875..e7e3e0fa319 100644 --- a/packages/react-core/src/components/Modal/ModalContent.tsx +++ b/packages/react-core/src/components/Modal/ModalContent.tsx @@ -7,6 +7,7 @@ const FocusTrap: any = require('focus-trap-react'); import styles from '@patternfly/react-styles/css/layouts/Bullseye/bullseye'; import titleStyles from '@patternfly/react-styles/css/components/Title/title'; +import modalStyles from '@patternfly/react-styles/css/components/ModalBox/modal-box'; import { css } from '@patternfly/react-styles'; import { Backdrop } from '../Backdrop/Backdrop'; @@ -29,6 +30,8 @@ export interface ModalContentProps { isOpen?: boolean; /** Complex header (more than just text), supersedes title for header content */ header?: React.ReactNode; + /** Description of the modal */ + description?: React.ReactNode; /** Simple text content of the Modal Header, also used for aria-label on the body */ title: string; /** Flag to show the title (ignored for custom headers) */ @@ -58,6 +61,7 @@ export const ModalContent: React.FunctionComponent = ({ className = '', isOpen = false, header = null, + description = null, title, hideTitle = false, showClose = true, @@ -100,7 +104,12 @@ export const ModalContent: React.FunctionComponent = ({ > {showClose && } {modalBoxHeader} - + {description && ( +
+ {description} +
+ )} + {children} {modalBoxFooter} diff --git a/packages/react-core/src/components/Modal/__tests__/ModalContent.test.tsx b/packages/react-core/src/components/Modal/__tests__/ModalContent.test.tsx index b37b08c477d..b479d065103 100644 --- a/packages/react-core/src/components/Modal/__tests__/ModalContent.test.tsx +++ b/packages/react-core/src/components/Modal/__tests__/ModalContent.test.tsx @@ -21,6 +21,15 @@ test('Modal Content Test isOpen', () => { expect(view).toMatchSnapshot(); }); +test('Modal Content Test description', () => { + const view = shallow( + + This is a ModalBox header + + ); + expect(view).toMatchSnapshot(); +}); + test('Modal Content Test with footer', () => { const view = shallow( diff --git a/packages/react-core/src/components/Modal/__tests__/__snapshots__/ModalContent.test.tsx.snap b/packages/react-core/src/components/Modal/__tests__/__snapshots__/ModalContent.test.tsx.snap index ab1f5e8d564..a20dadff1bf 100644 --- a/packages/react-core/src/components/Modal/__tests__/__snapshots__/ModalContent.test.tsx.snap +++ b/packages/react-core/src/components/Modal/__tests__/__snapshots__/ModalContent.test.tsx.snap @@ -1,5 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Modal Content Test description 1`] = ` + + + + + + + Test Modal Content title + + +
+ This is a test description. +
+ + This is a ModalBox header + +
+
+
+`; + exports[`Modal Content Test isOpen 1`] = ` { + this.setState(({ isModalOpen }) => ({ + isModalOpen: !isModalOpen + })); + }; + } + + render() { + const { isModalOpen } = this.state; + + return ( + + + + Confirm + , + + ]} + isFooterLeftAligned + > + 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. + + + ); + } +} +``` + ```js title=Small import React from 'react'; import { Modal, Button } from '@patternfly/react-core'; @@ -241,19 +295,17 @@ class CustomHeaderFooter extends React.Component { const header = ( - + <Title headingLevel={TitleLevel.h1} size={BaseSizes['2xl']}> Custom Modal Header/Footer -

- Allows for custom content in the header and/or footer by passing components. -

+

Allows for custom content in the header and/or footer by passing components.

); const footer = ( <WarningTriangleIcon /> - <span className="pf-u-pl-sm">Custom modal footer.</span> + <span className="pf-u-pl-sm">Custom modal footer.</span> ); @@ -278,9 +330,9 @@ class CustomHeaderFooter extends React.Component {

- 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. + 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. ); @@ -307,11 +359,7 @@ class NoHeader extends React.Component { render() { const { isModalOpen } = this.state; - const footer = ( - - Modal Footer - - ); + const footer = Modal Footer; return ( @@ -335,9 +383,9 @@ class NoHeader extends React.Component {

- 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. + 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.
); diff --git a/packages/react-integration/cypress/integration/modal.spec.ts b/packages/react-integration/cypress/integration/modal.spec.ts index 920d484ef48..2c0bf3bc4ee 100644 --- a/packages/react-integration/cypress/integration/modal.spec.ts +++ b/packages/react-integration/cypress/integration/modal.spec.ts @@ -24,6 +24,25 @@ describe('Modal Test', () => { }); }); + it('Verify Description Modal', () => { + cy.get('#showDescriptionModalButton').then((modalButton: JQuery) => { + cy.wrap(modalButton).click(); + cy.get('.pf-c-modal-box') + .then(() => { + cy.get('.pf-c-modal-box .pf-c-button[aria-label="Close"]').then(closeButton => { + cy.wrap(closeButton).click(); + cy.get('.pf-c-modal-box').should('not.exist'); + }); + }) + .then(() => { + cy.wrap(modalButton).click(); + cy.get('.pf-c-modal-box').should('exist'); + cy.get('body').trigger('keydown', { keyCode: 27, which: 27 }); + cy.get('.pf-c-modal-box').should('not.exist'); + }); + }); + }); + it('Verify Small Modal', () => { cy.get('#showSmallModalButton').then((modalButton: JQuery) => { cy.wrap(modalButton).click(); diff --git a/packages/react-integration/demo-app-ts/src/components/demos/ModalDemo/ModalDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/ModalDemo/ModalDemo.tsx index c4f2b512788..c532c2574fb 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/ModalDemo/ModalDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/ModalDemo/ModalDemo.tsx @@ -4,6 +4,7 @@ import WarningTriangleIcon from '@patternfly/react-icons/dist/js/icons/warning-t interface ModalDemoState { isModalOpen: boolean; + isModalDescriptionOpen: boolean; isSmallModalOpen: boolean; isLargeModalOpen: boolean; isHalfWidthModalOpen: boolean; @@ -14,6 +15,7 @@ interface ModalDemoState { export class ModalDemo extends React.Component, ModalDemoState> { state = { isModalOpen: false, + isModalDescriptionOpen: false, isSmallModalOpen: false, isLargeModalOpen: false, isHalfWidthModalOpen: false, @@ -27,6 +29,12 @@ export class ModalDemo extends React.Component, })); }; + handleModalDescriptionToggle = () => { + this.setState(({ isModalDescriptionOpen }) => ({ + isModalDescriptionOpen: !isModalDescriptionOpen + })); + }; + handleSmallModalToggle = () => { this.setState(({ isSmallModalOpen }) => ({ isSmallModalOpen: !isSmallModalOpen @@ -88,6 +96,35 @@ export class ModalDemo extends React.Component, ); } + renderModalWithDescription() { + const { isModalDescriptionOpen } = this.state; + + return ( + + Cancel + , + + ]} + isFooterLeftAligned + > + 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; @@ -283,6 +320,14 @@ export class ModalDemo extends React.Component, > Show No Header Modal + {this.renderModal()} {this.renderSmallModal()} @@ -290,6 +335,7 @@ export class ModalDemo extends React.Component, {this.renderHalfWidthModal()} {this.renderCustomHeaderFooterModal()} {this.renderNoHeaderModal()} + {this.renderModalWithDescription()} ); }