Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Styles: Switch block style selection to select dropdown with color swatches #57331

Closed
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
1 change: 1 addition & 0 deletions lib/block-editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function gutenberg_get_block_editor_settings( $settings ) {

$settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() );

$settings['__experimentalStyles'] = gutenberg_get_global_styles();
$settings['__experimentalFeatures'] = gutenberg_get_global_settings();
// These settings may need to be updated based on data coming from theme.json sources.
if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public function get_item_schema() {
'__experimentalStyles' => array(
'description' => __( 'Styles consolidated from core, theme, and user origins.', 'gutenberg' ),
'type' => 'object',
'context' => array( 'mobile' ),
'context' => array( 'post-editor', 'site-editor', 'widgets-editor', 'mobile' ),
),

'__experimentalEnableQuoteBlockV2' => array(
Expand Down
136 changes: 81 additions & 55 deletions packages/block-editor/src/components/block-styles/index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,59 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { debounce, useViewportMatch } from '@wordpress/compose';
import {
Button,
__experimentalTruncate as Truncate,
ColorIndicator,
Flex,
FlexItem,
Popover,
privateApis as componentsPrivateApis,
__experimentalHStack as HStack,
__experimentalZStack as ZStack,
} from '@wordpress/components';
import { debounce, useViewportMatch } from '@wordpress/compose';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import BlockStylesPreviewPanel from './preview-panel';
import useStylesForBlocks from './use-styles-for-block';
import { getDefaultStyle } from './utils';

const noop = () => {};
const { CustomSelect, CustomSelectItem } = unlock( componentsPrivateApis );

const BlockStyleItem = ( { blockStyle } ) => {
const indicators = [
blockStyle.styles?.color?.background,
blockStyle.styles?.color?.text,
];

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 title={ blockStyle.label }>{ blockStyle.label }</FlexItem>
</HStack>
);
};

// Block Styles component for the Settings Sidebar.
function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {
const {
onSelect,
stylesToRender,
activeStyle,
genericPreviewBlock,
className: previewClassName,
} = useStylesForBlocks( {
clientId,
onSwitch,
} );
} = useStylesForBlocks( { clientId, onSwitch } );

const [ hoveredStyle, setHoveredStyle ] = useState( null );
const isMobileViewport = useViewportMatch( 'medium', '<' );

Expand All @@ -43,58 +63,64 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {

const debouncedSetHoveredStyle = debounce( setHoveredStyle, 250 );

const onSelectStylePreview = ( style ) => {
onSelect( style );
const handleOnChange = ( style ) => {
onSelect( { name: style } );
onHoverClassName( null );
setHoveredStyle( null );
debouncedSetHoveredStyle.cancel();
};

const styleItemHandler = ( item ) => {
if ( hoveredStyle === item ) {
const hoverStyleHandler = ( style ) => {
if ( hoveredStyle === style ) {
debouncedSetHoveredStyle.cancel();
return;
}
debouncedSetHoveredStyle( item );
onHoverClassName( item?.name ?? null );

debouncedSetHoveredStyle( style );
onHoverClassName( style?.name ?? null );
};

const renderSelectedBlockStlye = ( currentStyle ) => {
const currentBlockStyle = stylesToRender.find(
( style ) => style.name === currentStyle
);

if ( ! currentBlockStyle ) {
return null;
}

return <BlockStyleItem blockStyle={ currentBlockStyle } />;
};

const defaultStyle = getDefaultStyle( stylesToRender );

return (
<div className="block-editor-block-styles">
<div className="block-editor-block-styles__variants">
{ stylesToRender.map( ( style ) => {
const buttonText = style.label || style.name;

return (
<Button
__next40pxDefaultSize
className={ classnames(
'block-editor-block-styles__item',
{
'is-active':
activeStyle.name === style.name,
}
) }
key={ style.name }
variant="secondary"
label={ buttonText }
onMouseEnter={ () => styleItemHandler( style ) }
onFocus={ () => styleItemHandler( style ) }
onMouseLeave={ () => styleItemHandler( null ) }
onBlur={ () => styleItemHandler( null ) }
onClick={ () => onSelectStylePreview( style ) }
aria-current={ activeStyle.name === style.name }
>
<Truncate
numberOfLines={ 1 }
className="block-editor-block-styles__item-text"
>
{ buttonText }
</Truncate>
</Button>
);
} ) }
</div>
<CustomSelect
className="block-editor-block-styles__button"
defaultValue={ defaultStyle?.name }
label={ __( 'Select a block style' ) }
onChange={ handleOnChange }
renderSelectedValue={ renderSelectedBlockStlye }
value={ activeStyle?.name }
hideLabelFromVision
popoverProps={ {
wrapperProps: { style: { zIndex: 1000000 } },
} }
>
{ stylesToRender.map( ( blockStyle, index ) => (
<CustomSelectItem
key={ index }
value={ blockStyle.name }
onMouseEnter={ () => hoverStyleHandler( blockStyle ) }
onMouseLeave={ () => hoverStyleHandler( null ) }
onFocus={ () => hoverStyleHandler( blockStyle ) }
onBlur={ () => hoverStyleHandler( null ) }
>
<BlockStyleItem blockStyle={ blockStyle } />
</CustomSelectItem>
) ) }
</CustomSelect>
{ hoveredStyle && ! isMobileViewport && (
<Popover
placement="left-start"
Expand All @@ -103,7 +129,7 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {
>
<div
className="block-editor-block-styles__preview-panel"
onMouseLeave={ () => styleItemHandler( null ) }
onMouseLeave={ () => hoverStyleHandler( null ) }
>
<BlockStylesPreviewPanel
activeStyle={ activeStyle }
Expand Down
52 changes: 7 additions & 45 deletions packages/block-editor/src/components/block-styles/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,17 @@
}
}

.block-editor-block-styles__variants {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: $grid-unit-10;

button.components-button.block-editor-block-styles__item {
color: $gray-900;
box-shadow: inset 0 0 0 $border-width $gray-300;
display: inline-block;
width: calc(50% - #{$grid-unit-05});

&:hover {
color: var(--wp-admin-theme-color);
box-shadow: inset 0 0 0 $border-width $gray-300;
}

&.is-active,
&.is-active:hover {
background-color: $gray-900;
box-shadow: none;
}

&.is-active .block-editor-block-styles__item-text,
&.is-active:hover .block-editor-block-styles__item-text {
color: $white;
}

&:focus,
&.is-active:focus {
@include block-toolbar-button-style__focus();
}
}

.block-editor-block-styles__item-text {
word-break: break-all;
// The Button component is white-space: nowrap, and that won't work with line-clamp.
white-space: normal;

// Without this, the ellipsis can sometimes be partially hidden by the Button padding.
text-align: start;
text-align-last: center;
}
}

// To prevent overflow in the preview container,
// ensure that block contents' margin and padding
// do not add to the block container's width.
.block-editor-block-styles__block-preview-container,
.block-editor-block-styles__block-preview-container * {
box-sizing: border-box !important;
}

.block-editor-block-styles__button {
/* Increased specificity required to overcome default button padding. */
&#{&} {
padding: 6px 12px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,34 @@ function useGenericPreviewBlock( block, type ) {
*/
export default function useStylesForBlocks( { clientId, onSwitch } ) {
const selector = ( select ) => {
const { getBlock } = select( blockEditorStore );
const { getBlock, getSettings } = select( blockEditorStore );
const block = getBlock( clientId );

if ( ! block ) {
return {};
}

const blockType = getBlockType( block.name );
const { getBlockStyles } = select( blocksStore );
const styles = getBlockStyles( block.name );

// Add theme.json styles for each block style if available.
const variations =
getSettings().__experimentalStyles?.blocks?.[ block.name ]
?.variations ?? {};

if ( variations ) {
styles?.forEach( ( style, index ) => {
if ( variations[ style.name ] ) {
styles[ index ].styles = variations[ style.name ];
}
} );
}

return {
block,
blockType,
styles: getBlockStyles( block.name ),
styles,
className: block.attributes.className || '',
};
};
Expand Down
8 changes: 0 additions & 8 deletions packages/block-library/src/group/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ export { metadata, name };
export const settings = {
icon,
example: {
attributes: {
style: {
color: {
text: '#000000',
background: '#ffffff',
},
},
},
innerBlocks: [
{
name: 'core/paragraph',
Expand Down
22 changes: 18 additions & 4 deletions packages/components/src/custom-select-control-v2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
CustomSelectContext as CustomSelectContextType,
} from './types';
import type { WordPressComponentProps } from '../context';
import { VisuallyHidden } from '../visually-hidden';

export const CustomSelectContext =
createContext< CustomSelectContextType >( undefined );
Expand All @@ -45,11 +46,13 @@ function defaultRenderSelectedValue( value: CustomSelectProps[ 'value' ] ) {
export function CustomSelect( {
children,
defaultValue,
hideLabelFromVision = false,
label,
onChange,
size = 'default',
value,
renderSelectedValue = defaultRenderSelectedValue,
popoverProps,
...props
}: WordPressComponentProps< CustomSelectProps, 'button', false > ) {
const store = Ariakit.useSelectStore( {
Expand All @@ -59,12 +62,23 @@ export function CustomSelect( {
} );

const { value: currentValue } = store.useState();
const selectPopoverProps = {
gutter: 12,
sameWidth: true,
...popoverProps,
store,
};

return (
<>
<Styled.CustomSelectLabel store={ store }>
{ label }
</Styled.CustomSelectLabel>
{ hideLabelFromVision && (
<VisuallyHidden as="label">{ label }</VisuallyHidden>
) }
{ ! hideLabelFromVision && (
<Styled.CustomSelectLabel store={ store }>
{ label }
</Styled.CustomSelectLabel>
) }
<Styled.CustomSelectButton
{ ...props }
size={ size }
Expand All @@ -74,7 +88,7 @@ export function CustomSelect( {
{ renderSelectedValue( currentValue ) }
<Ariakit.SelectArrow />
</Styled.CustomSelectButton>
<Styled.CustomSelectPopover gutter={ 12 } store={ store } sameWidth>
<Styled.CustomSelectPopover { ...selectPopoverProps }>
<CustomSelectContext.Provider value={ { store } }>
{ children }
</CustomSelectContext.Provider>
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/custom-select-control-v2/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export const CustomSelectPopover = styled( Ariakit.SelectPopover )`
border-radius: ${ space( 0.5 ) };
background: ${ COLORS.white };
border: 1px solid ${ COLORS.gray[ 900 ] };
/* Force the passed wrapper props z-index otherwise it's overridden. */
z-index: ${ ( props ) => props.wrapperProps?.style?.zIndex };
`;

export const CustomSelectItem = styled( Ariakit.SelectItem )`
Expand Down
Loading
Loading