-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Block Styles: Show style preview when hovered or focused #34522
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
098b54b
Initial commit
ramonjd 6fb45f3
Tweaking text and hover colours
ramonjd 04e7ac1
Adding container ref to try to position the popover
ramonjd e334afa
Experimenting with removing the default style picker select dropdown …
ramonjd caf01b9
Abstract renderedStyles, which adds a default to the array for user s…
ramonjd 05952b2
Reduce the switcher to a Make default button where the preferred defa…
ramonjd 9aa6248
Aligning preview with preview block popover styles
ramonjd a651c01
Fixed typo in expected label in the getRenderedStyles test
ramonjd 35ea380
Cleaning up styles and classnames
ramonjd 3a1cf50
Removing unused preview block code in the block styles menu switcher
ramonjd e16a4eb
This commit reverts the default style picker, and creates a condition…
ramonjd ebc57c5
This commit:
ramonjd 51cf2f3
Removing `useCallback` function memo
ramonjd 8373343
Using the Text component to control character overflow.
ramonjd 7e1c8f5
This commit splits BlockStyles component into two components: one for…
ramonjd aa7bc1d
This commit fixes the position of the block preview container and rem…
ramonjd e1c8294
This is an attempt to ensure that we should the preview panel outside…
ramonjd 71fcad7
This commit adds a slot in the editor content area for the block styl…
ramonjd bf66057
Fixing z-index to have the same z-index as the sidebar.
ramonjd 51319b0
Using lodash debounce so we can cancel the debounce when needed.
ramonjd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
264 changes: 119 additions & 145 deletions
264
packages/block-editor/src/components/block-styles/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ); | ||
| }, [ 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
49
packages/block-editor/src/components/block-styles/menu-items.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ); | ||
| } ) } | ||
| </> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.