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 a119bd2a255d75..cbc3bc77195fd2 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 @@ -48,11 +49,14 @@ 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 - onUpdateVisibility( 'private' ); - onSave(); - this.setState( { opened: false } ); - } + const message = __( 'Would you like to privately publish this post now?' ); + accept( message, ( accepted ) => { + if ( accepted ) { + onUpdateVisibility( 'private' ); + onSave(); + this.setState( { opened: false } ); + } + }, __( 'Yes' ), __( 'No' ) ); }; const setPasswordProtected = () => { onUpdateVisibility( visibility === 'private' ? 'draft' : status, password || '' ); diff --git a/lib/client-assets.php b/lib/client-assets.php index b8e2bc8cd36b87..b2b08be96fa736 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -78,7 +78,7 @@ function gutenberg_register_scripts() { wp_register_script( 'wp-utils', gutenberg_url( 'utils/build/index.js' ), - array(), + array( 'wp-components', 'wp-i18n' ), filemtime( gutenberg_dir_path() . 'utils/build/index.js' ) ); wp_register_script( @@ -145,6 +145,12 @@ function gutenberg_register_scripts() { array(), filemtime( gutenberg_dir_path() . 'components/build/style.css' ) ); + wp_register_style( + 'wp-utils', + gutenberg_url( 'utils/build/style.css' ), + array(), + filemtime( gutenberg_dir_path() . 'utils/build/style.css' ) + ); wp_register_style( 'wp-blocks', gutenberg_url( 'blocks/build/style.css' ), @@ -343,7 +349,7 @@ function gutenberg_scripts_and_styles( $hook ) { wp_enqueue_style( 'wp-editor', gutenberg_url( 'editor/build/style.css' ), - array( 'wp-components', 'wp-blocks' ), + array( 'wp-components', 'wp-blocks', 'wp-utils' ), filemtime( gutenberg_dir_path() . '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..2959aa1446fac9 --- /dev/null +++ b/utils/accept/style.scss @@ -0,0 +1,36 @@ +.utils-accept__dialog { + top: 10px; + bottom: 10px; + max-width: 400px; + width: 100%; + margin: auto; + box-shadow: $shadow-popover; + border: 1px solid $light-gray-500; + background: $white; +} + +.utils-accept__dialog-content { + padding: 20px; +} + +.utils-accept__dialog-buttons { + text-align: right; + padding: 10px 20px 20px 20px; + + .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( $dark-gray-900, 0.4 ); +} 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 };