Skip to content

Commit

Permalink
Explore applying colorways to block instances including UI updates
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronrobertshaw committed Jan 2, 2024
1 parent fa55feb commit bb8003e
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 12 deletions.
4 changes: 4 additions & 0 deletions lib/block-supports/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) {
}
}
$variables_root_selector = WP_Theme_JSON_Gutenberg::scope_selector( $class_name, $variables_root_selector );
// TODO: Work out if there are any downsides to the adding the variables at the block's level not only on children.
// For colorways, we set styles that leverage a color palette applied in the block instance's `settings` attribute.
// Without the CSS custom properties and associated classes those colors won't take effect.
$variables_root_selector = $class_name . ',' . $variables_root_selector;

// Remove any potentially unsafe styles.
$theme_json_shape = WP_Theme_JSON_Gutenberg::remove_insecure_properties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,20 @@ $swatch-gap: 12px;

// Identify the first visible instance as placeholder items will not have this class.
&:nth-child(1 of &) {
// Needed for backward compatibility of color gradient dropdowns before
// the merge with ToolsPanelItems.
margin-top: $grid-unit-30;
border-top-left-radius: $radius-block-ui;
border-top-right-radius: $radius-block-ui;
border-top: 1px solid $gray-300;
}

// No need for the top margin when displaying colorway controls above the
// individual color options.
.block-editor-tools-panel-color-gradient-settings__label + &:nth-child(1 of &) {
margin-top: 0;
}

// Identify the last visible instance as placeholder items will not have this class.
&:nth-last-child(1 of &) {
border-bottom-left-radius: $radius-block-ui;
Expand Down
91 changes: 89 additions & 2 deletions packages/block-editor/src/components/global-styles/color-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,28 @@ import {
__experimentalHStack as HStack,
__experimentalZStack as ZStack,
__experimentalDropdownContentWrapper as DropdownContentWrapper,
BaseControl,
ColorIndicator,
Flex,
FlexItem,
Dropdown,
Button,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { useCallback } from '@wordpress/element';
import { useCallback, useMemo } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import ColorGradientControl from '../colors-gradients/control';
import ColorwayDropdown from './colorway-dropdown.js';
import { useColorsPerOrigin, useGradientsPerOrigin } from './hooks';
import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils';
import {
findActiveColorway,
getValueFromVariable,
TOOLSPANEL_DROPDOWNMENU_PROPS,
} from './utils';
import { setImmutably } from '../../utils/object';
import { unlock } from '../../lock-unlock';

Expand Down Expand Up @@ -305,6 +311,7 @@ export default function ColorPanel( {
settings,
panelId,
defaultControls = DEFAULT_CONTROLS,
colorways,
children,
} ) {
const colors = useColorsPerOrigin( settings );
Expand Down Expand Up @@ -514,6 +521,8 @@ export default function ColorPanel( {
}, {} ),
},
};
// TODO: Existing missing dep. Refactor.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );

const items = [
Expand Down Expand Up @@ -702,13 +711,91 @@ export default function ColorPanel( {
} );
} );

const currentColorway = useMemo(
() => findActiveColorway( colorways, settings, value ),
[ colorways, settings, value ]
);

// TODO: This might need to receive the colorway being reset if we
// need a more sophisticated approach to clearing it's styles and settings.
const resetColorway = () => {
// Passing object with undefined values so that the top-level preset
// attributes also get reset.
const newValue = setImmutably( value, [ 'color' ], {
background: undefined,
gradient: undefined,
text: undefined,
} );

// Reset all the element color styles.
// TODO: Determine if this is the desired behaviour or should it only
// reset elements included in the colorway?
if ( value?.elements ) {
const newElements = {};

Object.keys( value.elements ).forEach( ( elementName ) => {
newElements[ elementName ] = {
...value.elements[ elementName ],
color: undefined,
};
} );

newValue.elements = newElements;
}

// TODO: Find better approach for resetting the settings object as well
// as styles.
// `null` below is to explicitly call for the resetting of the palette settings
// otherwise we'll need to update all onChange handlers so they can pass a second argument.
onChange( newValue, null );
};

const setColorway = ( colorway ) => {
const newValue = setImmutably(
value,
[ 'color' ],
colorway.styles?.color
);

if ( colorway.styles?.elements ) {
const newElements = {};

Object.keys( colorway.styles?.elements ).forEach( ( element ) => {
newElements[ element ] = {
...value.elements?.[ element ],
...colorway.styles.elements[ element ],
};
} );

newValue.elements = newElements;
}

onChange( newValue, colorway.settings?.color?.palette?.theme );
};

return (
<Wrapper
resetAllFilter={ resetAllFilter }
value={ value }
onChange={ onChange }
panelId={ panelId }
>
{ !! colorways?.length && (
<>
<ColorwayDropdown
colorways={ colorways }
label={ __( 'Colorway' ) }
onDeselect={ resetColorway }
onSelect={ setColorway }
popoverProps={ popoverProps }
panelId={ panelId }
value={ currentColorway }
/>
<BaseControl.VisualLabel className="block-editor-tools-panel-color-gradient-settings__label">
{ __( 'Colors' ) }
</BaseControl.VisualLabel>
</>
) }
{ items.map( ( item ) => (
<ColorPanelDropdown
key={ item.key }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import {
BaseControl,
Button,
ColorIndicator,
Dropdown,
Flex,
FlexItem,
Icon,
MenuGroup,
MenuItem,
__experimentalHStack as HStack,
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalVStack as VStack,
__experimentalZStack as ZStack,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { check } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { getValueFromVariable } from './utils';

const checkIcon = <Icon icon={ check } size={ 24 } />;
const noop = () => undefined;

// TODO: Work out what styling of options happens. The element colour sets
// issue shows two options that appear to have background colors but they
// don't match the swatches and the dark one has light text.
function getColorwayPaletteStyles( colorway ) {
if ( ! colorway?.styles?.color ) {
return;
}

const styles = {};
const { background, gradient, text } = colorway.styles.color;

if ( background ) {
styles.backgroundColor = getValueFromVariable(
colorway,
'',
background
);
}

if ( gradient ) {
styles.background = getValueFromVariable( colorway, '', gradient );
}

if ( text ) {
styles.color = getValueFromVariable( colorway, '', text );
}

return styles;
}

// TODO: Confirm which colors need indicators. Not clear from issue.
//
function ColorwayIndicator( { colorway } ) {
const { background, gradient, text } = colorway?.styles?.color || {};
const link = colorway?.styles?.elements?.link?.color?.text;
const heading = colorway?.styles?.elements?.heading?.color?.text;

const indicators = [
getValueFromVariable( colorway, '', background || gradient ),
getValueFromVariable( colorway, '', text ),
getValueFromVariable( colorway, '', link ),
getValueFromVariable( colorway, '', heading ),
];

const label = colorway?.title || __( 'Default' );

return (
<HStack justify="flex-start">
<ZStack isLayered={ false } offset={ -8 }>
{ indicators.map( ( indicator, index ) => (
<Flex key={ index } expanded={ false }>
<ColorIndicator colorValue={ indicator } />
</Flex>
) ) }
</ZStack>
<FlexItem className="" title={ label }>
{ label }
</FlexItem>
</HStack>
);
}

function ColorwayDropdownToggle( { onToggle, isOpen, value } ) {
const toggleProps = {
onClick: onToggle,
className: classnames(
'block-editor-tools-panel-colorway__dropdown-toggle',
{
'is-open': isOpen,
}
),
'aria-expanded': isOpen,
'aria-label': __( 'Colorways options' ),
style: getColorwayPaletteStyles( value ),
};

return (
<Button { ...toggleProps }>
<ColorwayIndicator colorway={ value } />
</Button>
);
}

function ColorwayDropdownContent( { colorways, activeColorway, onSelect } ) {
return (
<MenuGroup
className="block-editor-tools-panel-colorway__dropdown-group"
label={ __( 'Colorways' ) }
>
{ colorways.map( ( colorway, index ) => {
const paletteStyles = getColorwayPaletteStyles( colorway );
const isSelected = activeColorway?.title === colorway.title;

return (
<MenuItem
key={ index }
isSelected={ isSelected }
onClick={ () => onSelect( colorway ) }
role="menuitemradio"
style={ paletteStyles }
suffix={ isSelected ? checkIcon : undefined }
>
<ColorwayIndicator colorway={ colorway } />
</MenuItem>
);
} ) }
</MenuGroup>
);
}

export default function ColorwayDropdown( {
className,
colorways = [],
value,
label,
onDeselect = noop,
onSelect = noop,
panelId,
...props
} ) {
if ( ! colorways.length ) {
return null;
}

const classes = classnames(
className,
'block-editor-tools-panel-colorway__dropdown'
);

return (
<ToolsPanelItem
className="block-editor-tools-panel-colorway"
hasValue={ () => !! value }
label={ label }
onDeselect={ onDeselect }
isShownByDefault={ true }
panelId={ panelId }
>
<VStack spacing={ 0 }>
<BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel>
<Dropdown
{ ...props }
label={ label }
className={ classes }
renderToggle={ ( toggleProps ) => (
<ColorwayDropdownToggle
{ ...toggleProps }
value={ value }
/>
) }
renderContent={ ( contentProps ) => (
<ColorwayDropdownContent
{ ...contentProps }
activeColorway={ value }
colorways={ colorways }
onSelect={ onSelect }
/>
) }
/>
</VStack>
</ToolsPanelItem>
);
}
Loading

0 comments on commit bb8003e

Please sign in to comment.