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

Style Book: Move iframe to root of content area to support styles that overflow block previews #48664

Merged
Merged
235 changes: 181 additions & 54 deletions packages/edit-site/src/components/style-book/index.js
Expand Up @@ -8,6 +8,7 @@ import classnames from 'classnames';
*/
import {
Button,
Disabled,
TabPanel,
createSlotFill,
__experimentalUseSlotFills as useSlotFills,
Expand All @@ -20,9 +21,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 @@ -31,19 +36,81 @@ import {
useMergeRefs,
} from '@wordpress/compose';
import { useMemo, memo } from '@wordpress/element';
import { ESCAPE } from '@wordpress/keycodes';
import { ENTER, ESCAPE } from '@wordpress/keycodes';

/**
* Internal dependencies
*/
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
// affect and are displayed within the entire content area. To support elements that are
// no 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.
const STYLE_BOOK_IFRAME_STYLES = `
.edit-site-style-book__examples {
max-width: 900px;
margin: 0 auto;
}

.edit-site-style-book__example {
background: none;
border-radius: 2px;
border: none;
color: inherit;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to set background and border to none, or color to inherit here because we've already unset everything above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch, I'll tighten these up 👍

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-admin-theme-color);
}

.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 .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 +185,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 +232,116 @@ 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>
</>
}
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 }
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; }',
},
] }
/>
const Examples = memo(
( { className, examples, category, isSelected, onSelect } ) => (
<div className={ className }>
{ 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 } ) => {
const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);
const settings = useMemo(
() => ( { ...originalSettings, __unstableIsPreviewMode: true } ),
[ originalSettings ]
);

const selectOnEnter = ( event ) => {
if ( event.keyCode === ENTER ) {
event.preventDefault();
onClick();
}
};

/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
return (
<div
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
) }
role="button"
onClick={ onClick }
onKeyDown={ selectOnEnter }
tabIndex={ 0 }
>
<span className="edit-site-style-book__example-title">
{ title }
</span>
<div className="edit-site-style-book__example-preview">
<Disabled className="edit-site-style-book__example-preview__content">
<ExperimentalBlockEditorProvider
value={ Array.isArray( blocks ) ? blocks : [ blocks ] }
settings={ settings }
>
<BlockList renderAppender={ false } />
</ExperimentalBlockEditorProvider>
</Disabled>
</div>
</div>
</button>
) );
);
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
} );

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%;
}