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: Show style preview when hovered or focused #34522

Merged
merged 20 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
098b54b
Initial commit
ramonjd Sep 1, 2021
6fb45f3
Tweaking text and hover colours
ramonjd Sep 14, 2021
04e7ac1
Adding container ref to try to position the popover
ramonjd Sep 14, 2021
e334afa
Experimenting with removing the default style picker select dropdown …
ramonjd Sep 16, 2021
caf01b9
Abstract renderedStyles, which adds a default to the array for user s…
ramonjd Sep 17, 2021
05952b2
Reduce the switcher to a Make default button where the preferred defa…
ramonjd Sep 17, 2021
9aa6248
Aligning preview with preview block popover styles
ramonjd Sep 20, 2021
a651c01
Fixed typo in expected label in the getRenderedStyles test
ramonjd Sep 20, 2021
35ea380
Cleaning up styles and classnames
ramonjd Sep 20, 2021
3a1cf50
Removing unused preview block code in the block styles menu switcher
ramonjd Sep 21, 2021
e16a4eb
This commit reverts the default style picker, and creates a condition…
ramonjd Sep 29, 2021
ebc57c5
This commit:
ramonjd Oct 1, 2021
51cf2f3
Removing `useCallback` function memo
ramonjd Oct 1, 2021
8373343
Using the Text component to control character overflow.
ramonjd Oct 5, 2021
7e1c8f5
This commit splits BlockStyles component into two components: one for…
ramonjd Oct 12, 2021
aa7bc1d
This commit fixes the position of the block preview container and rem…
ramonjd Oct 26, 2021
e1c8294
This is an attempt to ensure that we should the preview panel outside…
ramonjd Oct 29, 2021
71fcad7
This commit adds a slot in the editor content area for the block styl…
ramonjd Nov 4, 2021
bf66057
Fixing z-index to have the same z-index as the sidebar.
ramonjd Nov 4, 2021
51319b0
Using lodash debounce so we can cancel the debounce when needed.
ramonjd Nov 4, 2021
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
2 changes: 1 addition & 1 deletion docs/reference-guides/block-api/block-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ supports: {
- Type: `boolean`
- Default value: `true`

When the style picker is shown, a dropdown is displayed so the user can select a default style for this block type. If you prefer not to show the dropdown, set this property to `false`.
When the style picker is shown, the user can set a default style for a block type based on the block's currently active style. If you prefer not to make this option available, set this property to `false`.

```js
supports: {
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ _Returns_

- `WPElement`: Element.

### BlockStyles

Undocumented declaration.

### BlockTitle

Renders the block's configured title as a string, or empty if the title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ const BlockInspectorSingleBlock = ( {
{ hasBlockStyles && (
<div>
<PanelBody title={ __( 'Styles' ) }>
<BlockStyles clientId={ clientId } />
<BlockStyles
scope="core/edit-post"
clientId={ clientId }
className="block-inspector__block-styles"
/>
{ hasBlockSupport(
blockName,
'defaultStylePicker',
Expand Down
264 changes: 119 additions & 145 deletions packages/block-editor/src/components/block-styles/index.js
Original file line number Diff line number Diff line change
@@ -1,182 +1,156 @@
/**
* External dependencies
*/
import { find, noop } from 'lodash';
import { noop, debounce } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { useState, useLayoutEffect } from '@wordpress/element';
import { useViewportMatch } from '@wordpress/compose';
import { ENTER, SPACE } from '@wordpress/keycodes';
import { _x } from '@wordpress/i18n';
import {
getBlockType,
cloneBlock,
getBlockFromExample,
store as blocksStore,
} from '@wordpress/blocks';
Button,
__experimentalText as Text,
Slot,
Fill,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import { getActiveStyle, replaceActiveStyle } from './utils';
import BlockPreview from '../block-preview';
import { store as blockEditorStore } from '../../store';
import BlockStylesPreviewPanel from './preview-panel';
import useStylesForBlocks from './use-styles-for-block';

const EMPTY_OBJECT = {};

function useGenericPreviewBlock( block, type ) {
return useMemo( () => {
const example = type?.example;
const blockName = type?.name;

if ( example && blockName ) {
return getBlockFromExample( blockName, {
attributes: example.attributes,
innerBlocks: example.innerBlocks,
} );
}
function BlockStylesPreviewPanelSlot( { scope } ) {
return <Slot name={ `BlockStylesPreviewPanel/${ scope }` } />;
}

if ( block ) {
return cloneBlock( block );
}
}, [ type?.example ? block?.name : block, type ] );
function BlockStylesPreviewPanelFill( { children, scope, ...props } ) {
return (
<Fill name={ `BlockStylesPreviewPanel/${ scope }` }>
<div { ...props }>{ children }</div>
</Fill>
);
}

// Block Styles component for the Settings Sidebar.
function BlockStyles( {
clientId,
onSwitch = noop,
onHoverClassName = noop,
itemRole,
scope,
} ) {
const selector = ( select ) => {
const { getBlock } = select( blockEditorStore );
const block = getBlock( clientId );

if ( ! block ) {
return EMPTY_OBJECT;
}

const blockType = getBlockType( block.name );
const { getBlockStyles } = select( blocksStore );
return {
block,
type: blockType,
styles: getBlockStyles( block.name ),
className: block.attributes.className || '',
};
};

const { styles, block, type, className } = useSelect( selector, [
const {
onSelect,
stylesToRender,
activeStyle,
genericPreviewBlock,
className: previewClassName,
} = useStylesForBlocks( {
clientId,
] );

const { updateBlockAttributes } = useDispatch( blockEditorStore );
const genericPreviewBlock = useGenericPreviewBlock( block, type );

if ( ! styles || styles.length === 0 ) {
onSwitch,
} );
const [ hoveredStyle, setHoveredStyle ] = useState( null );
const [ containerScrollTop, setContainerScrollTop ] = useState( 0 );
const isMobileViewport = useViewportMatch( 'medium', '<' );

useLayoutEffect( () => {
const scrollContainer = document.querySelector(
'.interface-interface-skeleton__content'
);
setContainerScrollTop( scrollContainer.scrollTop + 16 );
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
}, [ hoveredStyle ] );

if ( ! stylesToRender || stylesToRender.length === 0 ) {
return null;
}

const renderedStyles = find( styles, 'isDefault' )
? styles
: [
{
name: 'default',
label: _x( 'Default', 'block style' ),
isDefault: true,
},
...styles,
];
const debouncedSetHoveredStyle = debounce( setHoveredStyle, 250 );

const activeStyle = getActiveStyle( renderedStyles, className );
return (
<div className="block-editor-block-styles">
{ renderedStyles.map( ( style ) => {
const styleClassName = replaceActiveStyle(
className,
activeStyle,
style
);
return (
<BlockStyleItem
genericPreviewBlock={ genericPreviewBlock }
viewportWidth={ type.example?.viewportWidth ?? 500 }
className={ className }
isActive={ activeStyle === style }
key={ style.name }
onSelect={ () => {
updateBlockAttributes( clientId, {
className: styleClassName,
} );
onHoverClassName( null );
onSwitch();
} }
onBlur={ () => onHoverClassName( null ) }
onHover={ () => onHoverClassName( styleClassName ) }
style={ style }
styleClassName={ styleClassName }
itemRole={ itemRole }
/>
);
} ) }
</div>
);
}
const onSelectStylePreview = ( style ) => {
onSelect( style );
onHoverClassName( null );
setHoveredStyle( null );
debouncedSetHoveredStyle.cancel();
};

function BlockStyleItem( {
genericPreviewBlock,
viewportWidth,
style,
isActive,
onBlur,
onHover,
onSelect,
styleClassName,
itemRole,
} ) {
const previewBlocks = useMemo( () => {
return {
...genericPreviewBlock,
attributes: {
...genericPreviewBlock.attributes,
className: styleClassName,
},
};
}, [ genericPreviewBlock, styleClassName ] );
const styleItemHandler = ( item ) => {
if ( hoveredStyle === item ) {
debouncedSetHoveredStyle.cancel();
return;
}
debouncedSetHoveredStyle( item );
onHoverClassName( item?.name ?? null );
};

return (
<div
key={ style.name }
className={ classnames( 'block-editor-block-styles__item', {
'is-active': isActive,
} ) }
onClick={ () => onSelect() }
onKeyDown={ ( event ) => {
if ( ENTER === event.keyCode || SPACE === event.keyCode ) {
event.preventDefault();
onSelect();
}
} }
onMouseEnter={ onHover }
onMouseLeave={ onBlur }
role={ itemRole || 'button' }
tabIndex="0"
aria-label={ style.label || style.name }
>
<div className="block-editor-block-styles__item-preview">
<BlockPreview
viewportWidth={ viewportWidth }
blocks={ previewBlocks }
/>
</div>
<div className="block-editor-block-styles__item-label">
{ style.label || style.name }
<div className="block-editor-block-styles">
<div className="block-editor-block-styles__variants">
{ stylesToRender.map( ( style ) => {
const buttonText = style.label || style.name;

return (
<Button
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 ) }
onKeyDown={ ( event ) => {
if (
ENTER === event.keyCode ||
SPACE === event.keyCode
) {
event.preventDefault();
onSelectStylePreview( style );
}
} }
onClick={ () => onSelectStylePreview( style ) }
role="button"
tabIndex="0"
>
<Text
as="span"
limit={ 12 }
ellipsizeMode="tail"
className="block-editor-block-styles__item-text"
truncate
>
{ buttonText }
</Text>
</Button>
);
} ) }
</div>
{ hoveredStyle && ! isMobileViewport && (
<BlockStylesPreviewPanelFill
scope={ scope }
className="block-editor-block-styles__preview-panel"
style={ { top: containerScrollTop } }
onMouseLeave={ () => styleItemHandler( null ) }
>
<BlockStylesPreviewPanel
activeStyle={ activeStyle }
className={ previewClassName }
genericPreviewBlock={ genericPreviewBlock }
style={ hoveredStyle }
/>
</BlockStylesPreviewPanelFill>
) }
</div>
);
}

BlockStyles.Slot = BlockStylesPreviewPanelSlot;
export default BlockStyles;
49 changes: 49 additions & 0 deletions packages/block-editor/src/components/block-styles/menu-items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { noop } from 'lodash';

/**
* WordPress dependencies
*/
import { MenuItem, __experimentalText as Text } from '@wordpress/components';
import { check } from '@wordpress/icons';

/**
* Internal dependencies
*/
import useStylesForBlocks from './use-styles-for-block';

export default function BlockStylesMenuItems( { clientId, onSwitch = noop } ) {
const { onSelect, stylesToRender, activeStyle } = useStylesForBlocks( {
clientId,
onSwitch,
} );

if ( ! stylesToRender || stylesToRender.length === 0 ) {
return null;
}
return (
<>
{ stylesToRender.map( ( style ) => {
const menuItemText = style.label || style.name;
return (
<MenuItem
key={ style.name }
icon={ activeStyle.name === style.name ? check : null }
onClick={ () => onSelect( style ) }
>
<Text
as="span"
limit={ 18 }
ellipsizeMode="tail"
truncate
>
{ menuItemText }
</Text>
</MenuItem>
);
} ) }
</>
);
}
Loading