From 9c8a7447fb1fff2ff24a48faa0e7c47aa71704b8 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 1 Mar 2023 14:51:37 +0100 Subject: [PATCH] Use the BorderPanel component in block inspector --- .../components/global-styles/border-panel.js | 67 +++- .../block-editor/src/hooks/border-radius.js | 70 ---- packages/block-editor/src/hooks/border.js | 319 ++++++------------ packages/block-editor/src/hooks/utils.js | 33 ++ 4 files changed, 187 insertions(+), 302 deletions(-) delete mode 100644 packages/block-editor/src/hooks/border-radius.js diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js index b2313a139c9d0..83979b2845c4b 100644 --- a/packages/block-editor/src/components/global-styles/border-panel.js +++ b/packages/block-editor/src/components/global-styles/border-panel.js @@ -8,7 +8,7 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -98,8 +98,9 @@ function BorderToolsPanel( { } const DEFAULT_CONTROLS = { - borderStyle: true, - borderRadius: true, + radius: true, + color: true, + width: true, }; export default function BorderPanel( { @@ -111,12 +112,52 @@ export default function BorderPanel( { panelId, defaultControls = DEFAULT_CONTROLS, } ) { + const colors = useColorsPerOrigin( settings ); const decodeValue = ( rawValue ) => getValueFromVariable( { settings }, '', rawValue ); - const border = inheritedValue?.border; + const encodeColorValue = ( colorValue ) => { + const allColors = colors.flatMap( + ( { colors: originColors } ) => originColors + ); + const colorObject = allColors.find( + ( { color } ) => color === colorValue + ); + return colorObject + ? 'var:preset|color|' + colorObject.slug + : colorValue; + }; + const decodeColorValue = useCallback( + ( colorValue ) => { + const allColors = colors.flatMap( + ( { colors: originColors } ) => originColors + ); + const colorObject = allColors.find( + ( { slug } ) => colorValue === 'var:preset|color|' + slug + ); + return colorObject ? colorObject.color : colorValue; + }, + [ colors ] + ); + const border = useMemo( () => { + if ( hasSplitBorders( inheritedValue?.border ) ) { + const borderValue = { ...inheritedValue?.border }; + [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => { + borderValue[ side ] = { + ...borderValue[ side ], + color: decodeColorValue( borderValue[ side ]?.color ), + }; + } ); + return borderValue; + } + return { + ...inheritedValue?.border, + color: inheritedValue?.border?.color + ? decodeColorValue( inheritedValue?.border?.color ) + : undefined, + }; + }, [ inheritedValue?.border, decodeColorValue ] ); const setBorder = ( newBorder ) => onChange( { ...value, border: newBorder } ); - const colors = useColorsPerOrigin( settings ); const showBorderColor = useHasBorderColorControl( settings ); const showBorderStyle = useHasBorderStyleControl( settings ); const showBorderWidth = useHasBorderWidthControl( settings ); @@ -172,6 +213,13 @@ export default function BorderPanel( { ...newBorderWithStyle, }; + [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => { + updatedBorder[ side ] = { + ...updatedBorder[ side ], + color: encodeColorValue( updatedBorder[ side ]?.color ), + }; + } ); + // As radius is maintained separately to color, style, and width // maintain its value. Undefined values here will be cleaned when // global styles are saved. @@ -185,6 +233,9 @@ export default function BorderPanel( { }; }, [] ); + const showBorderByDefault = + defaultControls?.color || defaultControls?.width; + return ( isDefinedBorder( value?.border ) } label={ __( 'Border' ) } onDeselect={ () => resetBorder() } - isShownByDefault={ defaultControls.borderStyle } + isShownByDefault={ showBorderByDefault } + panelId={ panelId } > setBorderRadius( undefined ) } - isShownByDefault={ defaultControls.borderRadius } + isShownByDefault={ defaultControls.radius } + panelId={ panelId } > { - const newStyle = cleanEmptyObject( { - ...style, - border: { - ...style?.border, - radius: newRadius, - }, - } ); - - setAttributes( { style: newStyle } ); - }; - - return ( - - ); -} - -/** - * Checks if there is a current value in the border radius block support - * attributes. - * - * @param {Object} props Block props. - * @return {boolean} Whether or not the block has a border radius value set. - */ -export function hasBorderRadiusValue( props ) { - const borderRadius = props.attributes.style?.border?.radius; - - if ( typeof borderRadius === 'object' ) { - return Object.entries( borderRadius ).some( Boolean ); - } - - return !! borderRadius; -} - -/** - * Resets the border radius block support attributes. This can be used when - * disabling the border radius support controls for a block via a progressive - * discovery panel. - * - * @param {Object} props Block props. - * @param {Object} props.attributes Block's attributes. - * @param {Object} props.setAttributes Function to set block's attributes. - */ -export function resetBorderRadius( { attributes = {}, setAttributes } ) { - const { style } = attributes; - setAttributes( { style: removeBorderAttribute( style, 'radius' ) } ); -} diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index d99416e6cd665..a5f08b64515b0 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -7,66 +7,29 @@ import classnames from 'classnames'; * WordPress dependencies */ import { getBlockSupport } from '@wordpress/blocks'; -import { - __experimentalBorderBoxControl as BorderBoxControl, - __experimentalHasSplitBorders as hasSplitBorders, - __experimentalIsDefinedBorder as isDefinedBorder, - __experimentalToolsPanelItem as ToolsPanelItem, -} from '@wordpress/components'; +import { __experimentalHasSplitBorders as hasSplitBorders } from '@wordpress/components'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { Platform } from '@wordpress/element'; +import { Platform, useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { - BorderRadiusEdit, - hasBorderRadiusValue, - resetBorderRadius, -} from './border-radius'; import { getColorClassName } from '../components/colors'; import InspectorControls from '../components/inspector-controls'; import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients'; -import useSetting from '../components/use-setting'; -import { cleanEmptyObject, shouldSkipSerialization } from './utils'; +import { + cleanEmptyObject, + shouldSkipSerialization, + useBlockSettings, +} from './utils'; +import { + useHasBorderPanel, + BorderPanel as StylesBorderPanel, +} from '../components/global-styles'; export const BORDER_SUPPORT_KEY = '__experimentalBorder'; -const borderSides = [ 'top', 'right', 'bottom', 'left' ]; - -const hasBorderValue = ( props ) => { - const { borderColor, style } = props.attributes; - return isDefinedBorder( style?.border ) || !! borderColor; -}; - -// The border color, style, and width are omitted so they get undefined. The -// border radius is separate and must retain its selection. -const resetBorder = ( { attributes = {}, setAttributes } ) => { - const { style } = attributes; - setAttributes( { - borderColor: undefined, - style: { - ...style, - border: cleanEmptyObject( { - radius: style?.border?.radius, - } ), - }, - } ); -}; - -const resetBorderFilter = ( newAttributes ) => ( { - ...newAttributes, - borderColor: undefined, - style: { - ...newAttributes.style, - border: { - radius: newAttributes.style?.border?.radius, - }, - }, -} ); - const getColorByProperty = ( colors, property, value ) => { let matchedColor; @@ -103,51 +66,6 @@ export const getMultiOriginColor = ( { colors, namedColor, customColor } ) => { return colorObject ? colorObject : { color: customColor }; }; -const getBorderObject = ( attributes, colors ) => { - const { borderColor, style } = attributes; - const { border: borderStyles } = style || {}; - - // If we have a named color for a flat border. Fetch that color object and - // apply that color's value to the color property within the style object. - if ( borderColor ) { - const { color } = getMultiOriginColor( { - colors, - namedColor: borderColor, - } ); - - return color ? { ...borderStyles, color } : borderStyles; - } - - // Individual side border color slugs are stored within the border style - // object. If we don't have a border styles object we have nothing further - // to hydrate. - if ( ! borderStyles ) { - return borderStyles; - } - - // If we have named colors for the individual side borders, retrieve their - // related color objects and apply the real color values to the split - // border objects. - const hydratedBorderStyles = { ...borderStyles }; - borderSides.forEach( ( side ) => { - const colorSlug = getColorSlugFromVariable( - hydratedBorderStyles[ side ]?.color - ); - if ( colorSlug ) { - const { color } = getMultiOriginColor( { - colors, - namedColor: colorSlug, - } ); - hydratedBorderStyles[ side ] = { - ...hydratedBorderStyles[ side ], - color, - }; - } - } ); - - return hydratedBorderStyles; -}; - function getColorSlugFromVariable( value ) { const namedColor = /var:preset\|color\|(.+)/.exec( value ); if ( namedColor && namedColor[ 1 ] ) { @@ -156,150 +74,101 @@ function getColorSlugFromVariable( value ) { return null; } -export function BorderPanel( props ) { - const { attributes, clientId, setAttributes } = props; - const { style } = attributes; - const { colors } = useMultipleOriginColorsAndGradients(); - - const isSupported = hasBorderSupport( props.name ); - const isColorSupported = - useSetting( 'border.color' ) && hasBorderSupport( props.name, 'color' ); - const isRadiusSupported = - useSetting( 'border.radius' ) && - hasBorderSupport( props.name, 'radius' ); - const isStyleSupported = - useSetting( 'border.style' ) && hasBorderSupport( props.name, 'style' ); - const isWidthSupported = - useSetting( 'border.width' ) && hasBorderSupport( props.name, 'width' ); - - const isDisabled = [ - ! isColorSupported, - ! isRadiusSupported, - ! isStyleSupported, - ! isWidthSupported, - ].every( Boolean ); - - if ( isDisabled || ! isSupported ) { - return null; +function styleToAttributes( style ) { + if ( hasSplitBorders( style?.border ) ) { + return { + style, + borderColor: undefined, + }; } - const defaultBorderControls = getBlockSupport( props.name, [ - BORDER_SUPPORT_KEY, - '__experimentalDefaultControls', - ] ); + const borderColorValue = style?.border?.color; + const borderColorSlug = borderColorValue?.startsWith( 'var:preset|color|' ) + ? borderColorSlug.substring( 'var:preset|color|'.length ) + : undefined; + const updatedStyle = { ...style }; + updatedStyle.border = { + ...updatedStyle.border, + color: borderColorSlug ? undefined : borderColorValue, + }; + return { + style: cleanEmptyObject( updatedStyle ), + borderColor: borderColorSlug, + }; +} - const showBorderByDefault = - defaultBorderControls?.color || defaultBorderControls?.width; - - const onBorderChange = ( newBorder ) => { - // Filter out named colors and apply them to appropriate block - // attributes so that CSS classes can be used to apply those colors. - // e.g. has-primary-border-top-color. - - let newBorderStyles = { ...newBorder }; - let newBorderColor; - - if ( hasSplitBorders( newBorder ) ) { - // For each side check if the side has a color value set - // If so, determine if it belongs to a named color, in which case - // we update the color property. - // - // This deliberately overwrites `newBorderStyles` to avoid mutating - // the passed object which causes problems otherwise. - newBorderStyles = { - top: { ...newBorder.top }, - right: { ...newBorder.right }, - bottom: { ...newBorder.bottom }, - left: { ...newBorder.left }, +function attributesToStyle( attributes ) { + if ( hasSplitBorders( attributes.style?.border ) ) { + return attributes.style; + } + return { + ...attributes.style, + border: { + ...attributes.style?.border, + color: attributes.borderColor + ? 'var:preset|color|' + attributes.borderColor + : attributes.style?.border?.color, + }, + }; +} + +function BordersInspectorControl( { children, resetAllFilter } ) { + const attributesResetAllFilter = useCallback( + ( attributes ) => { + const existingStyle = attributesToStyle( attributes ); + const updatedStyle = resetAllFilter( existingStyle ); + return { + ...attributes, + ...styleToAttributes( updatedStyle ), }; + }, + [ resetAllFilter ] + ); - borderSides.forEach( ( side ) => { - if ( newBorder[ side ]?.color ) { - const colorObject = getMultiOriginColor( { - colors, - customColor: newBorder[ side ]?.color, - } ); - - if ( colorObject.slug ) { - newBorderStyles[ - side - ].color = `var:preset|color|${ colorObject.slug }`; - } - } - } ); - } else if ( newBorder?.color ) { - // We have a flat border configuration. Apply named color slug to - // `borderColor` attribute and clear color style property if found. - const customColor = newBorder?.color; - const colorObject = getMultiOriginColor( { colors, customColor } ); - - if ( colorObject.slug ) { - newBorderColor = colorObject.slug; - newBorderStyles.color = undefined; - } - } + return ( + + { children } + + ); +} - // Ensure previous border radius styles are maintained and clean - // overall result for empty objects or properties. - const newStyle = cleanEmptyObject( { - ...style, - border: { radius: style?.border?.radius, ...newBorderStyles }, +export function BorderPanel( props ) { + const { clientId, name, attributes, setAttributes } = props; + const settings = useBlockSettings( name ); + const isEnabled = useHasBorderPanel( settings ); + const value = useMemo( () => { + return attributesToStyle( { + style: attributes.style, + borderColor: attributes.borderColor, } ); + }, [ attributes.style, attributes.borderColor ] ); - setAttributes( { - style: newStyle, - borderColor: newBorderColor, - } ); + const onChange = ( newStyle ) => { + setAttributes( styleToAttributes( newStyle ) ); }; - const hydratedBorder = getBorderObject( attributes, colors ); + if ( ! isEnabled ) { + return null; + } + + const defaultControls = getBlockSupport( props.name, [ + BORDER_SUPPORT_KEY, + '__experimentalDefaultControls', + ] ); return ( - - { ( isWidthSupported || isColorSupported ) && ( - hasBorderValue( props ) } - label={ __( 'Border' ) } - onDeselect={ () => resetBorder( props ) } - isShownByDefault={ showBorderByDefault } - resetAllFilter={ resetBorderFilter } - panelId={ clientId } - > - - - ) } - { isRadiusSupported && ( - hasBorderRadiusValue( props ) } - label={ __( 'Radius' ) } - onDeselect={ () => resetBorderRadius( props ) } - isShownByDefault={ defaultBorderControls?.radius } - resetAllFilter={ ( newAttributes ) => ( { - ...newAttributes, - style: { - ...newAttributes.style, - border: { - ...newAttributes.style?.border, - radius: undefined, - }, - }, - } ) } - panelId={ clientId } - > - - - ) } - + ); } diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 87d666e7cb769..3684598997a6a 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -212,9 +212,27 @@ export function useBlockSettings( name, parentLayout ) { const units = useSetting( 'spacing.units' ); const minHeight = useSetting( 'dimensions.minHeight' ); const layout = useSetting( 'layout' ); + const borderColor = useSetting( 'border.color' ); + const borderRadius = useSetting( 'border.radius' ); + const borderStyle = useSetting( 'border.style' ); + const borderWidth = useSetting( 'border.width' ); + const customColorsEnabled = useSetting( 'color.custom' ); + const customColors = useSetting( 'color.palette.custom' ); + const themeColors = useSetting( 'color.palette.theme' ); + const defaultColors = useSetting( 'color.palette.default' ); + const defaultPalette = useSetting( 'color.defaultPalette' ); const rawSettings = useMemo( () => { return { + color: { + palette: { + custom: customColors, + theme: themeColors, + default: defaultColors, + }, + defaultPalette, + custom: customColorsEnabled, + }, typography: { fontFamilies: { custom: fontFamilies, @@ -239,6 +257,12 @@ export function useBlockSettings( name, parentLayout ) { blockGap, units, }, + border: { + color: borderColor, + radius: borderRadius, + style: borderStyle, + width: borderWidth, + }, dimensions: { minHeight, }, @@ -263,6 +287,15 @@ export function useBlockSettings( name, parentLayout ) { minHeight, layout, parentLayout, + borderColor, + borderRadius, + borderStyle, + borderWidth, + customColorsEnabled, + customColors, + themeColors, + defaultColors, + defaultPalette, ] ); return useSettingsForBlockElement( rawSettings, name );