Skip to content

Commit

Permalink
Style Book: Move iframe to root of content area to support styles tha…
Browse files Browse the repository at this point in the history
…t overflow block previews (#48664)

* Style Book: Try moving iframe to root of content area

* Try to match styles inside the iframe to the existing text styles from Gutenberg

* Restore margin rules to prevent vertical margins on previews from affecting the height of the preview

* Fix svg filters, attempt to fix tabbing behaviour

* Add Enter key handling, button role, update e2e tests

* Tidy hard-coded styling rules

* Switch back to button element, add additional focus styles, update e2e tests

* Remove unneeded CSS rules

* Update e2e test

* Improve caching of blocks

* Try swapping out button element for Composite and CompositeItem

* Update id key, remove unnecessary memo call
  • Loading branch information
andrewserong committed Mar 8, 2023
1 parent 63479e0 commit 8a81086
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 117 deletions.
263 changes: 209 additions & 54 deletions packages/edit-site/src/components/style-book/index.js
Expand Up @@ -8,6 +8,10 @@ import classnames from 'classnames';
*/
import {
Button,
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
__unstableCompositeItem as CompositeItem,
Disabled,
TabPanel,
createSlotFill,
__experimentalUseSlotFills as useSlotFills,
Expand All @@ -20,9 +24,13 @@ import {
createBlock,
} from '@wordpress/blocks';
import {
BlockPreview,
BlockList,
privateApis as blockEditorPrivateApis,
store as blockEditorStore,
__unstableEditorStyles as EditorStyles,
__unstableIframe as Iframe,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { closeSmall } from '@wordpress/icons';
import {
useResizeObserver,
Expand All @@ -38,12 +46,84 @@ import { ESCAPE } from '@wordpress/keycodes';
*/
import { unlock } from '../../private-apis';

const { useGlobalStyle } = unlock( blockEditorPrivateApis );
const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock(
blockEditorPrivateApis
);

const SLOT_FILL_NAME = 'EditSiteStyleBook';
const { Slot: StyleBookSlot, Fill: StyleBookFill } =
createSlotFill( SLOT_FILL_NAME );

// The content area of the Style Book is rendered within an iframe so that global styles
// are applied to elements within the entire content area. To support elements that are
// not part of the block previews, such as headings and layout for the block previews,
// additional CSS rules need to be passed into the iframe. These are hard-coded below.
// Note that button styles are unset, and then focus rules from the `Button` component are
// applied to the `button` element, targeted via `.edit-site-style-book__example`.
// This is to ensure that browser default styles for buttons are not applied to the previews.
const STYLE_BOOK_IFRAME_STYLES = `
.edit-site-style-book__examples {
max-width: 900px;
margin: 0 auto;
}
.edit-site-style-book__example {
border-radius: 2px;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 40px;
margin-bottom: 40px;
padding: 16px;
width: 100%;
box-sizing: border-box;
}
.edit-site-style-book__example.is-selected {
box-shadow: 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
}
.edit-site-style-book__example:focus:not(:disabled) {
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
outline: 3px solid transparent;
}
.edit-site-style-book__examples.is-wide .edit-site-style-book__example {
flex-direction: row;
}
.edit-site-style-book__example-title {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 11px;
font-weight: 500;
line-height: normal;
margin: 0;
text-align: left;
text-transform: uppercase;
}
.edit-site-style-book__examples.is-wide .edit-site-style-book__example-title {
text-align: right;
width: 120px;
}
.edit-site-style-book__example-preview {
width: 100%;
}
.edit-site-style-book__example-preview .block-editor-block-list__insertion-point,
.edit-site-style-book__example-preview .block-list-appender {
display: none;
}
.edit-site-style-book__example-preview .is-root-container > .wp-block:first-child {
margin-top: 0;
}
.edit-site-style-book__example-preview .is-root-container > .wp-block:last-child {
margin-bottom: 0;
}
`;

function getExamples() {
// Use our own example for the Heading block so that we can show multiple
// heading levels.
Expand Down Expand Up @@ -118,6 +198,15 @@ function StyleBook( { isSelected, onSelect, onClose } ) {
[ examples ]
);

const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);
const settings = useMemo(
() => ( { ...originalSettings, __unstableIsPreviewMode: true } ),
[ originalSettings ]
);

function closeOnEscape( event ) {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
Expand Down Expand Up @@ -156,65 +245,131 @@ function StyleBook( { isSelected, onSelect, onClose } ) {
tabs={ tabs }
>
{ ( tab ) => (
<Examples
examples={ examples }
category={ tab.name }
isSelected={ isSelected }
onSelect={ onSelect }
/>
<Iframe
className="edit-site-style-book__iframe"
head={
<>
<EditorStyles styles={ settings.styles } />
<style>
{
// Forming a "block formatting context" to prevent margin collapsing.
// @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
`.is-root-container { display: flow-root; }
body { position: relative; padding: 32px !important; }` +
STYLE_BOOK_IFRAME_STYLES
}
</style>
</>
}
name="style-book-canvas"
tabIndex={ 0 }
>
{ /* Filters need to be rendered before children to avoid Safari rendering issues. */ }
{ settings.svgFilters }
<Examples
className={ classnames(
'edit-site-style-book__examples',
{
'is-wide': sizes.width > 600,
}
) }
examples={ examples }
category={ tab.name }
label={ sprintf(
// translators: %s: Category of blocks, e.g. Text.
__(
'Examples of blocks in the %s category'
),
tab.title
) }
isSelected={ isSelected }
onSelect={ onSelect }
/>
</Iframe>
) }
</TabPanel>
</section>
</StyleBookFill>
);
}

const Examples = memo( ( { examples, category, isSelected, onSelect } ) => (
<div className="edit-site-style-book__examples">
{ examples
.filter( ( example ) => example.category === category )
.map( ( example ) => (
<Example
key={ example.name }
title={ example.title }
blocks={ example.blocks }
isSelected={ isSelected( example.name ) }
onClick={ () => {
onSelect( example.name );
} }
/>
) ) }
</div>
) );

const Example = memo( ( { title, blocks, isSelected, onClick } ) => (
<button
className={ classnames( 'edit-site-style-book__example', {
'is-selected': isSelected,
} ) }
aria-label={ sprintf(
// translators: %s: Title of a block, e.g. Heading.
__( 'Open %s styles in Styles panel' ),
title
) }
onClick={ onClick }
>
<span className="edit-site-style-book__example-title">{ title }</span>
<div className="edit-site-style-book__example-preview">
<BlockPreview
blocks={ blocks }
viewportWidth={ 0 }
additionalStyles={ [
{
css:
'.wp-block:first-child { margin-top: 0; }' +
'.wp-block:last-child { margin-bottom: 0; }',
},
] }
/>
</div>
</button>
) );
const Examples = memo(
( { className, examples, category, label, isSelected, onSelect } ) => {
const composite = useCompositeState( { orientation: 'vertical' } );
return (
<Composite
{ ...composite }
className={ className }
aria-label={ label }
>
{ examples
.filter( ( example ) => example.category === category )
.map( ( example ) => (
<Example
key={ example.name }
id={ `example-${ example.name }` }
composite={ composite }
title={ example.title }
blocks={ example.blocks }
isSelected={ isSelected( example.name ) }
onClick={ () => {
onSelect( example.name );
} }
/>
) ) }
</Composite>
);
}
);

const Example = ( { composite, id, title, blocks, isSelected, onClick } ) => {
const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);
const settings = useMemo(
() => ( { ...originalSettings, __unstableIsPreviewMode: true } ),
[ originalSettings ]
);

// Cache the list of blocks to avoid additional processing when the component is re-rendered.
const renderedBlocks = useMemo(
() => ( Array.isArray( blocks ) ? blocks : [ blocks ] ),
[ blocks ]
);

return (
<CompositeItem
{ ...composite }
className={ classnames( 'edit-site-style-book__example', {
'is-selected': isSelected,
} ) }
id={ id }
aria-label={ sprintf(
// translators: %s: Title of a block, e.g. Heading.
__( 'Open %s styles in Styles panel' ),
title
) }
onClick={ onClick }
role="button"
as="div"
>
<span className="edit-site-style-book__example-title">
{ title }
</span>
<div className="edit-site-style-book__example-preview" aria-hidden>
<Disabled className="edit-site-style-book__example-preview__content">
<ExperimentalBlockEditorProvider
value={ renderedBlocks }
settings={ settings }
>
<BlockList renderAppender={ false } />
</ExperimentalBlockEditorProvider>
</Disabled>
</div>
</CompositeItem>
);
};

function useHasStyleBook() {
const fills = useSlotFills( SLOT_FILL_NAME );
Expand Down
46 changes: 1 addition & 45 deletions packages/edit-site/src/components/style-book/style.scss
Expand Up @@ -26,53 +26,9 @@
bottom: 0;
left: 0;
overflow: auto;
padding: $grid-unit-40;
padding: 0;
position: absolute;
right: 0;
top: $grid-unit-60; // Height of tabs.
}
}

.edit-site-style-book__examples {
max-width: 900px;
margin: 0 auto;
}

.edit-site-style-book__example {
background: none;
border-radius: $radius-block-ui;
border: none;
color: inherit;
cursor: pointer;
display: flex;
flex-direction: column;
gap: $grid-unit-50;
margin-bottom: $grid-unit-50;
padding: $grid-unit-20;
width: 100%;

&.is-selected {
box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
}

.edit-site-style-book.is-wide & {
flex-direction: row;
}
}

.edit-site-style-book__example-title {
font-size: 11px;
font-weight: 500;
margin: 0;
text-align: left;
text-transform: uppercase;

.edit-site-style-book.is-wide & {
text-align: right;
width: 120px;
}
}

.edit-site-style-book__example-preview {
width: 100%;
}

0 comments on commit 8a81086

Please sign in to comment.