Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions src/blocks/author-profile-social/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,31 @@
"iconSize": {
Comment thread
adekbadek marked this conversation as resolved.
"type": "number",
"default": 24
},
"iconColor": {
"type": "string"
},
"customIconColor": {
"type": "string"
},
"iconColorValue": {
"type": "string"
},
"iconBackgroundColor": {
"type": "string"
},
"customIconBackgroundColor": {
"type": "string"
},
"iconBackgroundColorValue": {
"type": "string"
}
},
"styles": [
{ "name": "default", "label": "Default", "isDefault": true },
{ "name": "brand", "label": "Brand" }
],
"supports": {
"color": {
"enableContrastChecker": false,
"background": true,
"text": true,
"link": false,
"__experimentalSkipSerialization": [ "text", "background" ]
},
"html": false,
"layout": {
"allowSwitching": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,21 @@ private static function render_social_flat( array $attributes, WP_Block $block,

/**
* Get wrapper attributes (class, style, etc.) for the block.
* Sets block context so core includes default class, custom className, and other supports.
* Color serialization is skipped via block.json so colors are applied only as CSS vars.
*
* @param WP_Block $block Block instance.
* @param array $attributes Block attributes.
* @param int $icon_size Icon size in pixels.
* @return string HTML attributes for the wrapper element.
*/
private static function get_block_wrapper_attributes( WP_Block $block, array $attributes, int $icon_size ): string {
$previous = \WP_Block_Supports::$block_to_render ?? null;
// Defensive: each inner WP_Block::render() snapshots/restores
// $block_to_render around its own callback, so by this point it
// should already point at the parent's parsed_block. Re-set
// explicitly to guard against a third-party filter or future Core
// change breaking that chain — otherwise the wrapper would be built
// from the wrong block's supports and lose this block's className,
// spacing, default class, etc.
$previous = \WP_Block_Supports::$block_to_render ?? null;
\WP_Block_Supports::$block_to_render = $block->parsed_block;

$wrapper_attributes = get_block_wrapper_attributes(
Expand All @@ -246,33 +251,30 @@ private static function get_block_wrapper_attributes( WP_Block $block, array $at
}

/**
* Convert a preset token (var:preset|type|slug) to a CSS variable reference.
*
* @param string $value Raw value, e.g. "var:preset|color|primary" or "#fff".
* @return string CSS value, e.g. "var(--wp--preset--color--primary)" or "#fff".
*/
private static function preset_to_css( string $value ): string {
if ( preg_match( '/^var:preset\|([^|]+)\|(.+)$/', $value, $matches ) ) {
return sprintf( 'var(--wp--preset--%s--%s)', $matches[1], $matches[2] );
}
return $value;
}

/**
* Resolve a color value from attributes (preset slug or custom style token).
* Resolve a color value from the block's icon color attributes.
* When both a preset slug and a resolved value are present, emits the
* CSS variable with the value as a native fallback — so theme switches
* that redefine the slug pick up the new palette value, and theme
* switches that drop the slug fall back to the saved hex instead of
* rendering uncoloured.
*
* @param array $attributes Block attributes.
* @param string $preset_key Top-level preset attribute key (e.g. "textColor").
* @param string $style_key Key under style.color (e.g. "text").
* @param string $preset_key Preset slug attribute key (e.g. "iconColor").
* @param string $value_key Resolved CSS value attribute key (e.g. "iconColorValue").
* @return string|null CSS color value or null.
*/
private static function resolve_color( array $attributes, string $preset_key, string $style_key ): ?string {
if ( ! empty( $attributes[ $preset_key ] ) && is_string( $attributes[ $preset_key ] ) ) {
return sprintf( 'var(--wp--preset--color--%s)', $attributes[ $preset_key ] );
private static function resolve_color( array $attributes, string $preset_key, string $value_key ): ?string {
$slug = ! empty( $attributes[ $preset_key ] ) && is_string( $attributes[ $preset_key ] ) ? $attributes[ $preset_key ] : null;
$value = ! empty( $attributes[ $value_key ] ) && is_string( $attributes[ $value_key ] ) ? $attributes[ $value_key ] : null;

if ( $slug && $value ) {
return sprintf( 'var(--wp--preset--color--%s, %s)', $slug, $value );
}
if ( $slug ) {
return sprintf( 'var(--wp--preset--color--%s)', $slug );
}
$custom = $attributes['style']['color'][ $style_key ] ?? null;
if ( ! empty( $custom ) && is_string( $custom ) ) {
return self::preset_to_css( $custom );
if ( $value ) {
return $value;
}
return null;
}
Expand All @@ -281,7 +283,6 @@ private static function resolve_color( array $attributes, string $preset_key, st
* Build wrapper inline style with CSS variables for icon sizing and color.
* Margin is handled natively by get_block_wrapper_attributes().
* Gap is handled by WP layout support (outputs scoped <style> tag per block).
* Color classes/inline styles are skipped via __experimentalSkipSerialization in block.json.
*
* @param array $attributes Block attributes.
* @param int $icon_size Icon size in pixels.
Expand All @@ -292,8 +293,8 @@ private static function get_wrapper_style( array $attributes, int $icon_size ):
$is_brand = ! empty( $attributes['className'] ) && str_contains( $attributes['className'], 'is-style-brand' );

if ( ! $is_brand ) {
$icon_color = self::resolve_color( $attributes, 'textColor', 'text' );
$icon_background = self::resolve_color( $attributes, 'backgroundColor', 'background' );
$icon_color = self::resolve_color( $attributes, 'iconColor', 'iconColorValue' );
$icon_background = self::resolve_color( $attributes, 'iconBackgroundColor', 'iconBackgroundColorValue' );
Comment thread
laurelfulford marked this conversation as resolved.

if ( null !== $icon_color ) {
$parts[] = sprintf( '--icon-color: %s;', $icon_color );
Expand Down
128 changes: 60 additions & 68 deletions src/blocks/author-profile-social/edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
* WordPress dependencies
*/
import { useContext, useEffect, useMemo, useRef, useState } from '@wordpress/element';
import { BlockControls, useBlockProps, useInnerBlocksProps, InspectorControls } from '@wordpress/block-editor';
import {
BlockControls,
useBlockProps,
useInnerBlocksProps,
InspectorControls,
withColors,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
} from '@wordpress/block-editor';
import { PanelBody, SelectControl, Button, ToolbarButton, ToolbarGroup, Tooltip } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
Expand All @@ -28,91 +38,63 @@ const fetchAllServiceKeys = () => {
return allServiceKeysCache;
};

const presetToVar = value => {
if ( typeof value !== 'string' ) {
return value;
}
return value.replace( /^var:preset\|([^|]+)\|(.+)$/, 'var(--wp--preset--$1--$2)' );
};

const resolveColor = ( presetSlug, customValue ) => {
if ( presetSlug ) {
return `var(--wp--preset--color--${ presetSlug })`;
}
if ( typeof customValue === 'string' ) {
return presetToVar( customValue ) || customValue;
}
return undefined;
};

/**
* Edit component for the Author Social Links inner block.
*
* @param {Object} props Block props.
* @param {Object} props.attributes Block attributes.
* @param {Function} props.setAttributes Function to update attributes.
* @param {string} props.clientId Block client ID.
* @param {Object} props Block props.
* @param {Object} props.attributes Block attributes.
* @param {Function} props.setAttributes Function to update attributes.
* @param {string} props.clientId Block client ID.
* @param {Object} props.iconColor Resolved icon color (from withColors).
* @param {Function} props.setIconColor Setter for icon color (from withColors).
* @param {Object} props.iconBackgroundColor Resolved icon background color (from withColors).
* @param {Function} props.setIconBackgroundColor Setter for icon background color (from withColors).
* @return {JSX.Element} The edit component.
*/
export default function Edit( { attributes, setAttributes, clientId } ) {
function Edit( { attributes, setAttributes, clientId, iconColor, setIconColor, iconBackgroundColor, setIconBackgroundColor } ) {
const AuthorContext = getSharedAuthorContext();
const author = useContext( AuthorContext );
const { iconSize, style: styleAttr, textColor, backgroundColor, className } = attributes;
const { iconSize, iconColorValue, iconBackgroundColorValue, className } = attributes;
const hasPopulated = useRef( false );
const [ allServiceKeys, setAllServiceKeys ] = useState( null ); // null = loading

const isBrand = ( className || '' ).split( ' ' ).includes( 'is-style-brand' );
const iconSizeValue = typeof iconSize === 'number' ? iconSize : parseInt( iconSize ?? 24, 10 ) || 24;
const iconColor = ! isBrand ? resolveColor( textColor, styleAttr?.color?.text ) : undefined;
const iconBackground = ! isBrand ? resolveColor( backgroundColor, styleAttr?.color?.background ) : undefined;

// Hide color panel when "Brand" is active; rename labels when "Default".
useEffect( () => {
const sidebar = document.querySelector( '.interface-complementary-area' );
if ( ! sidebar ) {
return;
}

const COLOR_LABEL_MAP = {
Text: __( 'Icon color', 'newspack-plugin' ),
Background: __( 'Icon background', 'newspack-plugin' ),
};

const updateColorPanel = container => {
container.querySelectorAll( '.color-block-support-panel' ).forEach( el => {
el.style.display = isBrand ? 'none' : '';
} );

if ( isBrand ) {
return;
}

container.querySelectorAll( '.block-editor-panel-color-gradient-settings__color-name' ).forEach( el => {
if ( COLOR_LABEL_MAP[ el.textContent ] ) {
el.textContent = COLOR_LABEL_MAP[ el.textContent ];
}
} );
container.querySelectorAll( '.components-menu-item__item' ).forEach( el => {
if ( COLOR_LABEL_MAP[ el.textContent ] ) {
el.textContent = COLOR_LABEL_MAP[ el.textContent ];
}
} );
};

updateColorPanel( sidebar );

const observer = new MutationObserver( () => updateColorPanel( sidebar ) );
observer.observe( sidebar, { childList: true, subtree: true } );

return () => observer.disconnect();
}, [ isBrand ] );
const resolvedIconColor = ! isBrand ? iconColor?.color || iconColorValue : undefined;
const resolvedIconBackground = ! isBrand ? iconBackgroundColor?.color || iconBackgroundColorValue : undefined;
Comment thread
laurelfulford marked this conversation as resolved.

// Color panel is hidden entirely when the "Brand" style is active —
// brand uses each service's own colors, so neither icon nor background
// apply. The settings render as ToolsPanelItems directly inside the
// Styles tab's Color slot, so they integrate with WP's native panel
// (no nested panel-in-a-panel).
const colorGradientSettings = useMultipleOriginColorsAndGradients();
const colorSettings = [
{
colorValue: iconColor?.color || iconColorValue,
onColorChange: value => {
setIconColor( value );
setAttributes( { iconColorValue: value } );
},
label: __( 'Icon color', 'newspack-plugin' ),
},
{
colorValue: iconBackgroundColor?.color || iconBackgroundColorValue,
onColorChange: value => {
setIconBackgroundColor( value );
setAttributes( { iconBackgroundColorValue: value } );
},
label: __( 'Icon background', 'newspack-plugin' ),
},
];
Comment thread
adekbadek marked this conversation as resolved.

const blockProps = useBlockProps( {
className: 'author-profile-social__list',
style: {
'--icon-size': `${ roundIconSize( iconSizeValue ) }px`,
...( iconColor && { '--icon-color': iconColor } ),
...( iconBackground && { '--icon-background': iconBackground } ),
...( resolvedIconColor && { '--icon-color': resolvedIconColor } ),
...( resolvedIconBackground && { '--icon-background': resolvedIconBackground } ),
},
} );

Expand Down Expand Up @@ -201,7 +183,17 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
) }
</PanelBody>
</InspectorControls>
{ ! isBrand && (
<InspectorControls group="color">
<ColorGradientSettingsDropdown settings={ colorSettings } panelId={ clientId } { ...colorGradientSettings } />
</InspectorControls>
) }
<ul { ...innerBlocksProps } />
</>
);
}

export default withColors( {
iconColor: 'icon-color',
iconBackgroundColor: 'icon-background-color',
} )( Edit );
Loading