diff --git a/CHANGELOG.md b/CHANGELOG.md index b2807e87..4b657f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. The format - Updated: Updated GitHub default & 3rd-party action versions to eliminate (node version warnings)[https://github.com/moderntribe/moose/actions/runs/9617664104]. - Chore: WP version to 6.5.5 - Chore: Composer updates including plugins: limit-login-attempts-reloaded (2.26.8 => 2.26.11), seo-by-rank-math (1.0.218 => 1.0.221), social-sharing-block (1.1.0 => 1.2.0), advanced-custom-fields-pro (6.2.9 => 6.3.2.1) +- Added: Create WP Controls script & documentation. +- Changed: Column block now uses the Create WP Controls script to create the "stacking order" controls. ## [2024.05] - Updated: Pattern definition consistency for usage of `Inserter:` diff --git a/docs/create-wp-controls-script.md b/docs/create-wp-controls-script.md new file mode 100644 index 00000000..a23fd413 --- /dev/null +++ b/docs/create-wp-controls-script.md @@ -0,0 +1,90 @@ +# Create WP Controls Script + +The "Create WP Controls" script is used to quickly and easily add custom, core WP controls to core blocks. Note that while this can be used to add controls to custom blocks, it should only be used with core blocks, as there are better ways to add controls to custom blocks. + +## Supported Control Types + +- [ToggleControl](https://wordpress.github.io/gutenberg/?path=/docs/components-togglecontrol--docs) / `toggle`: A true/false field that can be used to assign a class or property when the control is toggled. +- [NumberControl](https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-numbercontrol--docs) / `number`: A text field that only accepts numbers. Can be used to set a property with the number entered into the field. +- [SelectControl](https://wordpress.github.io/gutenberg/?path=/docs/components-selectcontrol--docs) / `select`: A normal select box field that can be used to give the user options and assign a property based on the value selected. + +## Usage + +1. Import the script +```js +import createWPControls from 'utils/create-wp-controls'; +``` + +2. Create your settings object +```js +const settings = { + attributes: { + stackingOrder: { + type: 'string', + }, + }, + blocks: [ 'core/column' ], + controls: [ + { + applyClass: 'tribe-has-stacking-order', + applyStyleProperty: '--tribe-stacking-order', + attribute: 'stackingOrder', + defaultValue: 0, + helpText: __( + 'The stacking order of the element at mobile breakpoints. This setting only applies if the "Stack on mobile" setting for the Columns block is turned on.', + 'tribe' + ), + label: __( 'Stacking Order', 'tribe' ), + type: 'number', + }, + ], +}; +``` + +Let's break this down a little bit: + +```js +attributes: { + stackingOrder: { + type: 'string', + }, +}, +``` +> :bulb: First, we're creating an `attributes` object that is defining the attributes we want to add to the block. In this case, we're creating a `stackingOrder` attribute for the `core/column` block. + +```js +blocks: [ 'core/column' ], +``` +> :bulb: Next, we define what blocks we want the controls to appear on. In this case we're saying that this control should appear on the `core/column` block. + +```js +controls: [ + { + applyClass: 'tribe-has-stacking-order', + applyStyleProperty: '--tribe-stacking-order', + attribute: 'stackingOrder', + defaultValue: 0, + helpText: __( + 'The stacking order of the element at mobile breakpoints. This setting only applies if the "Stack on mobile" setting for the Columns block is turned on.', + 'tribe' + ), + label: __( 'Stacking Order', 'tribe' ), + type: 'number', + }, +], +``` +> :bulb: Lastly, we're defining the controls array that will be used to define what controls we want to create for the block we've defined. In this case we're defining a `number` control with a default value of `0`. The `applyClass` property tells the create control script that when the value is changed from the default, we should apply the `tribe-has-stacking-order` class. The `applyStyleProperty` property tells the create control script that the `--tribe-stacking-order` style property should be set to the value of the control when the default is changed. + +> :memo: **Note:** You do not always have to set both the `applyClass` and `applyStyleProperty` properties. You can use one or the other separately. Using them together though can be a powerful tool in order to only apply the style property if the class is applied. + +3. Call the create controls script and pass in your settings array. + +```js +createWPControls( settings ); +``` + +## Limitations + +- Because the script currently only supports specific controls, additional time will be required to extend the script if a different type of control needs to be added. This can be done in the `determineControlToRender` function within the script. Make sure to import your control type! +- Currently the script only supports adding a class or a style property. There are no other functions of the script in terms of block output. +- Not really a limitation but I should note that we shouldn't use this script unless it's decided with the project team that these controls should be created using this method. There are other ways that may be better for your projects (block styles, adding classes manually, etc). If you feel strongly that this script gives the client the best experience, go for it! diff --git a/wp-content/themes/core/assets/js/utils/create-wp-controls.js b/wp-content/themes/core/assets/js/utils/create-wp-controls.js new file mode 100644 index 00000000..44138912 --- /dev/null +++ b/wp-content/themes/core/assets/js/utils/create-wp-controls.js @@ -0,0 +1,298 @@ +/** + * @module create-wp-controls + * + * @description handles creating WP controls given an object of settings + * + * @see https://github.com/moderntribe/moose/tree/main/docs/create-wp-controls-script.md + */ + +import { InspectorControls } from '@wordpress/block-editor'; +import { + PanelBody, + SelectControl, + ToggleControl, + __experimentalNumberControl as NumberControl, // eslint-disable-line +} from '@wordpress/components'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { Fragment } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; +import { __ } from '@wordpress/i18n'; + +const state = { + settings: {}, +}; + +/** + * @function applyBlockProps + * + * @description applys additional conditional props to core blocks + * + * @param {*} props + * @param {*} block + * @param {*} attributes + * + * @return {*} return original or updated props + */ +const applyBlockProps = ( props, block, attributes ) => { + // return default props if block isn't in the includes array + if ( ! state.settings.blocks.includes( block.name ) ) { + return props; + } + + state.settings.controls.forEach( ( control ) => { + if ( attributes[ control.attribute ] === undefined ) { + return props; + } + + // determine if we should add a class name to the block + if ( + Object.keys( control ).includes( 'applyClass' ) && + props.className && + ! props.className.includes( control.applyClass ) && + attributes[ control.attribute ] && + attributes[ control.attribute ] !== control.defaultValue + ) { + props.className = `${ props.className } ${ control.applyClass }`; + } + + // determine if we should add style properties to the block + // assumes we only have one style property to add and assigns it to the value of the control created + if ( + Object.keys( control ).includes( 'applyStyleProperty' ) && + attributes[ control.attribute ] && + attributes[ control.attribute ] !== control.defaultValue + ) { + props.style = { + ...props.style, + [ control.applyStyleProperty ]: attributes[ control.attribute ], + }; + } + } ); + + return props; +}; + +/** + * @function applyEditorBlockProps + * + * @description assigns new props / classes to the block + */ +const applyEditorBlockProps = createHigherOrderComponent( + ( BlockListBlock ) => { + return ( props ) => { + const { name, attributes } = props; + const classes = attributes.classes + ? attributes.classes.split( ' ' ) + : []; + const styles = { style: {} }; + + // return default BlockListBlock if we're dealing with an unsupported block + if ( ! state.settings.blocks.includes( name ) ) { + return ; + } + + // loop through controls to assign classes & styles if necessary + state.settings.controls.forEach( ( control ) => { + if ( + Object.keys( control ).includes( 'applyClass' ) && + ! classes.includes( control.applyClass ) && + attributes[ control.attribute ] && + attributes[ control.attribute ] !== control.defaultValue + ) { + classes.push( control.applyClass ); + } + + // styles get added to the `wrapperProps` attribute on the BlockListBlock + if ( + Object.keys( control ).includes( 'applyStyleProperty' ) && + attributes[ control.attribute ] && + attributes[ control.attribute ] !== control.defaultValue + ) { + styles.style = { + [ control.applyStyleProperty ]: + attributes[ control.attribute ], + }; + } + } ); + + return ( + + ); + }; + } +); + +/** + * @function determineControlToRender + * + * @description based on provided settings, choose a type of WP control to render; currently supports ToggleControl & NumberControl. + * + * @param {*} control + * @param {*} attributes + * @param {*} setAttributes + * + * @return {*} returns WP React control component + */ +const determineControlToRender = ( control, attributes, setAttributes ) => { + if ( control.type === 'toggle' ) { + return ( + { + setAttributes( { + [ control.attribute ]: + ! attributes[ control.attribute ], + } ); + } } + /> + ); + } else if ( control.type === 'number' ) { + return ( + { + setAttributes( { + [ control.attribute ]: value, + } ); + } } + min={ 0 } + isShiftStepEnabled={ false } + /> + ); + } else if ( control.type === 'select' ) { + return ( + { + setAttributes( { + [ control.attribute ]: value, + } ); + } } + /> + ); + } +}; + +/** + * @function addBlockControls + * + * @description based on provided settings, adds new controls to existing blocks + */ +const addBlockControls = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + const { attributes, setAttributes, name, isSelected } = props; + + // return default Edit function if block doesn't match blocks we want to target + if ( ! state.settings.blocks.includes( name ) ) { + return ; + } + + // set default attributes if not set + state.settings.controls.forEach( ( control ) => { + if ( ! attributes[ control.attribute ] ) { + attributes[ control.attribute ] = control.defaultValue; + } + } ); + + return ( + + + { isSelected && ( + + + { state.settings.controls.map( ( control ) => + determineControlToRender( + control, + attributes, + setAttributes + ) + ) } + + + ) } + + ); + }; +}, 'addBlockControls' ); + +/** + * @function addBlockAttributes + * + * @description adds new attributes to existing blocks + * + * @param {*} settings + * @param {*} name + * + * @return {*} existing or updated settings + */ +const addBlockAttributes = ( settings, name ) => { + if ( ! state.settings.blocks.includes( name ) ) { + return settings; + } + + if ( settings?.attributes !== undefined ) { + settings.attributes = { + ...settings.attributes, + ...state.settings.attributes, + }; + } + + return settings; +}; + +/** + * @function init + * + * @description assumes settings object is provided; if not, module does nothing + * + * @param {*} settings + */ +const init = ( settings = null ) => { + if ( ! settings ) { + return; + } + + state.settings = settings; + + addFilter( + 'blocks.registerBlockType', + 'tribe/add-block-attributes', + addBlockAttributes + ); + + addFilter( + 'editor.BlockEdit', + 'tribe/add-block-controls', + addBlockControls + ); + + addFilter( + 'editor.BlockListBlock', + 'tribe/add-editor-block-props', + applyEditorBlockProps + ); + + addFilter( + 'blocks.getSaveContent.extraProps', + 'tribe/apply-block-props', + applyBlockProps + ); +}; + +export default init; diff --git a/wp-content/themes/core/assets/pcss/_utilities.pcss b/wp-content/themes/core/assets/pcss/_utilities.pcss index 66c79a19..4d6dcc9f 100644 --- a/wp-content/themes/core/assets/pcss/_utilities.pcss +++ b/wp-content/themes/core/assets/pcss/_utilities.pcss @@ -21,3 +21,6 @@ /* Cards */ @import "cards/_utilities.pcss"; + +/* Media */ +@import "media/_utilities.pcss"; diff --git a/wp-content/themes/core/assets/pcss/global/reset.pcss b/wp-content/themes/core/assets/pcss/global/reset.pcss index 3daa2e06..6beaf015 100644 --- a/wp-content/themes/core/assets/pcss/global/reset.pcss +++ b/wp-content/themes/core/assets/pcss/global/reset.pcss @@ -34,15 +34,6 @@ figcaption { display: block; } -/** - * Avoid text overflows. - */ -p, -:--heading { - overflow-wrap: break-word; - hyphens: auto; -} - /** * Add the correct font size in all browsers. */ diff --git a/wp-content/themes/core/assets/pcss/media/_utilities.pcss b/wp-content/themes/core/assets/pcss/media/_utilities.pcss new file mode 100644 index 00000000..013e59e0 --- /dev/null +++ b/wp-content/themes/core/assets/pcss/media/_utilities.pcss @@ -0,0 +1,38 @@ +/* ------------------------------------------------------------------------- + * + * Aspect Ratio Helpers + * + * Useful for fixing the height of an element based on it's width. + * Since the default Image block contains aspect ratio controls, + * these helpers should only be used if using an aspect ratio that doesn't + * exist within WP. + * + * Example: + *
+ * + *
+ * + * ------------------------------------------------------------------------- */ + +.aspect-ratio-cover, +.aspect-ratio-cover > img { + height: 100%; + width: 100%; + object-fit: cover; +} + +.aspect-ratio-4-5 { + aspect-ratio: 4 / 5; +} + +.aspect-ratio-5-4 { + aspect-ratio: 5 / 4; +} + +.aspect-ratio-16-10 { + aspect-ratio: 16 / 10; +} + +.aspect-ratio-10-16 { + aspect-ratio: 10 / 16; +} diff --git a/wp-content/themes/core/assets/pcss/spacing/_utilities.pcss b/wp-content/themes/core/assets/pcss/spacing/_utilities.pcss index 60bc2603..f505f808 100644 --- a/wp-content/themes/core/assets/pcss/spacing/_utilities.pcss +++ b/wp-content/themes/core/assets/pcss/spacing/_utilities.pcss @@ -1,31 +1,23 @@ /* ------------------------------------------------------------------------- * - * Aspect Ratio Helpers - * - * Useful for fixing the height of an element based on it's width. - * - * Example: - *
- * - *
+ * Spacing Helpers * * ------------------------------------------------------------------------- */ -.s-aspect-ratio-cover, -.s-aspect-ratio-cover > img { - height: 100%; - width: 100%; - object-fit: cover; +.s-remove-margin { + margin: 0 !important; /* override editor default */ } -.s-aspect-ratio-1-1 { - aspect-ratio: 1 / 1; +.s-remove-margin--vertical { + margin-top: 0 !important; /* override editor default */ + margin-bottom: 0 !important; /* override editor default */ } -.s-aspect-ratio-4-3 { - aspect-ratio: 4 / 3; +.s-remove-margin--horizontal { + margin-left: 0 !important; /* override editor default */ + margin-right: 0 !important; /* override editor default */ } -.s-aspect-ratio-16-9 { - aspect-ratio: 16 / 9; +.s-remove-margin--top { + margin-top: 0 !important; /* override editor default */ } diff --git a/wp-content/themes/core/blocks/core/column/editor.js b/wp-content/themes/core/blocks/core/column/editor.js index ef9e10f2..13c8d4ae 100644 --- a/wp-content/themes/core/blocks/core/column/editor.js +++ b/wp-content/themes/core/blocks/core/column/editor.js @@ -1,155 +1,27 @@ -/** - * @module stacking-order - * - * @description handles setting up stacking order settings for columns block - */ - -import { InspectorAdvancedControls } from '@wordpress/block-editor'; -// eslint-disable-next-line -import { __experimentalNumberControl as NumberControl } from '@wordpress/components'; -import { createHigherOrderComponent } from '@wordpress/compose'; -import { Fragment } from '@wordpress/element'; -import { addFilter } from '@wordpress/hooks'; +import createWPControls from 'utils/create-wp-controls'; import { __ } from '@wordpress/i18n'; -/** - * @function applyAnimationProps - * - * @description updates props on the block with new animation settings - * - * @param {Object} props - * @param {Object} block - * @param {Object} attributes - * - * @return {Object} updated props object - */ -const applyStackingOrderProps = ( props, block, attributes ) => { - // return default props if block isn't in the includes array - if ( block.name !== 'core/column' ) { - return props; - } - - const { stackingOrder } = attributes; - - if ( stackingOrder === undefined || stackingOrder === 0 ) { - return props; - } - - props.className = `${ props.className } tribe-has-stacking-order`; - props.style = { - ...props.style, - '--tribe-stacking-order': stackingOrder, - }; - - return props; -}; - -/** - * @function stackingOrderControls - * - * @description creates component that overrides the edit functionality of the block with new stacking order controls - */ -const stackingOrderControls = createHigherOrderComponent( ( BlockEdit ) => { - return ( props ) => { - const { attributes, setAttributes, isSelected, name } = props; - - // return default Edit function if block isn't a column block - if ( name !== 'core/column' ) { - return ; - } - - const { stackingOrder } = attributes; - - let blockClass = - attributes.className !== undefined - ? attributes.className - : 'wp-block-column'; - const blockStyles = { ...props.style }; - - if ( stackingOrder !== undefined && stackingOrder !== 0 ) { - blockClass = `${ blockClass } tribe-has-stacking-order`; - blockStyles[ '--tribe-stacking-order' ] = stackingOrder; - } - - const blockProps = { - ...props, - attributes: { - ...attributes, - className: blockClass, - }, - style: blockStyles, - }; - - return ( - - - { isSelected && ( - - { - setAttributes( { - stackingOrder: newValue, - } ); - } } - min={ 0 } - isShiftStepEnabled={ false } - /> - - ) } - - ); - }; -}, 'stackingOrderControls' ); - -/** - * @function addStackingOrderAttributes - * - * @description add new attributes to blocks for stacking order settings - * - * @param {Object} settings - * @param {string} name - * - * @return {Object} returns updates settings object - */ -const addStackingOrderAttributes = ( settings, name ) => { - // return default settings if block isn't a column block - if ( name !== 'core/column' ) { - return settings; - } - - if ( settings?.attributes !== undefined ) { - settings.attributes = { - ...settings.attributes, - stackingOrder: { - type: 'string', - }, - }; - } - - return settings; +const settings = { + attributes: { + stackingOrder: { + type: 'string', + }, + }, + blocks: [ 'core/column' ], + controls: [ + { + applyClass: 'tribe-has-stacking-order', + applyStyleProperty: '--tribe-stacking-order', + attribute: 'stackingOrder', + defaultValue: 0, + helpText: __( + 'The stacking order of the element at mobile breakpoints. This setting only applies if the "Stack on mobile" setting for the Columns block is turned on.', + 'tribe' + ), + label: __( 'Stacking Order', 'tribe' ), + type: 'number', + }, + ], }; -// register block filters for adding stacking order controls -addFilter( - 'blocks.registerBlockType', - 'tribe/add-stacking-order-options', - addStackingOrderAttributes -); - -addFilter( - 'editor.BlockEdit', - 'tribe/stacking-order-advanced-control', - stackingOrderControls -); - -addFilter( - 'blocks.getSaveContent.extraProps', - 'tribe/apply-stacking-order-classes', - applyStackingOrderProps -); +createWPControls( settings ); diff --git a/wp-content/themes/core/blocks/core/image/style.pcss b/wp-content/themes/core/blocks/core/image/style.pcss index 34795e5c..39e07064 100644 --- a/wp-content/themes/core/blocks/core/image/style.pcss +++ b/wp-content/themes/core/blocks/core/image/style.pcss @@ -10,4 +10,12 @@ margin-bottom: 0; text-align: left; } + + &.is-full-width { + width: 100%; + + & img { + width: 100%; + } + } }