-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #128 from moderntribe/s7/MOOSE-109/migrate-create-…
…wp-controls-script [MOOSE-91/109] Migrate "Create WP Controls" script
- Loading branch information
Showing
9 changed files
with
473 additions
and
179 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! |
298 changes: 298 additions & 0 deletions
298
wp-content/themes/core/assets/js/utils/create-wp-controls.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <BlockListBlock { ...props } />; | ||
} | ||
|
||
// 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 ( | ||
<BlockListBlock | ||
{ ...props } | ||
className={ classes } | ||
wrapperProps={ styles } | ||
/> | ||
); | ||
}; | ||
} | ||
); | ||
|
||
/** | ||
* @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 ( | ||
<ToggleControl | ||
key={ control.attribute } | ||
label={ control.label } | ||
checked={ attributes[ control.attribute ] } | ||
help={ control.helpText } | ||
onChange={ () => { | ||
setAttributes( { | ||
[ control.attribute ]: | ||
! attributes[ control.attribute ], | ||
} ); | ||
} } | ||
/> | ||
); | ||
} else if ( control.type === 'number' ) { | ||
return ( | ||
<NumberControl | ||
key={ control.attribute } | ||
label={ control.label } | ||
value={ attributes[ control.attribute ] } | ||
help={ control.helpText } | ||
onChange={ ( value ) => { | ||
setAttributes( { | ||
[ control.attribute ]: value, | ||
} ); | ||
} } | ||
min={ 0 } | ||
isShiftStepEnabled={ false } | ||
/> | ||
); | ||
} else if ( control.type === 'select' ) { | ||
return ( | ||
<SelectControl | ||
key={ control.attribute } | ||
label={ control.label } | ||
value={ attributes[ control.attribute ] } | ||
help={ control.helpText } | ||
options={ control.selectOptions } | ||
onChange={ ( value ) => { | ||
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 <BlockEdit { ...props } />; | ||
} | ||
|
||
// set default attributes if not set | ||
state.settings.controls.forEach( ( control ) => { | ||
if ( ! attributes[ control.attribute ] ) { | ||
attributes[ control.attribute ] = control.defaultValue; | ||
} | ||
} ); | ||
|
||
return ( | ||
<Fragment> | ||
<BlockEdit { ...props } /> | ||
{ isSelected && ( | ||
<InspectorControls> | ||
<PanelBody | ||
title={ __( 'Custom Block Settings', 'tribe' ) } | ||
> | ||
{ state.settings.controls.map( ( control ) => | ||
determineControlToRender( | ||
control, | ||
attributes, | ||
setAttributes | ||
) | ||
) } | ||
</PanelBody> | ||
</InspectorControls> | ||
) } | ||
</Fragment> | ||
); | ||
}; | ||
}, '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; |
Oops, something went wrong.