From 21c3e00d9ba1fe1031602d8df0f076a00c80ca36 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Fri, 5 Jul 2024 20:36:56 +0300 Subject: [PATCH] Subscription and payment blocks: improve loading animation (#38174) --- .../changelog/update-subscribe-modal-loading | 4 + .../blocks/subscriptions/spinner.svg | 23 +++++ .../extensions/blocks/subscriptions/view.js | 20 ++++- .../extensions/blocks/subscriptions/view.scss | 3 - .../jetpack/extensions/shared/memberships.js | 90 ++++++++++++------- .../extensions/shared/memberships.scss | 90 +++++++++++-------- .../subscribe-modal/subscribe-modal.js | 8 +- .../subscribe-overlay/subscribe-overlay.js | 9 +- 8 files changed, 168 insertions(+), 79 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-subscribe-modal-loading create mode 100644 projects/plugins/jetpack/extensions/blocks/subscriptions/spinner.svg diff --git a/projects/plugins/jetpack/changelog/update-subscribe-modal-loading b/projects/plugins/jetpack/changelog/update-subscribe-modal-loading new file mode 100644 index 0000000000000..637c4fdf39461 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-subscribe-modal-loading @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Subscribe block: improve loading animation diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/spinner.svg b/projects/plugins/jetpack/extensions/blocks/subscriptions/spinner.svg new file mode 100644 index 0000000000000..9c7bc859acfa6 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/spinner.svg @@ -0,0 +1,23 @@ + + + + + diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/view.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/view.js index 06f1fe47d4c19..af141a3099d29 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/view.js +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/view.js @@ -2,7 +2,7 @@ import './view.scss'; import '../../shared/memberships.scss'; import domReady from '@wordpress/dom-ready'; -import { showModal } from '../../shared/memberships'; +import { showModal, spinner } from '../../shared/memberships'; // @ts-ignore function show_iframe_retrieve_subscriptions_from_email() { @@ -33,7 +33,7 @@ function show_iframe( data ) { const url = 'https://subscribe.wordpress.com/memberships/?' + params.toString(); - showModal( url ); + return showModal( url ); } domReady( function () { @@ -49,11 +49,21 @@ domReady( function () { forms.forEach( form => { if ( ! form.payments_attached ) { form.payments_attached = true; + + const button = form.querySelector( 'button[type="submit"]' ); + + // Injects loading animation in hidden state + button.insertAdjacentHTML( 'beforeend', spinner ); + form.addEventListener( 'submit', function ( event ) { if ( form.resubmitted ) { return; } + button.classList.add( 'is-loading' ); + button.setAttribute( 'aria-busy', 'true' ); + button.setAttribute( 'aria-live', 'polite' ); + // If email is empty, we will ask for it in the modal that opens // Email input can be hidden for "button only style" for example. let email = form.querySelector( 'input[type=email]' )?.value ?? ''; @@ -83,6 +93,12 @@ domReady( function () { app_source, post_access_level: form.dataset.post_access_level, display: 'alternate', + } ).then( () => { + // Allows hiding other modals when the subscription modal/iframe shows up, e.g. hiding the subscription overlay modal + form.dispatchEvent( new Event( 'subscription-modal-loaded' ) ); + + button.classList.remove( 'is-loading' ); + button.setAttribute( 'aria-busy', 'false' ); } ); } } ); diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/view.scss b/projects/plugins/jetpack/extensions/blocks/subscriptions/view.scss index 1255e4bf2daa7..f6ed193d3c806 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/view.scss +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/view.scss @@ -1,10 +1,7 @@ -@import '@automattic/jetpack-base-styles/gutenberg-base-styles'; - @function x($var-name, $fallback) { @return unquote("var(#{$var-name}, #{$fallback})"); } - .is-style-compact { .is-not-subscriber { .wp-block-jetpack-subscriptions__button, diff --git a/projects/plugins/jetpack/extensions/shared/memberships.js b/projects/plugins/jetpack/extensions/shared/memberships.js index 079766ef95ed5..9cb258c21b3d4 100644 --- a/projects/plugins/jetpack/extensions/shared/memberships.js +++ b/projects/plugins/jetpack/extensions/shared/memberships.js @@ -22,53 +22,79 @@ export function handleIframeResult( eventFromIframe ) { window.removeEventListener( 'message', handleIframeResult ); const dialog = document.getElementById( 'memberships-modal-window' ); dialog.close(); - document.body.classList.remove( 'modal-open' ); + document.body.classList.remove( 'jetpack-memberships-modal-open' ); } } } export function showModal( url ) { - // prevent double scroll bars. We use the entire viewport for the modal so we need to hide overflow on the body element. - document.body.classList.add( 'modal-open' ); + return new Promise( resolvePromise => { + const existingModal = document.getElementById( 'memberships-modal-window' ); + if ( existingModal ) { + document.body.removeChild( existingModal ); + } - const existingModal = document.getElementById( 'memberships-modal-window' ); - if ( existingModal ) { - document.body.removeChild( existingModal ); - } + const dialog = document.createElement( 'dialog' ); + dialog.setAttribute( 'id', 'memberships-modal-window' ); + dialog.classList.add( 'jetpack-memberships-modal' ); + dialog.classList.add( 'is-loading' ); - const dialog = document.createElement( 'dialog' ); - dialog.setAttribute( 'id', 'memberships-modal-window' ); + const iframe = document.createElement( 'iframe' ); + iframe.setAttribute( 'frameborder', '0' ); + iframe.setAttribute( 'allowtransparency', 'true' ); + iframe.setAttribute( 'allowfullscreen', 'true' ); - const iframe = document.createElement( 'iframe' ); - const inputLanguage = document.querySelector( 'input[name="lang"]' ); - let siteLanguage = null; - if ( inputLanguage ) { - siteLanguage = inputLanguage.value; - } - iframe.setAttribute( 'id', 'memberships-modal-iframe' ); - iframe.innerText = - 'This feature requires inline frames. You have iframes disabled or your browser does not support them.'; - iframe.src = url + '&display=alternate&jwt_token=' + getTokenFromCookie(); - if ( siteLanguage ) { - iframe.src = iframe.src + '&lang=' + siteLanguage; - } - iframe.setAttribute( 'frameborder', '0' ); - iframe.setAttribute( 'allowtransparency', 'true' ); - iframe.setAttribute( 'allowfullscreen', 'true' ); - dialog.classList.add( 'jetpack-memberships-modal' ); + iframe.addEventListener( 'load', function () { + // prevent double scroll bars. We use the entire viewport for the modal so we need to hide overflow on the body element. + document.body.classList.add( 'jetpack-memberships-modal-open' ); + dialog.classList.remove( 'is-loading' ); + resolvePromise(); + } ); + + iframe.setAttribute( 'id', 'memberships-modal-iframe' ); + iframe.innerText = + 'This feature requires inline frames. You have iframes disabled or your browser does not support them.'; + iframe.src = url + '&display=alternate&jwt_token=' + getTokenFromCookie(); - document.body.appendChild( dialog ); - dialog.appendChild( iframe ); + const siteLanguage = document.querySelector( 'input[name="lang"]' )?.value; + if ( siteLanguage ) { + iframe.src = iframe.src + '&lang=' + siteLanguage; + } + document.body.appendChild( dialog ); + dialog.appendChild( iframe ); - window.addEventListener( 'message', handleIframeResult, false ); - dialog.showModal(); + window.addEventListener( 'message', handleIframeResult, false ); + dialog.showModal(); + } ); } +export const spinner = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + function setUpModal( button ) { + // Injects loading animation in hidden state + button.insertAdjacentHTML( 'beforeend', spinner ); + button.addEventListener( 'click', event => { event.preventDefault(); - showModal( button.getAttribute( 'href' ) ); - this.blur(); + + // Shows the injected loading animation in button + button.classList.add( 'is-loading' ); + button.setAttribute( 'aria-busy', 'true' ); + button.setAttribute( 'aria-live', 'polite' ); + + const url = button.getAttribute( 'href' ); + showModal( url ).then( () => { + button.classList.remove( 'is-loading' ); + button.setAttribute( 'aria-busy', 'false' ); + } ); + + button.blur(); return false; } ); } diff --git a/projects/plugins/jetpack/extensions/shared/memberships.scss b/projects/plugins/jetpack/extensions/shared/memberships.scss index b67ed10f47f29..7dacbdaecd208 100644 --- a/projects/plugins/jetpack/extensions/shared/memberships.scss +++ b/projects/plugins/jetpack/extensions/shared/memberships.scss @@ -1,51 +1,67 @@ /* Additional styling to thickbox that displays modal */ /* stylelint-disable selector-max-id */ +@import '@automattic/jetpack-base-styles/gutenberg-base-styles'; -.jetpack-memberships-modal #TB_title { - display: none; +@keyframes jetpack-memberships_button__spinner-animation { + 100% { + transform: rotate(360deg); + } } -#memberships-modal-window.jetpack-memberships-modal, -#TB_window.jetpack-memberships-modal { - background-color: transparent; - background-image: url( 'https://s0.wp.com/i/loading/dark-200.gif' ); - background-size: 50px; - background-repeat: no-repeat; - background-position: center 150px; - margin: 0 !important; - box-shadow: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - border: none; - bottom: 0; - left: 0; - right: 0; - top: 0; - width: 100% !important; - height: 100%; +.jetpack-memberships-spinner { + display: none; + width: 1em; + height: 1em; + margin: 0 0 0 5px; + svg { + /* Better center-align the spinner with button text, because 1m height doesn't take line-height into account */ + margin-bottom: -2px; + width: 100%; + height: 100%; + } } -#memberships-modal-window.jetpack-memberships-modal { - padding: 21px; +.jetpack-memberships-spinner-rotating { + transform-origin: center; + animation: jetpack-memberships_button__spinner-animation .75s infinite linear } -.jetpack-memberships-modal #TB_iframeContent, -.jetpack-memberships-modal #memberships-modal-iframe { - margin: 0 !important; - height: 100% !important; - width: 100% !important; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; +.is-loading .jetpack-memberships-spinner { + display: inline-block; } -/* This is a class added by Thickbox on open modals. */ -BODY.modal-open { + +body.jetpack-memberships-modal-open { overflow: hidden; } -dialog::backdrop { - background-color: #000; - opacity: 0.7; +dialog.jetpack-memberships-modal { + opacity: 1; + + &, iframe { + position: fixed; + margin: 0; + padding: 0; + height: 100%; + width: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + box-shadow: none; + border: 0; + background: transparent; + } + + &::backdrop { + background-color: #000; + opacity: 0.7; + transition: opacity .2s ease-out; + } + + &.is-loading { + &, &::backdrop { + opacity: 0; + } + } + } diff --git a/projects/plugins/jetpack/modules/subscriptions/subscribe-modal/subscribe-modal.js b/projects/plugins/jetpack/modules/subscriptions/subscribe-modal/subscribe-modal.js index 23c7c22c6be17..3dca3a92b7830 100644 --- a/projects/plugins/jetpack/modules/subscriptions/subscribe-modal/subscribe-modal.js +++ b/projects/plugins/jetpack/modules/subscriptions/subscribe-modal/subscribe-modal.js @@ -11,7 +11,6 @@ domReady( function () { return; } - const close = document.getElementsByClassName( 'jetpack-subscribe-modal__close' )[ 0 ]; let hasLoaded = false; let isScrolling; @@ -25,7 +24,14 @@ domReady( function () { }, Jetpack_Subscriptions.modalLoadTime ); }; + // When the form is submitted, and next modal loads, it'll fire "subscription-modal-loaded" signalling that this form can be hidden. + const form = modal.querySelector( 'form' ); + if ( form ) { + form.addEventListener( 'subscription-modal-loaded', closeModal ); + } + // User can edit modal, and could remove close link. + const close = document.getElementsByClassName( 'jetpack-subscribe-modal__close' )[ 0 ]; if ( close ) { close.onclick = function ( event ) { event.preventDefault(); diff --git a/projects/plugins/jetpack/modules/subscriptions/subscribe-overlay/subscribe-overlay.js b/projects/plugins/jetpack/modules/subscriptions/subscribe-overlay/subscribe-overlay.js index 8aeba31838ec6..922b61c4b7a87 100644 --- a/projects/plugins/jetpack/modules/subscriptions/subscribe-overlay/subscribe-overlay.js +++ b/projects/plugins/jetpack/modules/subscriptions/subscribe-overlay/subscribe-overlay.js @@ -10,13 +10,13 @@ domReady( function () { return; } - const close = document.querySelector( '.jetpack-subscribe-overlay__close' ); + const close = overlay.querySelector( '.jetpack-subscribe-overlay__close' ); close.onclick = function ( event ) { event.preventDefault(); closeOverlay(); }; - const toContent = document.querySelector( '.jetpack-subscribe-overlay__to-content' ); + const toContent = overlay.querySelector( '.jetpack-subscribe-overlay__to-content' ); // User can edit overlay, and could remove to content link. if ( toContent ) { toContent.onclick = function ( event ) { @@ -25,9 +25,10 @@ domReady( function () { }; } - const form = document.querySelector( '.jetpack-subscribe-overlay form' ); + // When the form is submitted, and next modal loads, it'll fire "subscription-modal-loaded" signalling that this form can be hidden. + const form = overlay.querySelector( 'form' ); if ( form ) { - form.addEventListener( 'submit', closeOverlay ); + form.addEventListener( 'subscription-modal-loaded', closeOverlay ); } function closeOverlayOnEscapeKeydown( event ) {