diff --git a/editor/assets/stylesheets/_z-index.scss b/editor/assets/stylesheets/_z-index.scss
index 951d8c582082dc..99053b7bb7a915 100644
--- a/editor/assets/stylesheets/_z-index.scss
+++ b/editor/assets/stylesheets/_z-index.scss
@@ -10,6 +10,7 @@ $z-layers: (
'.editor-header': 20,
'.editor-post-visibility__dialog': 30,
'.editor-post-schedule__dialog': 30,
+ '.utils-accept__backdrop': 100000
);
@function z-index( $key ) {
diff --git a/editor/sidebar/post-visibility/index.js b/editor/sidebar/post-visibility/index.js
index a96550075d4959..8d8c50e1427b5e 100644
--- a/editor/sidebar/post-visibility/index.js
+++ b/editor/sidebar/post-visibility/index.js
@@ -10,6 +10,7 @@ import { find } from 'lodash';
*/
import { __ } from 'i18n';
import { Component } from 'element';
+import { accept } from 'utils';
/**
* Internal Dependencies
@@ -52,10 +53,13 @@ class PostVisibility extends Component {
this.setState( { hasPassword: false } );
};
const setPrivate = () => {
- if ( window.confirm( __( 'Would you like to privately publish this post now?' ) ) ) { // eslint-disable-line no-alert
- onSave( post, { ...edits, status: 'private' }, blocks );
- this.setState( { opened: false } );
- }
+ const message = __( 'Would you like to privately publish this post now?' );
+ accept( message, ( accepted ) => {
+ if ( accepted ) {
+ onSave( post, { ...edits, status: 'private' }, blocks );
+ this.setState( { opened: false } );
+ }
+ }, __( 'Yes' ), __( 'No' ) );
};
const setPasswordProtected = () => {
onUpdateVisibility( visibility === 'private' ? 'publish' : status, password || '' );
diff --git a/index.php b/index.php
index fcca4adf570a3b..cb8fae39faad09 100644
--- a/index.php
+++ b/index.php
@@ -192,7 +192,7 @@ function gutenberg_register_scripts() {
wp_register_script(
'wp-utils',
plugins_url( 'utils/build/index.js', __FILE__ ),
- array(),
+ array( 'wp-components', 'wp-i18n' ),
filemtime( plugin_dir_path( __FILE__ ) . 'utils/build/index.js' )
);
wp_register_script(
@@ -253,6 +253,12 @@ function gutenberg_register_scripts() {
);
// Editor Styles.
+ wp_register_style(
+ 'wp-utils',
+ plugins_url( 'utils/build/style.css', __FILE__ ),
+ array(),
+ filemtime( plugin_dir_path( __FILE__ ) . 'components/build/style.css' )
+ );
wp_register_style(
'wp-components',
plugins_url( 'components/build/style.css', __FILE__ ),
@@ -548,7 +554,7 @@ function gutenberg_scripts_and_styles( $hook ) {
wp_enqueue_style(
'wp-editor',
plugins_url( 'editor/build/style.css', __FILE__ ),
- array( 'wp-components', 'wp-blocks' ),
+ array( 'wp-components', 'wp-blocks', 'wp-utils' ),
filemtime( plugin_dir_path( __FILE__ ) . 'editor/build/style.css' )
);
}
diff --git a/utils/accept/dialog.js b/utils/accept/dialog.js
new file mode 100644
index 00000000000000..80dae58f5a7036
--- /dev/null
+++ b/utils/accept/dialog.js
@@ -0,0 +1,43 @@
+/**
+ * External dependencies
+ */
+import clickOutside from 'react-click-outside';
+
+/**
+ * WordPress Dependencies
+ */
+import { Component } from 'element';
+import { Button } from 'components';
+import { __ } from 'i18n';
+
+class AcceptDialog extends Component {
+ handleClickOutside() {
+ this.props.onClose( 'cancel' );
+ }
+
+ render() {
+ const { message, onClose, confirmButtonText, cancelButtonText } = this.props;
+ const accept = () => onClose( 'accept' );
+ const cancel = () => onClose( 'cancel' );
+
+ return (
+
+
+
+ { message }
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default clickOutside( AcceptDialog );
diff --git a/utils/accept/index.js b/utils/accept/index.js
new file mode 100644
index 00000000000000..b25a6126643136
--- /dev/null
+++ b/utils/accept/index.js
@@ -0,0 +1,38 @@
+/**
+ * External dependencies
+ */
+import { render, unmountComponentAtNode } from 'react-dom';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+import AcceptDialog from './dialog';
+
+export default function accept( message, callback, confirmButtonText, cancelButtonText ) {
+ let wrapper = document.createElement( 'div' );
+ wrapper.className = 'utils-accept__backdrop';
+ document.body.appendChild( wrapper );
+
+ function onClose( result ) {
+ if ( wrapper ) {
+ unmountComponentAtNode( wrapper );
+ document.body.removeChild( wrapper );
+ wrapper = null;
+ }
+
+ if ( callback ) {
+ callback( result === 'accept' );
+ }
+ }
+
+ render(
+ ,
+ wrapper
+ );
+}
diff --git a/utils/accept/style.scss b/utils/accept/style.scss
new file mode 100644
index 00000000000000..344d199158eb99
--- /dev/null
+++ b/utils/accept/style.scss
@@ -0,0 +1,36 @@
+.utils-accept__dialog {
+ top: 10px;
+ bottom: 10px;
+ width: 400px;
+ margin: auto;
+ background: $white;
+ box-shadow: $shadow-popover;
+ border: 1px solid $light-gray-500;
+}
+
+.utils-accept__dialog-content {
+ padding: 20px;
+}
+
+.utils-accept__dialog-buttons {
+ text-align: right;
+ padding: 10px 20px;
+ border-top: 1px solid $light-gray-500;
+
+ .components-button {
+ margin-left: 10px;
+ }
+}
+
+.utils-accept__backdrop {
+ align-items: center;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+ display: flex;
+ justify-content: center;
+ position: fixed;
+ z-index: z-index( '.utils-accept__backdrop' );
+ background-color: rgba( $light-gray-900, 0.8 );
+}
diff --git a/utils/accept/test/index.js b/utils/accept/test/index.js
new file mode 100644
index 00000000000000..2093b5c243665d
--- /dev/null
+++ b/utils/accept/test/index.js
@@ -0,0 +1,55 @@
+/**
+ * External dependencies
+ */
+import { expect } from 'chai';
+
+/**
+ * Internal dependencies
+ */
+import accept from '../';
+
+describe( '#accept()', () => {
+ beforeEach( () => {
+ document.body.innerHTML = '';
+ } );
+
+ it( 'should render a dialog to the document body', () => {
+ const message = 'Are you sure?';
+
+ accept( message, () => {} );
+
+ const dialog = document.querySelector( '.utils-accept__dialog-content' );
+ expect( dialog ).to.be.an.instanceof( window.Element );
+ expect( dialog.textContent ).to.equal( message );
+ } );
+
+ it( 'should trigger the callback with an accepted prompt', ( done ) => {
+ accept( 'Are you sure?', ( accepted ) => {
+ expect( accepted ).to.be.be.true();
+ done();
+ } );
+
+ document.querySelector( '.components-button.button-primary' ).click();
+ } );
+
+ it( 'should trigger the callback with a denied prompt', ( done ) => {
+ accept( 'Are you sure?', ( accepted ) => {
+ expect( accepted ).to.be.be.false();
+ done();
+ } );
+
+ document.querySelector( '.components-button:not( .button-primary )' ).click();
+ } );
+
+ it( 'should clean up after itself once the prompt is closed', ( done ) => {
+ accept( 'Are you sure?', () => {
+ process.nextTick( () => {
+ expect( document.querySelector( '.utils-accept__dialog' ) ).to.be.null();
+
+ done();
+ } );
+ } );
+
+ document.querySelector( '.components-button.button-primary' ).click();
+ } );
+} );
diff --git a/utils/index.js b/utils/index.js
index 6400ec8e4489f0..4d8201a93fa14e 100644
--- a/utils/index.js
+++ b/utils/index.js
@@ -1,3 +1,4 @@
import * as keycodes from './keycodes';
+export { default as accept } from './accept';
export { keycodes };