Skip to content

Commit

Permalink
Suggest Block patterns in block placeholder states (#29602)
Browse files Browse the repository at this point in the history
* temp commit

* renamings and css classname changes

* Style tidy up

- Padding on various pattern selection elements
- Use `cursor: pointer` when hovering patterns
- Ensure carousel/grid view toggle is centrally aligned
- Clean up vertical alignment of toolbar

* remove __experimentalGetScopedBlockPatterns usage

* remove from Buttons

* cleanup BlockPatternSetup part 1

* add to Query and remove from Columns

* remove query styles from previous integration

* add title to grid patterns

* provide rootClientId for pattern fetching

* Polish.

* Add border, fix box sizing.

* adress feedback

* Fix z-index.

* add e2e tests

Co-authored-by: James Koster <[email protected]>
Co-authored-by: jasmussen <[email protected]>
  • Loading branch information
3 people authored Apr 22, 2021
1 parent 0d78904 commit 6244cf5
Show file tree
Hide file tree
Showing 15 changed files with 614 additions and 491 deletions.
5 changes: 5 additions & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ $z-layers: (
// should overlap most block content.
".block-editor-block-list__block.is-{selected,hovered} .block-editor-block-mover": 61,

// Query block setup state.
".block-editor-block-pattern-setup .pattern-slide": 100,
".block-editor-block-pattern-setup .{next,previous}-slide": 101,
".block-editor-block-pattern-setup .active-slide": 102,

// Show sidebar above wp-admin navigation bar for mobile viewports:
// #wpadminbar { z-index: 99999 }
".interface-interface-skeleton__sidebar": 100000,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const VIEWMODES = {
carousel: 'carousel',
grid: 'grid',
};
184 changes: 184 additions & 0 deletions packages/block-editor/src/components/block-pattern-setup/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* WordPress dependencies
*/
import { useDispatch } from '@wordpress/data';
import { cloneBlock } from '@wordpress/blocks';
import {
VisuallyHidden,
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
__unstableCompositeItem as CompositeItem,
} from '@wordpress/components';

import { useState } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import BlockPreview from '../block-preview';
import SetupToolbar from './setup-toolbar';
import usePatternsSetup from './use-patterns-setup';
import { VIEWMODES } from './constants';

const SetupContent = ( {
viewMode,
activeSlide,
patterns,
onBlockPatternSelect,
} ) => {
const composite = useCompositeState();
const containerClass = 'block-editor-block-pattern-setup__container';
if ( viewMode === VIEWMODES.carousel ) {
const slideClass = new Map( [
[ activeSlide, 'active-slide' ],
[ activeSlide - 1, 'previous-slide' ],
[ activeSlide + 1, 'next-slide' ],
] );
return (
<div className={ containerClass }>
<ul className="carousel-container">
{ patterns.map( ( pattern, index ) => (
<BlockPatternSlide
className={ slideClass.get( index ) || '' }
key={ pattern.name }
pattern={ pattern }
/>
) ) }
</ul>
</div>
);
}
return (
<Composite
{ ...composite }
role="listbox"
className={ containerClass }
aria-label={ __( 'Patterns list' ) }
>
{ patterns.map( ( pattern ) => (
<BlockPattern
key={ pattern.name }
pattern={ pattern }
onSelect={ onBlockPatternSelect }
composite={ composite }
/>
) ) }
</Composite>
);
};

function BlockPattern( { pattern, onSelect, composite } ) {
const baseClassName = 'block-editor-block-pattern-setup-list';
const { blocks, title, description, viewportWidth = 700 } = pattern;
const descriptionId = useInstanceId(
BlockPattern,
`${ baseClassName }__item-description`
);
return (
<div
className={ `${ baseClassName }__list-item` }
aria-label={ pattern.title }
aria-describedby={ pattern.description ? descriptionId : undefined }
>
<CompositeItem
role="option"
as="div"
{ ...composite }
className={ `${ baseClassName }__item` }
onClick={ () => onSelect( blocks ) }
>
<BlockPreview
blocks={ blocks }
viewportWidth={ viewportWidth }
/>
<div className={ `${ baseClassName }__item-title` }>
{ title }
</div>
</CompositeItem>
{ !! description && (
<VisuallyHidden id={ descriptionId }>
{ description }
</VisuallyHidden>
) }
</div>
);
}

function BlockPatternSlide( { className, pattern } ) {
const { blocks, title, description } = pattern;
const descriptionId = useInstanceId(
BlockPatternSlide,
'block-editor-block-pattern-setup-list__item-description'
);
return (
<li
className={ `pattern-slide ${ className }` }
aria-label={ title }
aria-describedby={ description ? descriptionId : undefined }
>
<BlockPreview blocks={ blocks } __experimentalLive />
{ !! description && (
<VisuallyHidden id={ descriptionId }>
{ description }
</VisuallyHidden>
) }
</li>
);
}

const BlockPatternSetup = ( {
clientId,
blockName,
filterPatternsFn,
startBlankComponent,
} ) => {
const [ viewMode, setViewMode ] = useState( VIEWMODES.carousel );
const [ activeSlide, setActiveSlide ] = useState( 0 );
const [ showBlank, setShowBlank ] = useState( false );
const { replaceBlock } = useDispatch( blockEditorStore );
const patterns = usePatternsSetup( clientId, blockName, filterPatternsFn );

if ( ! patterns?.length || showBlank ) {
return startBlankComponent;
}

const onBlockPatternSelect = ( blocks ) => {
const clonedBlocks = blocks.map( ( block ) => cloneBlock( block ) );
replaceBlock( clientId, clonedBlocks );
};
return (
<div
className={ `block-editor-block-pattern-setup view-mode-${ viewMode }` }
>
<SetupToolbar
viewMode={ viewMode }
setViewMode={ setViewMode }
activeSlide={ activeSlide }
totalSlides={ patterns.length }
handleNext={ () => {
setActiveSlide( ( active ) => active + 1 );
} }
handlePrevious={ () => {
setActiveSlide( ( active ) => active - 1 );
} }
onBlockPatternSelect={ () => {
onBlockPatternSelect( patterns[ activeSlide ].blocks );
} }
onStartBlank={ () => {
setShowBlank( true );
} }
/>
<SetupContent
viewMode={ viewMode }
activeSlide={ activeSlide }
patterns={ patterns }
onBlockPatternSelect={ onBlockPatternSelect }
/>
</div>
);
};

export default BlockPatternSetup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import {
chevronRight,
chevronLeft,
grid,
stretchFullWidth,
} from '@wordpress/icons';

/**
* Internal dependencies
*/
import { VIEWMODES } from './constants';

const Actions = ( { onStartBlank, onBlockPatternSelect } ) => (
<div className="block-editor-block-pattern-setup__actions">
<Button onClick={ onStartBlank }>{ __( 'Start blank' ) }</Button>
<Button isPrimary onClick={ onBlockPatternSelect }>
{ __( 'Choose' ) }
</Button>
</div>
);

const CarouselNavigation = ( {
handlePrevious,
handleNext,
activeSlide,
totalSlides,
} ) => (
<div className="block-editor-block-pattern-setup__navigation">
<Button
icon={ chevronLeft }
label={ __( 'Previous pattern' ) }
onClick={ handlePrevious }
disabled={ activeSlide === 0 }
/>
<Button
icon={ chevronRight }
label={ __( 'Next pattern' ) }
onClick={ handleNext }
disabled={ activeSlide === totalSlides - 1 }
/>
</div>
);

const SetupToolbar = ( {
viewMode,
setViewMode,
handlePrevious,
handleNext,
activeSlide,
totalSlides,
onBlockPatternSelect,
onStartBlank,
} ) => {
const isCarouselView = viewMode === VIEWMODES.carousel;
const displayControls = (
<div className="block-editor-block-pattern-setup__display-controls">
<Button
icon={ stretchFullWidth }
label={ __( 'Carousel view' ) }
onClick={ () => setViewMode( VIEWMODES.carousel ) }
isPressed={ isCarouselView }
/>
<Button
icon={ grid }
label={ __( 'Grid view' ) }
onClick={ () => setViewMode( VIEWMODES.grid ) }
isPressed={ viewMode === VIEWMODES.grid }
/>
</div>
);
return (
<div className="block-editor-block-pattern-setup__toolbar">
{ isCarouselView && (
<CarouselNavigation
handlePrevious={ handlePrevious }
handleNext={ handleNext }
activeSlide={ activeSlide }
totalSlides={ totalSlides }
/>
) }
{ displayControls }
{ isCarouselView && (
<Actions
onBlockPatternSelect={ onBlockPatternSelect }
onStartBlank={ onStartBlank }
/>
) }
</div>
);
};

export default SetupToolbar;
Loading

0 comments on commit 6244cf5

Please sign in to comment.