Skip to content

Commit

Permalink
Handle named color selections
Browse files Browse the repository at this point in the history
This covers both flat and split border color selections. It also fixes an oversight where the `useBorderProps` custom hook wasn't updated when multiple origin color palettes were introduced to the controls.
  • Loading branch information
aaronrobertshaw committed Jan 25, 2022
1 parent f993387 commit a14ebcf
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 64 deletions.
20 changes: 12 additions & 8 deletions lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,13 @@ class WP_Theme_JSON_Gutenberg {
'value_key' => 'color',
'css_vars' => '--wp--preset--color--$slug',
'classes' => array(
'.has-$slug-color' => 'color',
'.has-$slug-background-color' => 'background-color',
'.has-$slug-border-color' => 'border-color',
'.has-$slug-color' => 'color',
'.has-$slug-background-color' => 'background-color',
'.has-$slug-border-color' => 'border-color',
'.has-$slug-border-top-color' => 'border-top-color',
'.has-$slug-border-right-color' => 'border-right-color',
'.has-$slug-border-bottom-color' => 'border-bottom-color',
'.has-$slug-border-left-color' => 'border-left-color',
),
'properties' => array( 'color', 'background-color', 'border-color' ),
),
Expand Down Expand Up @@ -1440,7 +1444,7 @@ public function merge( $incoming ) {
* Additionally, for some preset types, we also want to make sure the
* values they introduce don't conflict with default values. We do so
* by checking the incoming slugs for theme presets and compare them
* with the equivalent dfefault presets: if a slug is present as a default
* with the equivalent default presets: if a slug is present as a default
* we remove it from the theme presets.
*/
$nodes = self::get_setting_nodes( $incoming_data );
Expand Down Expand Up @@ -1528,7 +1532,7 @@ public function get_svg_filters( $origins ) {
}

/**
* Returns whether a presets should be overriden or not.
* Returns whether a presets should be overridden or not.
*
* @param array $theme_json The theme.json like structure to inspect.
* @param array $path Path to inspect.
Expand All @@ -1543,8 +1547,8 @@ private static function should_override_preset( $theme_json, $path, $override )
// The relationship between whether to override the defaults
// and whether the defaults are enabled is inverse:
//
// - If defaults are enabled => theme presets should not be overriden
// - If defaults are disabled => theme presets should be overriden
// - If defaults are enabled => theme presets should not be overridden
// - If defaults are disabled => theme presets should be overridden
//
// For example, a theme sets defaultPalette to false,
// making the default palette hidden from the user.
Expand Down Expand Up @@ -1633,7 +1637,7 @@ private function get_name_from_defaults( $slug, $base_path ) {
* Removes the preset values whose slug is equal to any of given slugs.
*
* @param array $node The node with the presets to validate.
* @param array $slugs The slugs that should not be overriden.
* @param array $slugs The slugs that should not be overridden.
*
* @return array The new node
*/
Expand Down
74 changes: 60 additions & 14 deletions packages/block-editor/src/hooks/border-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function BorderColorEdit( props ) {

// Detect changes in the color attributes and update the colorValue to keep the
// UI in sync. This is necessary for situations when border controls interact with
// eachother: eg, setting the border width to zero causes the color and style
// each other: eg, setting the border width to zero causes the color and style
// selections to be cleared.
useEffect( () => {
setColorValue(
Expand Down Expand Up @@ -171,19 +171,33 @@ function addAttributes( settings ) {
return settings;
}

// Allow blocks to specify default value if needed.
if ( settings.attributes.borderColor ) {
// Allow blocks to specify border color values if needed.
const { attributes } = settings;

// Skip any adjustments if block already defines both border color
// attributes to set defaults etc.
if ( attributes.borderColor && attributes.sideBorderColors ) {
return settings;
}

// Add new borderColor attribute to block settings.
// If we are missing border color attribute definition, add it.
if ( ! attributes.borderColor ) {
return {
...settings,
attributes: {
...attributes,
borderColor: { type: 'string' },
},
};
}

// We are missing attribute for side border colors, add it to existing
// attribute definitions.
return {
...settings,
attributes: {
...settings.attributes,
borderColor: {
type: 'string',
},
...attributes,
sideBorderColors: { type: 'object', default: null },
},
};
}
Expand All @@ -205,19 +219,51 @@ function addSaveProps( props, blockType, attributes ) {
return props;
}

const borderClasses = getBorderClasses( attributes );
const newClassName = classnames( props.className, borderClasses );

// If we are clearing the last of the previous classes in `className`
// set it to `undefined` to avoid rendering empty DOM attributes.
props.className = newClassName ? newClassName : undefined;

return props;
}

export function getBorderClasses( attributes ) {
const { borderColor, style } = attributes;
const borderColorClass = getColorClassName( 'border-color', borderColor );

const newClassName = classnames( props.className, {
return classnames( {
'has-border-color': borderColor || style?.border?.color,
[ borderColorClass ]: !! borderColorClass,
...getSideBorderClasses( attributes ),
} );
}

// If we are clearing the last of the previous classes in `className`
// set it to `undefined` to avoid rendering empty DOM attributes.
props.className = newClassName ? newClassName : undefined;

return props;
/**
* Generates a collection of CSS classes for the block's current border color
* selections. The results are intended to be further processed via a call
* through `classnames()`.
*
* @param {Object} attributes Block attributes.
* @return {Object} CSS classes for side border colors.
*/
function getSideBorderClasses( attributes ) {
const { sideBorderColors, style } = attributes;
const sides = [ 'top', 'right', 'bottom', 'left' ];

return sides.reduce( ( classes, side ) => {
const color = sideBorderColors?.[ side ];
const hasColor = color || style?.border?.[ side ]?.color;
const baseClassName = `border-${ side }-color`;
const colorClass = getColorClassName( baseClassName, color );

return {
...classes,
[ `has-${ baseClassName }` ]: hasColor,
[ colorClass ]: !! colorClass,
};
}, {} );
}

/**
Expand Down
147 changes: 137 additions & 10 deletions packages/block-editor/src/hooks/border.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalBorderBoxControl as BorderBoxControl,
isDefinedBorder,
hasSplitBorders,
} from '@wordpress/components';
import { Platform } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
Expand All @@ -26,14 +27,17 @@ import { cleanEmptyObject } from './utils';
export const BORDER_SUPPORT_KEY = '__experimentalBorder';

const hasBorderValue = ( props ) => {
return isDefinedBorder( props.attributes.style?.border );
const { borderColor, sideBorderColors, style } = props.attributes;
return isDefinedBorder( style?.border ) || borderColor || sideBorderColors;
};

// The border color, style, and width are omitted so the get undefined. The
// 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,
sideBorderColors: undefined,
style: {
...style,
border: cleanEmptyObject( {
Expand All @@ -43,9 +47,10 @@ const resetBorder = ( { attributes = {}, setAttributes } ) => {
} );
};

// TODO: Revisit this and check if it actually works as expected.
const resetBorderFilter = ( newAttributes ) => ( {
...newAttributes,
borderColor: undefined,
sideBorderColors: undefined,
style: {
...newAttributes.style,
border: {
Expand All @@ -54,12 +59,90 @@ const resetBorderFilter = ( newAttributes ) => ( {
},
} );

export function BorderPanel( props ) {
const getColorByProperty = ( colors, property, value ) => {
let matchedColor;

colors.some( ( origin ) =>
origin.colors.some( ( color ) => {
if ( color[ property ] === value ) {
matchedColor = color;
return true;
}

return false;
} )
);

return matchedColor;
};

export const getMultiOriginColor = ( { colors, namedColor, customColor } ) => {
// Search each origin (default, theme, or user) for matching color by name.
if ( namedColor ) {
const colorObject = getColorByProperty( colors, 'slug', namedColor );
if ( colorObject ) {
return colorObject;
}
}

// Skip if no custom color or matching named color.
if ( ! customColor ) {
return { color: undefined };
}

// Attempt to find color via custom color value or build new object.
const colorObject = getColorByProperty( colors, 'color', customColor );
return colorObject ? colorObject : { color: customColor };
};

const getBorderObject = ( attributes, colors ) => {
const {
attributes: { style },
clientId,
setAttributes,
} = props;
borderColor,
sideBorderColors,
style: { border: borderStyles },
} = attributes;

// 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;
}

// 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.
if ( sideBorderColors ) {
const hydratedBorderStyles = { ...borderStyles };

Object.entries( sideBorderColors ).forEach(
( [ side, namedColor ] ) => {
const { color } = getMultiOriginColor( { colors, namedColor } );

if ( color ) {
hydratedBorderStyles[ side ] = {
...hydratedBorderStyles[ side ],
color,
};
}
}
);

return hydratedBorderStyles;
}

// No named colors selected all color values if any should already be in
// the style's border object.
return borderStyles;
};

export function BorderPanel( props ) {
const { attributes, clientId, setAttributes } = props;
const { style } = attributes;
const isDisabled = useIsBorderDisabled( props );
const isSupported = hasBorderSupport( props.name );
const { colors } = useMultipleOriginColorsAndGradients();
Expand Down Expand Up @@ -94,11 +177,55 @@ export function BorderPanel( props ) {
const borderStyles = !! style?.border?.radius
? { radius: style?.border?.radius, ...newBorder }
: newBorder;

let newBorderColor;
let newSideBorderColors;

if ( hasSplitBorders( borderStyles ) ) {
newSideBorderColors = {};

[ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
if ( borderStyles[ side ]?.color ) {
const colorObject = getMultiOriginColor( {
colors,
customColor: borderStyles[ side ]?.color,
} );

if ( colorObject.slug ) {
// If we have a named color, set the sides named color
// attribute and clear the saved style objects color value.
newSideBorderColors[ side ] = colorObject.slug;
borderStyles[ side ].color = undefined;
} else {
// If we no longer have a named color, clear the
// side's named color attribute.
newSideBorderColors[ side ] = undefined;
}
}
} );
} else if ( borderStyles?.color ) {
// We have a flat border configuration. Apply named color slug to
// `borderColor` attribute and clear color style property if found.
const customColor = borderStyles?.color;
const colorObject = getMultiOriginColor( { colors, customColor } );

if ( colorObject.slug ) {
newBorderColor = colorObject.slug;
borderStyles.color = undefined;
}
}

const newStyle = cleanEmptyObject( { ...style, border: borderStyles } );

setAttributes( { style: newStyle } );
setAttributes( {
style: newStyle,
borderColor: newBorderColor,
sideBorderColors: newSideBorderColors,
} );
};

const hydratedBorder = getBorderObject( attributes, colors );

return (
<InspectorControls __experimentalGroup="border">
{ ( isWidthSupported || isColorSupported ) && (
Expand All @@ -116,7 +243,7 @@ export function BorderPanel( props ) {
showColor={ isColorSupported }
showStyle={ isStyleSupported }
showWidth={ isWidthSupported }
value={ style?.border }
value={ hydratedBorder }
__experimentalHasMultipleOrigins={ true }
__experimentalIsRenderedInSidebar={ true }
/>
Expand Down
Loading

0 comments on commit a14ebcf

Please sign in to comment.