diff --git a/src/editor/components/StylesBorder.js b/src/editor/components/StylesBorder.js index 12c4067..7e2b275 100644 --- a/src/editor/components/StylesBorder.js +++ b/src/editor/components/StylesBorder.js @@ -7,6 +7,8 @@ import getThemeOption from '../../utils/get-theme-option'; import EditorContext from '../context/EditorContext'; import StylesContext from '../context/StylesContext'; +import StylesBorderRadius from './StylesBorderRadius'; + /** * Reusable border control style component * @@ -23,21 +25,49 @@ const Border = ( { selector } ) => { ); const onChange = ( newValue ) => { + // If the value has a radius, we need to merge it with the new value + const valueRadius = value?.radius; + let updatedValue = newValue; + if ( valueRadius ) { + updatedValue = { + ...newValue, + radius: valueRadius, + }; + } + + // Set the new value in the user config let config = structuredClone( userConfig ); - config = set( config, selector, newValue ); + config = set( config, selector, updatedValue ); setUserConfig( config ); }; return ( <> - { __( 'Border', 'themer' ) } + { __( 'Border and radius', 'themer' ) } - +
+
+ + { __( 'Border', 'themer' ) } + + +
+
+ + { __( 'Radius', 'themer' ) } + + +
+
); }; diff --git a/src/editor/components/StylesBorderRadius.js b/src/editor/components/StylesBorderRadius.js new file mode 100644 index 0000000..746a6a9 --- /dev/null +++ b/src/editor/components/StylesBorderRadius.js @@ -0,0 +1,221 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { + __experimentalUnitControl as UnitControl, + RangeControl, + __experimentalHStack as HStack, + __experimentalGrid as Grid, + Button, +} from '@wordpress/components'; + +import getThemeOption from '../../utils/get-theme-option'; +import EditorContext from '../context/EditorContext'; +import StylesContext from '../context/StylesContext'; + +/** + * Reusable border control style component + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const BorderRadius = ( { selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ); + + // Set initial state to determine if the values are linked or unlinked + const [ hasLinkedValues, setHasLinkedValues ] = useState( + typeof value === 'object' ? false : true + ); + + /** + * Handle changes to the border radius value + * + * @param {string} newValue The updated linked value + */ + const onChange = ( newValue ) => { + let config = structuredClone( userConfig ); + config = set( config, selector, newValue ); + setUserConfig( config ); + }; + + /** + * Handle toggling between linked and unlinked values + */ + const handleToggleValueType = () => { + setHasLinkedValues( ! hasLinkedValues ); + + /** + * When switching to unlinked values, set each corner to the current value + * + * When switching to linked values, set the value to the topleft value (or the next defined value) + */ + if ( hasLinkedValues ) { + onChange( handleUnlinkedValueChange() ); + } else { + onChange( + value?.topLeft || + value?.topRight || + value?.bottomRight || + value?.bottomLeft + ); + } + }; + + /** + * Handle value changes made via the range control + * + * @param {string} newValue The updated value + */ + const handleRangeValue = ( newValue ) => { + // Get the unit of measurement, or default to px + const valueUnit = value + ? String( value ).replace( /[0-9.]/g, '' ) + : 'px'; + + // Concatenate the new value with the unit of measurement (or use px if one isn't defined) + const updatedValue = newValue + ( valueUnit ?? 'px' ); + + // Update the value + onChange( updatedValue ); + }; + + /** + * A function to handle changes to an unlinked value + * + * @param {string} newValue The updated value + * @param {string} position The border radius position + * + * @return {Object} The updated unlinked values + */ + const handleUnlinkedValueChange = ( newValue, position ) => { + // Define the structure of the unlinked values and set default values, if required + const unlinkedStructure = { + topLeft: value?.topLeft ?? value, + topRight: value?.topRight ?? value, + bottomLeft: value?.bottomLeft ?? value, + bottomRight: value?.bottomRight ?? value, + }; + + // If a value and position are not defined, return the strucure with default values + if ( ! newValue && ! position ) { + return unlinkedStructure; + } + + // Insert in the updated value + const updatedValue = { + ...unlinkedStructure, + [ position ]: newValue, + }; + + // Return the updated values + return updatedValue; + }; + + return ( + <> + + { hasLinkedValues ? ( + +
+ +
+
+
+ +
+
+
+ ) : ( + + + onChange( + handleUnlinkedValueChange( + topLeftValue, + 'topLeft' + ) + ) + } + value={ value?.topLeft ?? value } + /> + + onChange( + handleUnlinkedValueChange( + topRightValue, + 'topRight' + ) + ) + } + value={ value?.topRight ?? value } + /> + + onChange( + handleUnlinkedValueChange( + bottomLeftValue, + 'bottomLeft' + ) + ) + } + value={ value?.bottomLeft ?? value } + /> + + onChange( + handleUnlinkedValueChange( + bottomRightValue, + 'bottomRight' + ) + ) + } + value={ value?.bottomRight ?? value } + /> + + ) } + +
+ + ); +}; + +export default BorderRadius;