From 3c632bae370ca8c30fbccdca827171e8cc2c3bcd Mon Sep 17 00:00:00 2001 From: jcheringer Date: Mon, 9 Jan 2023 15:40:47 -0300 Subject: [PATCH] Contact Form: Form Dropdown revamp (#28010) * PoC: new Dropdown block * changelog * Code clean up * Organizing and cleaning up the code a bit * Dropdown editor improvements * Scripts improvement * Adjust Dropdown toggle label logic * Layout improvements * Observe content changes to generate style variables * Code clean up * Fix tests * Style improvements * Steal font-family from input fields * Sync Dropdown placeholder text * Style adjustments * Fix tests * Fix Dropdown background color * Fix Dropdown width issue * Add chevron animation * Fix tests * Include jQuery UI CSS file * Update changelog description * Adjust chevron size in the Editor * Adjust Dropdown border and add some shadow to the popover * Adjust Dropdown popover shadow * Refactor form-styles to remove jQuery dependency * Update jQuery UI to use only necessary styles * Refactor validateFormWrapper function into a hook * Making the linter happy * Limite Dropdown popover height --- .../jetpack/changelog/update-form-dropdown | 4 + .../blocks/contact-form/child-blocks.js | 160 +++++++----------- .../components/jetpack-field-controls.js | 11 +- .../components/jetpack-field-dropdown.js | 149 ++++++++++++++++ .../blocks/contact-form/editor.scss | 63 +++++++ .../blocks/contact-form/util/focus.js | 23 +++ .../blocks/contact-form/util/form.js | 34 ++++ .../modules/contact-form/css/grunion.css | 86 ++++++++++ .../contact-form/css/jquery-ui-selectmenu.css | 40 +++++ .../contact-form/grunion-contact-form.php | 45 +++++ .../modules/contact-form/js/dropdown.js | 23 +++ .../modules/contact-form/js/form-styles.js | 89 ++++++++++ .../test-class.grunion-contact-form.php | 11 +- 13 files changed, 638 insertions(+), 100 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-form-dropdown create mode 100644 projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-dropdown.js create mode 100644 projects/plugins/jetpack/extensions/blocks/contact-form/util/focus.js create mode 100644 projects/plugins/jetpack/extensions/blocks/contact-form/util/form.js create mode 100644 projects/plugins/jetpack/modules/contact-form/css/jquery-ui-selectmenu.css create mode 100644 projects/plugins/jetpack/modules/contact-form/js/dropdown.js create mode 100644 projects/plugins/jetpack/modules/contact-form/js/form-styles.js diff --git a/projects/plugins/jetpack/changelog/update-form-dropdown b/projects/plugins/jetpack/changelog/update-form-dropdown new file mode 100644 index 0000000000000..e17391becc1da --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-form-dropdown @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Several UI and UX improvements for the Dropdown block diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js b/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js index 1314d5ca0cb85..b7be5eef5d007 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js @@ -1,16 +1,16 @@ -import { store as blockEditorStore } from '@wordpress/block-editor'; import { createBlock, getBlockType } from '@wordpress/blocks'; import { Path } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { Fragment, useEffect } from '@wordpress/element'; +import { Fragment } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; import { getIconColor } from '../../shared/block-icons'; import renderMaterialIcon from '../../shared/render-material-icon'; import JetpackField from './components/jetpack-field'; import JetpackFieldCheckbox from './components/jetpack-field-checkbox'; import JetpackFieldConsent from './components/jetpack-field-consent'; +import { JetpackDropdownEdit } from './components/jetpack-field-dropdown'; import JetpackFieldMultiple from './components/jetpack-field-multiple'; import JetpackFieldTextarea from './components/jetpack-field-textarea'; +import { useFormWrapper } from './util/form'; const FieldDefaults = { category: 'contact-form', @@ -32,7 +32,7 @@ const FieldDefaults = { }, options: { type: 'array', - default: [], + default: [ '' ], }, defaultValue: { type: 'string', @@ -125,44 +125,12 @@ const FieldDefaults = { example: {}, }; -const validateFormWrapper = ( { attributes, clientId, name } ) => { - const FORM_BLOCK_NAME = 'jetpack/contact-form'; - const BUTTON_BLOCK_NAME = 'jetpack/button'; - const SUBMIT_BUTTON_ATTR = { - text: __( 'Submit', 'jetpack' ), - element: 'button', - lock: { remove: true }, - }; - - // eslint-disable-next-line react-hooks/rules-of-hooks - const { replaceBlock } = useDispatch( blockEditorStore ); - - // eslint-disable-next-line react-hooks/rules-of-hooks - const parents = useSelect( select => { - return select( blockEditorStore ).getBlockParentsByBlockName( clientId, FORM_BLOCK_NAME ); - } ); - - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect( () => { - if ( ! parents?.length ) { - replaceBlock( - clientId, - createBlock( FORM_BLOCK_NAME, {}, [ - createBlock( name, attributes ), - createBlock( BUTTON_BLOCK_NAME, SUBMIT_BUTTON_ATTR ), - ] ) - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); -}; - const getFieldLabel = ( { attributes, name: blockName } ) => { return null === attributes.label ? getBlockType( blockName ).title : attributes.label; }; const editField = type => props => { - validateFormWrapper( props ); + useFormWrapper( props ); return ( props => { }; const editMultiField = type => props => { - validateFormWrapper( props ); + useFormWrapper( props ); return ( props => { ); }; +const EditTextarea = props => { + useFormWrapper( props ); + + return ( + + ); +}; + +const EditCheckbox = props => { + useFormWrapper( props ); + + return ( + + ); +}; + +const EditConsent = ( { attributes, clientId, isSelected, name, setAttributes } ) => { + useFormWrapper( { attributes, clientId, name } ); + + const { id, width, consentType, implicitConsentMessage, explicitConsentMessage } = attributes; + return ( + + ); +}; + export const childBlocks = [ { name: 'field-text', @@ -365,23 +385,7 @@ export const childBlocks = [ d="M20 5H4V6.5H20V5ZM5.5 11.5H18.5V18.5H5.5V11.5ZM20 20V10H4V20H20Z" /> ), - edit: props => { - validateFormWrapper( props ); - - return ( - - ); - }, + edit: EditTextarea, attributes: { ...FieldDefaults.attributes, }, @@ -401,22 +405,7 @@ export const childBlocks = [ d="M6.125 6H17.875C17.944 6 18 6.05596 18 6.125V17.875C18 17.944 17.944 18 17.875 18H6.125C6.05596 18 6 17.944 6 17.875V6.125C6 6.05596 6.05596 6 6.125 6ZM4.5 6.125C4.5 5.22754 5.22754 4.5 6.125 4.5H17.875C18.7725 4.5 19.5 5.22754 19.5 6.125V17.875C19.5 18.7725 18.7725 19.5 17.875 19.5H6.125C5.22754 19.5 4.5 18.7725 4.5 17.875V6.125ZM10.5171 16.4421L16.5897 8.71335L15.4103 7.78662L10.4828 14.0579L8.57616 11.7698L7.42383 12.7301L10.5171 16.4421Z" /> ), - edit: props => { - validateFormWrapper( props ); - - return ( - - ); - }, + edit: EditCheckbox, attributes: { ...FieldDefaults.attributes, label: { @@ -470,28 +459,7 @@ export const childBlocks = [ default: __( 'Can we send you an email from time to time?', 'jetpack' ), }, }, - edit: ( { attributes, clientId, isSelected, name, setAttributes } ) => { - validateFormWrapper( { attributes, clientId, name } ); - - const { - id, - width, - consentType, - implicitConsentMessage, - explicitConsentMessage, - } = attributes; - return ( - - ); - }, + edit: EditConsent, }, }, { @@ -568,12 +536,12 @@ export const childBlocks = [ d="M5 4.5H19C19.2761 4.5 19.5 4.72386 19.5 5V19C19.5 19.2761 19.2761 19.5 19 19.5H5C4.72386 19.5 4.5 19.2761 4.5 19V5C4.5 4.72386 4.72386 4.5 5 4.5ZM19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3ZM8.93582 10.1396L8.06396 11.3602L11.9999 14.1716L15.9358 11.3602L15.064 10.1396L11.9999 12.3283L8.93582 10.1396Z" /> ), - edit: editMultiField( 'select' ), + edit: JetpackDropdownEdit, attributes: { ...FieldDefaults.attributes, - label: { + toggleLabel: { type: 'string', - default: 'Select one option', + default: null, }, }, }, diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-controls.js b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-controls.js index e29cb4e86d147..1560f9bcb00f9 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-controls.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-controls.js @@ -17,7 +17,14 @@ import JetpackFieldCss from './jetpack-field-css'; import JetpackFieldWidth from './jetpack-field-width'; import JetpackManageResponsesSettings from './jetpack-manage-responses-settings'; -const JetpackFieldControls = ( { setAttributes, width, id, required, placeholder } ) => { +const JetpackFieldControls = ( { + setAttributes, + width, + id, + required, + placeholder, + placeholderField = 'placeholder', +} ) => { return ( <> @@ -54,7 +61,7 @@ const JetpackFieldControls = ( { setAttributes, width, id, required, placeholder setAttributes( { placeholder: value } ) } + onChange={ value => setAttributes( { [ placeholderField ]: value } ) } help={ __( 'Show visitors an example of the type of content expected. Otherwise, leave blank.', 'jetpack' diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-dropdown.js b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-dropdown.js new file mode 100644 index 0000000000000..f3a5b165901f2 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-dropdown.js @@ -0,0 +1,149 @@ +import { RichText } from '@wordpress/block-editor'; +import { useEffect, useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { isEmpty, isNil, noop, split, trim } from 'lodash'; +import { setFocus } from '../util/focus'; +import { useFormWrapper } from '../util/form'; +import JetpackFieldControls from './jetpack-field-controls'; +import JetpackFieldLabel from './jetpack-field-label'; + +export const JetpackDropdownEdit = ( { + attributes, + clientId, + isSelected, + name, + setAttributes, +} ) => { + const { id, label, options, required, requiredText, toggleLabel, width } = attributes; + const optionsWrapper = useRef(); + + useFormWrapper( { attributes, clientId, name } ); + + const changeFocus = ( index, cursorToEnd ) => + setFocus( optionsWrapper.current, '[role=textbox]', index, cursorToEnd ); + + const handleSingleValue = ( index, value ) => { + const _options = [ ...options ]; + + _options[ index ] = value; + + setAttributes( { options: _options } ); + changeFocus( index ); + }; + + const handleMultiValues = ( index, array ) => { + const _options = [ ...attributes.options ]; + const cursorToEnd = array[ array.length - 1 ] !== ''; + + if ( _options[ index ] ) { + _options[ index ] = array.shift(); + index++; + } + + _options.splice( index, 0, ...array ); + + setAttributes( { options: _options } ); + changeFocus( index + array.length - 1, cursorToEnd ); + }; + + const handleChangeOption = index => value => { + const values = split( value, '\n' ).filter( op => op && trim( op ) !== '' ); + + if ( ! values.length ) { + return; + } + + if ( values.length > 1 ) { + handleMultiValues( index, values ); + } else { + handleSingleValue( index, values.pop() ); + } + }; + + const handleSplitOption = index => ( value, isOriginal ) => { + if ( ! isOriginal ) { + return; + } + + const splitValue = attributes.options[ index ].slice( value.length ); + + if ( isEmpty( value ) && isEmpty( splitValue ) ) { + return; + } + + handleMultiValues( index, [ value, splitValue ] ); + }; + + const handleDeleteOption = index => () => { + if ( attributes.options.length === 1 ) { + return; + } + + const _options = [ ...attributes.options ]; + _options.splice( index, 1 ); + setAttributes( { options: _options } ); + changeFocus( Math.max( index - 1, 0 ), true ); + }; + + useEffect( () => { + if ( isNil( label ) ) { + setAttributes( { label: '' } ); + } + + if ( isNil( toggleLabel ) ) { + setAttributes( { toggleLabel: __( 'Select one option', 'jetpack' ) } ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + + return ( + <> + +
+
+ { + setAttributes( { toggleLabel: value } ); + } } + allowedFormats={ [ 'core/bold', 'core/italic' ] } + withoutInteractiveFormatting + /> + +
+ + { isSelected && ( +
+ { options.map( ( option, index ) => ( + + ) ) } +
+ ) } +
+ + + + ); +}; diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss b/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss index 9aafdcce975e4..b1ac343aae5c1 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss @@ -481,3 +481,66 @@ input.components-text-control__input { .jetpack-contact-form__crm_toggle p { margin-bottom: 0; } + +.jetpack-field-dropdown { + width: 100%; + display: inline-flex; + flex-direction: column; + font-family: var(--jetpack--contact-form--font-family); + + &__toggle { + border: var(--jetpack--contact-form--border); + border-radius: var(--jetpack--contact-form--border-radius); + background-color: var(--jetpack--contact-form--input-background); + color: var(--jetpack--contact-form--text-color); + font-size: var(--jetpack--contact-form--font-size); + line-height: var(--jetpack--contact-form--line-height); + padding: var(--jetpack--contact-form--input-padding); + display: flex; + align-items: center; + justify-content: space-between; + } + + &__icon { + width: 0.8em; + height: 0.8em; + position: relative; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + border: 2px solid transparent; + overflow: visible; + margin-right: 4px; + } + + &__icon::after { + content: ""; + display: block; + box-sizing: border-box; + width: 100%; + height: 100%; + border-bottom: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + margin-top: -2px; + } + + &__popover { + border: var(--jetpack--contact-form--border); + border-radius: var(--jetpack--contact-form--border-radius); + background-color: var(--jetpack--contact-form--input-background); + color: var(--jetpack--contact-form--text-color); + font-size: var(--jetpack--contact-form--font-size); + max-height: 165px; + overflow: auto; + padding: 0; + line-height: normal; + margin-top: 8px; + box-shadow: 0 2px 6px rgb(0 0 0 / 5%); + + .rich-text { + padding: var(--jetpack--contact-form--input-padding); + } + } +} diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/util/focus.js b/projects/plugins/jetpack/extensions/blocks/contact-form/util/focus.js new file mode 100644 index 0000000000000..23d6bbaff2ca6 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/util/focus.js @@ -0,0 +1,23 @@ +import { tap } from 'lodash'; + +export const setFocus = ( wrapper, selector, index, cursorToEnd ) => + setTimeout( () => { + tap( wrapper.querySelectorAll( selector )[ index ], input => { + if ( ! input ) { + return; + } + + input.focus(); + + // Allows moving the cursor to the end of + // 'contenteditable' elements like + if ( document.createRange && cursorToEnd ) { + const range = document.createRange(); + range.selectNodeContents( input ); + range.collapse( false ); + const selection = document.defaultView.getSelection(); + selection.removeAllRanges(); + selection.addRange( range ); + } + } ); + }, 0 ); diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/util/form.js b/projects/plugins/jetpack/extensions/blocks/contact-form/util/form.js new file mode 100644 index 0000000000000..6fe2b41f614b6 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/util/form.js @@ -0,0 +1,34 @@ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { createBlock } from '@wordpress/blocks'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const useFormWrapper = ( { attributes, clientId, name } ) => { + const FORM_BLOCK_NAME = 'jetpack/contact-form'; + const BUTTON_BLOCK_NAME = 'jetpack/button'; + const SUBMIT_BUTTON_ATTR = { + text: __( 'Submit', 'jetpack' ), + element: 'button', + lock: { remove: true }, + }; + + const { replaceBlock } = useDispatch( blockEditorStore ); + + const parents = useSelect( select => { + return select( blockEditorStore ).getBlockParentsByBlockName( clientId, FORM_BLOCK_NAME ); + } ); + + useEffect( () => { + if ( ! parents?.length ) { + replaceBlock( + clientId, + createBlock( FORM_BLOCK_NAME, {}, [ + createBlock( name, attributes ), + createBlock( BUTTON_BLOCK_NAME, SUBMIT_BUTTON_ATTR ), + ] ) + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); +}; diff --git a/projects/plugins/jetpack/modules/contact-form/css/grunion.css b/projects/plugins/jetpack/modules/contact-form/css/grunion.css index ec9270b875587..765dee11c0b78 100644 --- a/projects/plugins/jetpack/modules/contact-form/css/grunion.css +++ b/projects/plugins/jetpack/modules/contact-form/css/grunion.css @@ -245,3 +245,89 @@ .wp-block-jetpack-contact-form .wp-block-spacer { width: 100%; } + +.contact-form-dropdown-wrap { + z-index: unset; +} + +.contact-form .contact-form-dropdown__button.ui-button { + width: 100%; + box-sizing: border-box; + display: flex; + align-items: center; + flex-direction: row-reverse; + justify-content: space-between; + border: var(--jetpack--contact-form--border); + border-radius: var(--jetpack--contact-form--border-radius); + background-color: var(--jetpack--contact-form--input-background); + color: var(--jetpack--contact-form--text-color); + font-size: var(--jetpack--contact-form--font-size); + font-family: var(--jetpack--contact-form--font-family); + line-height: var(--jetpack--contact-form--line-height); + padding: var(--jetpack--contact-form--input-padding); +} + +.contact-form .contact-form-dropdown__button.ui-button .ui-selectmenu-icon.ui-icon { + background: none; +} + +.contact-form .contact-form-dropdown__button .ui-selectmenu-icon, +.contact-form .contact-form-dropdown__button.ui-selectmenu-button-open .ui-selectmenu-icon { + width: 0.8em; + height: 0.8em; + position: relative; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + border: 2px solid transparent; + overflow: visible; + margin-right: 4px; +} + +.contact-form .contact-form-dropdown__button .ui-selectmenu-icon::after { + content: ""; + display: block; + box-sizing: border-box; + width: 100%; + height: 100%; + border-bottom: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + margin-top: -5px; + transition: all 0.2s ease-in-out; +} + +.contact-form .contact-form-dropdown__button.ui-selectmenu-button-open .ui-selectmenu-icon::after { + transform: rotate(225deg); + margin-top: 8px; +} + +.contact-form .contact-form-dropdown__menu ul.ui-menu { + border: var(--jetpack--contact-form--border); + border-radius: var(--jetpack--contact-form--border-radius); + background-color: var(--jetpack--contact-form--input-background); + color: var(--jetpack--contact-form--text-color); + font-size: var(--jetpack--contact-form--font-size); + font-family: var(--jetpack--contact-form--font-family); + max-height: 230px; + overflow: auto; + padding: 0; + line-height: normal; + box-shadow: 0 2px 6px rgb(0 0 0 / 5%); +} + +.contact-form .contact-form-dropdown__menu .ui-menu { + margin-top: 8px; +} + +.contact-form .contact-form-dropdown__menu .ui-menu .ui-menu-item-wrapper { + padding: var(--jetpack--contact-form--input-padding); +} + +.contact-form .contact-form-dropdown__menu .ui-menu .ui-menu-item-wrapper.ui-state-active { + position: relative; + border: none; + color: var(--jetpack--contact-form--input-background); + background-color: var(--jetpack--contact-form--primary-color); +} diff --git a/projects/plugins/jetpack/modules/contact-form/css/jquery-ui-selectmenu.css b/projects/plugins/jetpack/modules/contact-form/css/jquery-ui-selectmenu.css new file mode 100644 index 0000000000000..bffb2a5f418a8 --- /dev/null +++ b/projects/plugins/jetpack/modules/contact-form/css/jquery-ui-selectmenu.css @@ -0,0 +1,40 @@ +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} diff --git a/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php index df992696bf251..b99fd857bd6ad 100644 --- a/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php +++ b/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php @@ -388,9 +388,23 @@ protected function __construct() { wp_register_style( 'grunion.css', GRUNION_PLUGIN_URL . 'css/grunion.css', array(), JETPACK__VERSION ); wp_style_add_data( 'grunion.css', 'rtl', 'replace' ); + self::enqueue_contact_forms_style_script(); self::register_contact_form_blocks(); } + /** + * Enqueue scripts responsible for handling contact form styles. + */ + private static function enqueue_contact_forms_style_script() { + wp_enqueue_script( + 'contact-form-styles', + plugins_url( 'js/form-styles.js', __FILE__ ), + array(), + JETPACK__VERSION, + true + ); + } + /** * Register the contact form block. */ @@ -4021,6 +4035,7 @@ public function __construct( $attributes, $content = null, $form = null ) { $attributes = shortcode_atts( array( 'label' => null, + 'togglelabel' => null, 'type' => 'text', 'required' => false, 'requiredtext' => null, @@ -4552,6 +4567,11 @@ public function render_checkbox_multiple_field( $id, $label, $value, $class, $re public function render_select_field( $id, $label, $value, $class, $required, $required_field_text ) { $field = $this->render_label( 'select', $id, $label, $required, $required_field_text ); $field .= "\t\n"; + + wp_enqueue_style( + 'jquery-ui-selectmenu', + plugins_url( 'css/jquery-ui-selectmenu.css', __FILE__ ), + array(), + '1.13.2' + ); + + wp_enqueue_script( 'jquery-ui-selectmenu' ); + + wp_enqueue_script( + 'contact-form-dropdown', + plugins_url( 'js/dropdown.js', __FILE__ ), + array( 'jquery', 'jquery-ui-selectmenu' ), + JETPACK__VERSION, + true + ); + return $field; } @@ -4646,11 +4684,18 @@ public function render_default_field( $id, $label, $value, $class, $required, $r * @return string HTML */ public function render_field( $type, $id, $label, $value, $class, $placeholder, $required, $required_field_text ) { + if ( $type === 'select' ) { + $class .= ' contact-form-dropdown'; + } $field_placeholder = ( ! empty( $placeholder ) ) ? "placeholder='" . esc_attr( $placeholder ) . "'" : ''; $field_class = "class='" . trim( esc_attr( $type ) . ' ' . esc_attr( $class ) ) . "' "; $wrap_classes = empty( $class ) ? '' : implode( '-wrap ', array_filter( explode( ' ', $class ) ) ) . '-wrap'; // this adds + if ( $type === 'select' ) { + $wrap_classes .= ' ui-front'; + } + $shell_field_class = "class='grunion-field-wrap grunion-field-" . trim( esc_attr( $type ) . '-wrap ' . esc_attr( $wrap_classes ) ) . "' "; /** diff --git a/projects/plugins/jetpack/modules/contact-form/js/dropdown.js b/projects/plugins/jetpack/modules/contact-form/js/dropdown.js new file mode 100644 index 0000000000000..4cf6d8bb7042b --- /dev/null +++ b/projects/plugins/jetpack/modules/contact-form/js/dropdown.js @@ -0,0 +1,23 @@ +jQuery( function ( $ ) { + $( document ).ready( function () { + initializeSelectMenu(); + + const observer = new MutationObserver( () => { + initializeSelectMenu(); + } ); + + observer.observe( document.querySelector( 'body' ), { + childList: true, + subtree: true, + } ); + } ); + + function initializeSelectMenu() { + $( '.contact-form .contact-form-dropdown' ).selectmenu( { + classes: { + 'ui-selectmenu-button': 'contact-form-dropdown__button', + 'ui-selectmenu-menu': 'contact-form-dropdown__menu', + }, + } ); + } +} ); diff --git a/projects/plugins/jetpack/modules/contact-form/js/form-styles.js b/projects/plugins/jetpack/modules/contact-form/js/form-styles.js new file mode 100644 index 0000000000000..3def87e060b12 --- /dev/null +++ b/projects/plugins/jetpack/modules/contact-form/js/form-styles.js @@ -0,0 +1,89 @@ +window.addEventListener( 'load', () => { + const FRONTEND_SELECTOR = '.wp-block-jetpack-contact-form-container'; + const EDITOR_SELECTOR = '[data-type="jetpack/contact-form"]'; + + const observer = new MutationObserver( () => { + generateStyleVariables( FRONTEND_SELECTOR ); + generateStyleVariables( EDITOR_SELECTOR ); + } ); + + observer.observe( document.querySelector( 'body' ), { + childList: true, + subtree: true, + } ); + + //Make sure to execute at least once if not triggered by the observer + setTimeout( () => { + generateStyleVariables( FRONTEND_SELECTOR ); + generateStyleVariables( EDITOR_SELECTOR ); + }, 300 ); +} ); + +function generateStyleVariables( selector, outputSelector = 'body' ) { + const STYLE_PROBE_CLASS = 'contact-form__style-probe'; + const STYLE_PROBE_STYLE = + 'position: absolute; z-index: -1; width: 1px; height: 1px; visibility: hidden'; + const HTML = ` +
+
+ +
+
+ +
+
+ `; + + const iframeCanvas = document.querySelector( 'iframe[name="editor-canvas"]' ); + const doc = iframeCanvas ? iframeCanvas.contentDocument : document; + + if ( + ! doc.querySelectorAll( selector ).length || + doc.querySelectorAll( `.${ STYLE_PROBE_CLASS }` ).length + ) { + return; + } + + const styleProbe = doc.createElement( 'div' ); + styleProbe.className = STYLE_PROBE_CLASS; + styleProbe.style = STYLE_PROBE_STYLE; + styleProbe.innerHTML = HTML; + + const container = doc.querySelector( selector ); + container.appendChild( styleProbe ); + + const bodyNode = doc.querySelector( 'body' ); + const buttonNode = styleProbe.querySelector( '.wp-block-button__link' ); + const inputNode = styleProbe.querySelector( 'input[type="text"]' ); + + const backgroundColor = window.getComputedStyle( bodyNode ).backgroundColor; + const primaryColor = window.getComputedStyle( buttonNode ).borderColor; + const { + color: textColor, + padding: inputPadding, + backgroundColor: inputBackground, + border, + borderColor, + borderWidth, + borderStyle, + borderRadius, + fontSize, + fontFamily, + lineHeight, + } = window.getComputedStyle( inputNode ); + + const outputContainer = doc.querySelector( outputSelector ); + outputContainer.style.setProperty( '--jetpack--contact-form--primary-color', primaryColor ); + outputContainer.style.setProperty( '--jetpack--contact-form--background-color', backgroundColor ); + outputContainer.style.setProperty( '--jetpack--contact-form--text-color', textColor ); + outputContainer.style.setProperty( '--jetpack--contact-form--border', border ); + outputContainer.style.setProperty( '--jetpack--contact-form--border-color', borderColor ); + outputContainer.style.setProperty( '--jetpack--contact-form--border-size', borderWidth ); + outputContainer.style.setProperty( '--jetpack--contact-form--border-style', borderStyle ); + outputContainer.style.setProperty( '--jetpack--contact-form--border-radius', borderRadius ); + outputContainer.style.setProperty( '--jetpack--contact-form--input-background', inputBackground ); + outputContainer.style.setProperty( '--jetpack--contact-form--input-padding', inputPadding ); + outputContainer.style.setProperty( '--jetpack--contact-form--font-size', fontSize ); + outputContainer.style.setProperty( '--jetpack--contact-form--font-family', fontFamily ); + outputContainer.style.setProperty( '--jetpack--contact-form--line-height', lineHeight ); +} diff --git a/projects/plugins/jetpack/tests/php/modules/contact-form/test-class.grunion-contact-form.php b/projects/plugins/jetpack/tests/php/modules/contact-form/test-class.grunion-contact-form.php index a3b5cbea28573..b3ca938ded07a 100644 --- a/projects/plugins/jetpack/tests/php/modules/contact-form/test-class.grunion-contact-form.php +++ b/projects/plugins/jetpack/tests/php/modules/contact-form/test-class.grunion-contact-form.php @@ -929,9 +929,16 @@ public function assertCommonValidHtml( $wrapper_div, $attributes ) { if ( 'date' === $attributes['type'] ) { $attributes['class'] = 'jp-contact-form-date'; } + + $css_class = "grunion-field-wrap grunion-field-{$attributes['type']}-wrap {$attributes['class']}-wrap"; + + if ( 'select' === $attributes['type'] ) { + $css_class .= ' contact-form-dropdown-wrap ui-front'; + } + $this->assertEquals( $wrapper_div->getAttribute( 'class' ), - "grunion-field-wrap grunion-field-{$attributes['type']}-wrap {$attributes['class']}-wrap", + $css_class, 'div class attribute doesn\'t match' ); @@ -1062,7 +1069,7 @@ public function assertValidFieldMultiField( $html, $attributes ) { 'label for does not equal input name!' ); - $this->assertEquals( $select->getAttribute( 'class' ), 'select ' . $attributes['class'], ' select class does not match expected' ); + $this->assertEquals( $select->getAttribute( 'class' ), 'select ' . $attributes['class'] . ' contact-form-dropdown', ' select class does not match expected' ); // Options. $options = $select->getElementsByTagName( 'option' );