UiFramework: Modals in react (#11500)

* Reactify the confirmation modal

Up next: jest tests

* Add tests

- Relies on https://github.com/elastic/kibana/pull/10821 getting
checked in first for commonHtmlProps

* Don't include the overlay as part of the confirm modal component

* Use the react version of a confirmation modal

- Can’t use the modalOverlay or it would be two nested react roots, due
to the way it’s embedded in angular.

* Add snapshots

* Fix tests

* fix confirm_modal_promise tests

I have no idea why the introduction of react would cause this to fail
as it was, but for some reason, grabbing the button reference has to be
after the digest loop.

* Address code review comments

* switch over the remaining React.PropType references (in the modals dir anyway)
This commit is contained in:
Stacey Gammon 2017-05-03 09:44:31 -04:00 committed by GitHub
parent 616390da61
commit 8fca519a39
40 changed files with 598 additions and 158 deletions

View file

@ -161,6 +161,7 @@
"node-uuid": "1.4.7",
"pegjs": "0.9.0",
"postcss-loader": "1.2.1",
"prop-types": "15.5.8",
"pui-react-overlay-trigger": "7.5.4",
"pui-react-tooltip": "7.5.4",
"querystring-browser": "1.0.4",

View file

@ -47,7 +47,7 @@ describe('ui/modals/confirm_modal', function () {
confirmModal(myMessage, { confirmButtonText: 'GREAT!', onConfirm: _.noop });
$rootScope.$digest();
const message = findByDataTestSubj('confirmModalBodyText')[0].innerText;
const message = findByDataTestSubj('confirmModalBodyText')[0].innerText.trim();
expect(message).to.equal(myMessage);
});
@ -62,21 +62,21 @@ describe('ui/modals/confirm_modal', function () {
it('for confirm button', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const confirmButtonText = findByDataTestSubj('confirmModalConfirmButton')[0].innerText;
const confirmButtonText = findByDataTestSubj('confirmModalConfirmButton')[0].innerText.trim();
expect(confirmButtonText).to.equal('Troodon');
});
it('for cancel button', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const cancelButtonText = findByDataTestSubj('confirmModalCancelButton')[0].innerText;
const cancelButtonText = findByDataTestSubj('confirmModalCancelButton')[0].innerText.trim();
expect(cancelButtonText).to.equal('Dilophosaurus');
});
it('for title text', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const titleText = findByDataTestSubj('confirmModalTitleText')[0].innerText;
const titleText = findByDataTestSubj('confirmModalTitleText')[0].innerText.trim();
expect(titleText).to.equal('Dinosaurs');
});
});

View file

@ -56,11 +56,10 @@ describe('ui/modals/confirm_modal_promise', function () {
const cancelCallback = sinon.spy();
promise.then(confirmCallback, cancelCallback);
$rootScope.$digest();
const confirmButton = angular.element(document.body).find('[data-test-subj=confirmModalConfirmButton]');
$rootScope.$digest();
confirmButton.click();
expect(confirmCallback.called).to.be(true);
expect(cancelCallback.called).to.be(false);
});
@ -72,9 +71,8 @@ describe('ui/modals/confirm_modal_promise', function () {
const cancelCallback = sinon.spy();
promise.then(confirmCallback, cancelCallback);
const noButton = angular.element(document.body).find('[data-test-subj=confirmModalCancelButton]');
$rootScope.$digest();
const noButton = angular.element(document.body).find('[data-test-subj=confirmModalCancelButton]');
noButton.click();
expect(cancelCallback.called).to.be(true);

View file

@ -1,32 +1,9 @@
<div class="kuiModal" style="width: 450px" data-test-subj="confirmModal">
<div class="kuiModalHeader" ng-if="title">
<div class="kuiModalHeader__title" data-test-subj="confirmModalTitleText">
{{title}}
</div>
</div>
<div class="kuiModalBody">
<div
class="kuiModalBodyText"
data-test-subj="confirmModalBodyText"
>
{{message}}
</div>
</div>
<div class="kuiModalFooter">
<button
class="kuiButton kuiButton--hollow"
data-test-subj="confirmModalCancelButton"
ng-click="onCancel()"
>
{{cancelButtonText}}
</button>
<button
class="kuiButton kuiButton--primary"
data-test-subj="confirmModalConfirmButton"
ng-click="onConfirm()"
>
{{confirmButtonText}}
</button>
</div>
</div>
<confirm-modal
data-test-subj="confirmModal"
on-cancel="onCancel"
on-confirm="onConfirm"
confirm-button-text="confirmButtonText"
cancel-button-text="cancelButtonText"
message="message"
title="title"
></confirm-modal>

View file

@ -2,10 +2,15 @@ import 'ngreact';
import {
KuiToolBarSearchBox,
KuiConfirmModal,
} from 'ui_framework/components';
import { uiModules } from 'ui/modules';
const app = uiModules.get('app/kibana', ['react']);
app.directive('toolBarSearchBox', function (reactDirective) {
return reactDirective(KuiToolBarSearchBox);
});
app.directive('confirmModal', function (reactDirective) {
return reactDirective(KuiConfirmModal);
});

View file

@ -5,8 +5,14 @@ export {
KuiLinkButton,
KuiSubmitButton,
} from './button';
export {
KuiToolBarSearchBox,
KuiToolBar,
KuiToolBarFooter,
} from './tool_bar';
export {
KuiConfirmModal,
KuiModalOverlay
} from './modal';

View file

@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiConfirmModal 1`] = `
<div
aria-label="aria-label"
class="kuiModal testClass1 testClass2"
data-tests-subj="test subject string"
style="width:450px;"
>
<div
class="kuiModalHeader"
>
<div
class="kuiModalHeader__title"
data-test-subj="confirmModalTitleText"
>
A confirmation modal
</div>
</div>
<div
class="kuiModalBody"
>
<div
class="kuiModalBodyText"
data-test-subj="confirmModalBodyText"
>
This is a confirmation modal example
</div>
</div>
<div
class="kuiModalFooter"
>
<button
class="kuiButton kuiButton--hollow"
data-test-subj="confirmModalCancelButton"
>
<span
class="kuiButton__inner"
>
<span>
Cancel Button Text
</span>
</span>
</button>
<button
class="kuiButton kuiButton--primary"
data-test-subj="confirmModalConfirmButton"
>
<span
class="kuiButton__inner"
>
<span>
Confirm Button Text
</span>
</span>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModal 1`] = `
<div
aria-label="aria-label"
class="kuiModal testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalBody 1`] = `
<div
aria-label="aria-label"
class="kuiModalBody testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalBodyText 1`] = `
<div
aria-label="aria-label"
class="kuiModalBodyText testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalFooter 1`] = `
<div
aria-label="aria-label"
class="kuiModalFooter testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalHeader 1`] = `
<div
aria-label="aria-label"
class="kuiModalHeader testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalHeaderTitle 1`] = `
<div
aria-label="aria-label"
class="kuiModalHeader__title testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders KuiModalOverlay 1`] = `
<div
aria-label="aria-label"
class="kuiModalOverlay testClass1 testClass2"
data-test-subj="test subject string"
>
children
</div>
`;

View file

@ -2,6 +2,7 @@ $modalPadding: 10px;
$modalBorderColor: #6EADC1;
$modalBackgroundColor: #FFF;
$modalOverlayBackground: rgba(#000000, 0.3);
$globalModalDepth: 1000;
@import "modal_overlay";
@import "modal";

View file

@ -4,6 +4,7 @@
border: 1px solid $modalBorderColor;
border-radius: $globalBorderRadius;
box-shadow: 0 5px 22px rgba(#000000, 0.25);
z-index: $globalModalDepth + 1;
}
.kuiModalHeader {

View file

@ -1,6 +1,6 @@
.kuiModalOverlay {
position: fixed;
z-index: 1000;
z-index: $globalModalDepth;
top: 0;
left: 0;
right: 0;

View file

@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import { KuiModal } from './modal';
import { KuiModalFooter } from './modal_footer';
import { KuiModalHeader } from './modal_header';
import { KuiModalHeaderTitle } from './modal_header_title';
import { KuiModalBody } from './modal_body';
import { KuiModalBodyText } from './modal_body_text';
import { KuiButton } from '../index';
export function KuiConfirmModal({
message,
title,
onCancel,
onConfirm,
cancelButtonText,
confirmButtonText,
className,
...rest }) {
const ariaLabel = rest['aria-label'];
const dataTestSubj = rest['data-test-subj'];
return (
<KuiModal
style={{ 'width': '450px' }}
data-tests-subj={ dataTestSubj }
aria-label={ ariaLabel }
className={ className }
>
{
title ?
<KuiModalHeader>
<KuiModalHeaderTitle data-test-subj="confirmModalTitleText">
{ title }
</KuiModalHeaderTitle>
</KuiModalHeader>
: null
}
<KuiModalBody>
<KuiModalBodyText data-test-subj="confirmModalBodyText">
{ message }
</KuiModalBodyText>
</KuiModalBody>
<KuiModalFooter>
<KuiButton
type="hollow"
data-test-subj="confirmModalCancelButton"
onClick={ onCancel }
>
{cancelButtonText}
</KuiButton>
<KuiButton
type="primary"
data-test-subj="confirmModalConfirmButton"
onClick={ onConfirm }
>
{confirmButtonText}
</KuiButton>
</KuiModalFooter>
</KuiModal>
);
}
KuiConfirmModal.propTypes = {
message: PropTypes.string,
title: PropTypes.string,
cancelButtonText: PropTypes.string,
confirmButtonText: PropTypes.string,
onCancel: PropTypes.func,
onConfirm: PropTypes.func,
dataTestSubj: PropTypes.string,
ariaLabel: PropTypes.string,
className: PropTypes.string,
};

View file

@ -0,0 +1,61 @@
import React from 'react';
import sinon from 'sinon';
import { mount, render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiConfirmModal,
} from './confirm_modal';
let onConfirm;
let onCancel;
beforeEach(() => {
onConfirm = sinon.spy();
onCancel = sinon.spy();
});
test('renders KuiConfirmModal', () => {
const component = render(<KuiConfirmModal
message="This is a confirmation modal example"
title="A confirmation modal"
onCancel={() => {}}
onConfirm={onConfirm}
cancelButtonText="Cancel Button Text"
confirmButtonText="Confirm Button Text"
{ ...requiredProps }
/>);
expect(component).toMatchSnapshot();
});
test('onConfirm', () => {
const component = mount(<KuiConfirmModal
message="This is a confirmation modal example"
title="A confirmation modal"
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText="Cancel"
confirmButtonText="Confirm"
{ ...requiredProps }
/>);
component.find('[data-test-subj="confirmModalConfirmButton"]').simulate('click');
sinon.assert.calledOnce(onConfirm);
sinon.assert.notCalled(onCancel);
});
test('onCancel', () => {
const component = mount(<KuiConfirmModal
message="This is a confirmation modal example"
title="A confirmation modal"
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText="Cancel"
confirmButtonText="Confirm"
{ ...requiredProps }
/>);
component.find('[data-test-subj="confirmModalCancelButton"]').simulate('click');
sinon.assert.notCalled(onConfirm);
sinon.assert.calledOnce(onCancel);
});

View file

@ -0,0 +1,5 @@
export { KuiConfirmModal } from './confirm_modal';
export { KuiModal } from './modal';
export { KuiModalFooter } from './modal_footer';
export { KuiModalHeader } from './modal_header';
export { KuiModalOverlay } from './modal_overlay';

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModal({ className, children, ...rest }) {
const classes = classnames('kuiModal', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModal.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModal,
} from './modal';
test('renders KuiModal', () => {
const component = <KuiModal { ...requiredProps }>children</KuiModal>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalBody({ className, children, ...rest }) {
const classes = classnames('kuiModalBody', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModalBody.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalBody,
} from './modal_body';
test('renders KuiModalBody', () => {
const component = <KuiModalBody { ...requiredProps }>children</KuiModalBody>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalBodyText({ className, children, ...rest }) {
const classes = classnames('kuiModalBodyText', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModalBodyText.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalBodyText,
} from './modal_body_text';
test('renders KuiModalBodyText', () => {
const component = <KuiModalBodyText { ...requiredProps }>children</KuiModalBodyText>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalFooter({ className, children, ...rest }) {
const classes = classnames('kuiModalFooter', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModalFooter.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalFooter,
} from './modal_footer';
test('renders KuiModalFooter', () => {
const component = <KuiModalFooter { ...requiredProps }>children</KuiModalFooter>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalHeader({ className, children, ...rest }) {
const classes = classnames('kuiModalHeader', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModalHeader.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalHeader,
} from './modal_header';
test('renders KuiModalHeader', () => {
const component = <KuiModalHeader { ...requiredProps }>children</KuiModalHeader>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalHeaderTitle({ className, children, ...rest }) {
const classes = classnames('kuiModalHeader__title', className);
return (
<div className={ classes } { ...rest }>
{ children }
</div>
);
}
KuiModalHeaderTitle.propTypes = {
className: PropTypes.string,
children: PropTypes.node
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalHeaderTitle,
} from './modal_header_title';
test('renders KuiModalHeaderTitle', () => {
const component = <KuiModalHeaderTitle { ...requiredProps }>children</KuiModalHeaderTitle>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -0,0 +1,17 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
export function KuiModalOverlay({ className, ...rest }) {
const classes = classnames('kuiModalOverlay', className);
return (
<div
className={ classes }
{ ...rest}
/>
);
}
KuiModalOverlay.propTypes = {
className: PropTypes.string,
};

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import {
KuiModalOverlay,
} from './modal_overlay';
test('renders KuiModalOverlay', () => {
const component = <KuiModalOverlay { ...requiredProps }>children</KuiModalOverlay>;
expect(render(component)).toMatchSnapshot();
});

View file

@ -2112,7 +2112,8 @@ body {
background-color: #FFF;
border: 1px solid #6EADC1;
border-radius: 4px;
box-shadow: 0 5px 22px rgba(0, 0, 0, 0.25); }
box-shadow: 0 5px 22px rgba(0, 0, 0, 0.25);
z-index: 1001; }
.kuiModalHeader {
display: -webkit-box;

View file

@ -0,0 +1,50 @@
import React from 'react';
import {
KuiConfirmModal,
KuiModalOverlay,
KuiButton
} from '../../../../components/index';
export class ConfirmModalExample extends React.Component {
constructor(props) {
super(props);
this.state = {
showConfirmModal: false
};
this.closeModal = this.closeModal.bind(this);
this.showModal = this.showModal.bind(this);
}
closeModal() {
this.setState({ showConfirmModal: false });
}
showModal() {
this.setState({ showConfirmModal: true });
}
render() {
return (
<div>
<KuiButton type="primary" onClick={this.showModal}>
Show Modal
</KuiButton>
{
this.state.showConfirmModal ?
<KuiModalOverlay>
<KuiConfirmModal
message="This is a confirmation modal example"
title="A confirmation modal"
onCancel={this.closeModal}
onConfirm={this.closeModal}
cancelButtonText="Cancel"
confirmButtonText="Confirm"
/>
</KuiModalOverlay>
: null
}
</div>
);
}
}

View file

@ -1,25 +0,0 @@
<div class="kuiModal" style="width: 450px">
<div class="kuiModalHeader">
<div class="kuiModalHeader__title">
Delete object
</div>
<div class="kuiModalHeaderCloseButton kuiIcon fa-times"></div>
</div>
<div class="kuiModalBody">
<div class="kuiModalBodyText">
Are you sure you want to delete this object? You can&rsquo;t undo this.
</div>
</div>
<div class="kuiModalFooter">
<button class="kuiButton kuiButton--hollow">
No, cancel
</button>
<button class="kuiButton kuiButton--primary">
Yes, delete this object
</button>
</div>
</div>

View file

@ -1,5 +1,7 @@
import React from 'react';
import { renderToHtml } from '../../services';
import {
GuideDemo,
GuidePage,
@ -7,38 +9,55 @@ import {
GuideSectionTypes,
} from '../../components';
const modalHtml = require('./modal.html');
const modalOverlayHtml = require('./modal_overlay.html');
const modalOverlayJs = require('raw!./modal_overlay.js');
import {
KuiConfirmModal,
} from '../../../../components';
import { ConfirmModalExample } from './confirm_modal_example';
const showConfirmModalSource = require('!!raw!./confirm_modal_example');
const showConfirmModalHtml = renderToHtml(ConfirmModalExample);
const kuiConfirmModalSource = require('!!raw!../../../../components/modal/confirm_modal');
const kuiConfirmModalHtml = renderToHtml(KuiConfirmModal);
export default props => (
<GuidePage title={props.route.name}>
<GuideSection
title="Modal"
title="Confirmation Modal"
source={[{
type: GuideSectionTypes.JS,
code: kuiConfirmModalSource,
}, {
type: GuideSectionTypes.HTML,
code: modalHtml,
code: kuiConfirmModalHtml,
}]}
>
<GuideDemo
html={modalHtml}
/>
<GuideDemo>
<KuiConfirmModal
onCancel={() => {}}
onConfirm={() => {}}
confirmButtonText="Confirm"
cancelButtonText="Cancel"
message="This is a confirmation modal"
title="Confirm Modal Title"
/>
</GuideDemo>
</GuideSection>
<GuideSection
title="ModalOverlay"
title="Pop up Confirmation Modal with Overlay"
source={[{
type: GuideSectionTypes.HTML,
code: modalOverlayHtml,
}, {
type: GuideSectionTypes.JS,
code: modalOverlayJs,
code: showConfirmModalSource,
}, {
type: GuideSectionTypes.HTML,
code: showConfirmModalHtml,
}]}
>
<GuideDemo
html={modalOverlayHtml}
js={modalOverlayJs}
/>
<GuideDemo>
<ConfirmModalExample />
</GuideDemo>
</GuideSection>
</GuidePage>
);

View file

@ -1,29 +0,0 @@
<button data-id="showModalOverlay">Show modal overlay</button>
<div class="kuiModalOverlay">
<div class="kuiModal" style="width: 450px">
<div class="kuiModalHeader">
<div class="kuiModalHeader__title">
Delete object
</div>
<div class="kuiModalHeaderCloseButton kuiIcon fa-times"></div>
</div>
<div class="kuiModalBody">
<div class="kuiModalBodyText">
Are you sure you want to delete this object? You can&rsquo;t undo this.
</div>
</div>
<div class="kuiModalFooter">
<button class="kuiButton kuiButton--hollow">
No, cancel
</button>
<button class="kuiButton kuiButton--primary">
Yes, delete this object
</button>
</div>
</div>
</div>

View file

@ -1,45 +0,0 @@
/* eslint-disable */
const $showModalOverlayButton = $('[data-id="showModalOverlay"]');
const $modalOverlay = $('.kuiModalOverlay');
const $modalOverlayCloseButton = $('.kuiModalOverlay .kuiModalHeaderCloseButton');
const $modalOverlayCancelButton = $('.kuiModalOverlay .kuiButton--hollow');
const $modalOverlayConfirmButton = $('.kuiModalOverlay .kuiButton--primary');
if (!$showModalOverlayButton.length) {
throw new Error('$showModalOverlayButton missing');
}
if (!$modalOverlay.length) {
throw new Error('$modalOverlay missing');
}
if (!$modalOverlayCloseButton.length) {
throw new Error('$modalOverlayCloseButton missing');
}
if (!$modalOverlayCancelButton.length) {
throw new Error('$modalOverlayCancelButton missing');
}
if (!$modalOverlayConfirmButton.length) {
throw new Error('$modalOverlayConfirmButton missing');
}
$modalOverlay.hide();
$showModalOverlayButton.on('click', () => {
$modalOverlay.show();
});
$modalOverlayCloseButton.on('click', () => {
$modalOverlay.hide();
});
$modalOverlayCancelButton.on('click', () => {
$modalOverlay.hide();
});
$modalOverlayConfirmButton.on('click', () => {
$modalOverlay.hide();
});