Add iframe editor header toolbar and block inserter (#38549)

* Add initial toolbar and secondary sidebar

* Style the sidebar and inserter panel

* Adjust toolbar and modal paddings

* Move header toolbar styles to separate file

* Make header toolbar class names consistent with component

* Dont use experimental insertion index and fall back to default insertion point

* Add changelog entry
This commit is contained in:
Joshua T Flowers 2023-06-08 12:14:25 -07:00 committed by GitHub
parent 338bf6e45e
commit 4569fda5c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 311 additions and 43 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add iframe editor header toolbar and block inserter

View File

@ -0,0 +1,32 @@
.woocommerce-iframe-editor__header-toolbar {
height: 60px;
border: 0;
border-bottom: 1px solid $gray-400;
display: flex;
align-items: center;
.woocommerce-iframe-editor__header-toolbar-inserter-toggle.components-button.has-icon {
height: 32px;
margin-right: 8px;
min-width: 32px;
padding: 0;
width: 32px;
svg {
transition: transform .2s cubic-bezier(.165,.84,.44,1);
}
&.is-pressed:before {
width: 100%;
left: 0;
}
&.is-pressed svg {
transform: rotate(45deg);
}
}
&-left {
padding-left: $gap-small;
}
}

View File

@ -0,0 +1,94 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
NavigableToolbar,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { plus } from '@wordpress/icons';
import { createElement, useRef, useCallback } from '@wordpress/element';
import { MouseEvent } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ToolbarItem exists in WordPress components.
// eslint-disable-next-line @woocommerce/dependency-group
import { Button, ToolbarItem } from '@wordpress/components';
type HeaderToolbarProps = {
isInserterOpened: boolean;
setIsInserterOpened: ( value: boolean ) => void;
};
export function HeaderToolbar( {
isInserterOpened,
setIsInserterOpened,
}: HeaderToolbarProps ) {
// console.log( editPost );
const inserterButton = useRef< HTMLButtonElement | null >( null );
const { isInserterEnabled } = useSelect( ( select ) => {
const {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore These selectors are available in the block data store.
hasInserterItems,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore These selectors are available in the block data store.
getBlockRootClientId,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore These selectors are available in the block data store.
getBlockSelectionEnd,
} = select( blockEditorStore );
return {
isInserterEnabled: hasInserterItems(
getBlockRootClientId( getBlockSelectionEnd() )
),
};
}, [] );
/* translators: accessibility text for the editor toolbar */
const toolbarAriaLabel = __( 'Document tools', 'woocommerce' );
const toggleInserter = useCallback( () => {
if ( isInserterOpened ) {
// Focusing the inserter button should close the inserter popover.
// However, there are some cases it won't close when the focus is lost.
// See https://github.com/WordPress/gutenberg/issues/43090 for more details.
inserterButton.current?.focus();
setIsInserterOpened( false );
} else {
setIsInserterOpened( true );
}
}, [ isInserterOpened, setIsInserterOpened ] );
return (
<NavigableToolbar
className="woocommerce-iframe-editor__header-toolbar"
aria-label={ toolbarAriaLabel }
>
<div className="woocommerce-iframe-editor__header-toolbar-left">
<ToolbarItem
ref={ inserterButton }
as={ Button }
className="woocommerce-iframe-editor__header-toolbar-inserter-toggle"
variant="primary"
isPressed={ isInserterOpened }
onMouseDown={ (
event: MouseEvent< HTMLButtonElement >
) => {
event.preventDefault();
} }
onClick={ toggleInserter }
disabled={ ! isInserterEnabled }
icon={ plus }
label={
! isInserterOpened
? __( 'Add', 'woocommerce' )
: __( 'Close', 'woocommerce' )
}
showTooltip
/>
</div>
</NavigableToolbar>
);
}

View File

@ -1,12 +1,18 @@
.woocommerce-iframe-editor {
align-items: flex-start;
display: flex;
flex-direction: row;
flex-grow: 1;
height: 100%;
width: 100%;
overflow: hidden;
z-index: 1000;
flex-direction: column;
height: 100%;
&__main {
align-items: flex-start;
display: flex;
flex-direction: row;
flex-grow: 1;
height: 100%;
width: 100%;
overflow: hidden;
z-index: 1000;
}
iframe {
width: 100%;

View File

@ -26,7 +26,9 @@ import {
*/
import { BackButton } from './back-button';
import { EditorCanvas } from './editor-canvas';
import { HeaderToolbar } from './header-toolbar';
import { ResizableEditor } from './resizable-editor';
import { SecondarySidebar } from './secondary-sidebar/secondary-sidebar';
type IframeEditorProps = {
initialBlocks?: BlockInstance[];
@ -45,6 +47,7 @@ export function IframeEditor( {
}: IframeEditorProps ) {
const [ resizeObserver, sizes ] = useResizeObserver();
const [ blocks, setBlocks ] = useState< BlockInstance[] >( initialBlocks );
const [ isInserterOpened, setIsInserterOpened ] = useState( false );
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This action exists in the block editor store.
const { clearSelectedBlock, updateSettings } =
@ -81,45 +84,58 @@ export function IframeEditor( {
onInput={ onInput }
useSubRegistry={ true }
>
<BlockTools
className={ 'woocommerce-iframe-editor__content' }
onClick={ (
event: React.MouseEvent< HTMLDivElement, MouseEvent >
) => {
// Clear selected block when clicking on the gray background.
if ( event.target === event.currentTarget ) {
clearSelectedBlock();
}
} }
>
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ }
<BlockEditorKeyboardShortcuts.Register />
{ onClose && (
<BackButton
onClick={ () => {
setTimeout( onClose, 550 );
} }
/>
) }
<ResizableEditor
enableResizing={ true }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This accepts numbers or strings.
height={ sizes.height ?? '100%' }
<HeaderToolbar
isInserterOpened={ isInserterOpened }
setIsInserterOpened={ setIsInserterOpened }
/>
<div className="woocommerce-iframe-editor__main">
<SecondarySidebar
isInserterOpened={ isInserterOpened }
setIsInserterOpened={ setIsInserterOpened }
/>
<BlockTools
className={ 'woocommerce-iframe-editor__content' }
onClick={ (
event: React.MouseEvent<
HTMLDivElement,
MouseEvent
>
) => {
// Clear selected block when clicking on the gray background.
if ( event.target === event.currentTarget ) {
clearSelectedBlock();
}
} }
>
<EditorCanvas
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ }
<BlockEditorKeyboardShortcuts.Register />
{ onClose && (
<BackButton
onClick={ () => {
setTimeout( onClose, 550 );
} }
/>
) }
<ResizableEditor
enableResizing={ true }
settings={ settings }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This accepts numbers or strings.
height={ sizes.height ?? '100%' }
>
{ resizeObserver }
<BlockList className="edit-site-block-editor__block-list wp-site-blocks" />
</EditorCanvas>
<Popover.Slot />
</ResizableEditor>
</BlockTools>
<div className="woocommerce-iframe-editor__sidebar">
<BlockInspector />
<EditorCanvas
enableResizing={ true }
settings={ settings }
>
{ resizeObserver }
<BlockList className="edit-site-block-editor__block-list wp-site-blocks" />
</EditorCanvas>
<Popover.Slot />
</ResizableEditor>
</BlockTools>
<div className="woocommerce-iframe-editor__sidebar">
<BlockInspector />
</div>
</div>
</BlockEditorProvider>
</div>

View File

@ -0,0 +1,3 @@
.woocommerce-iframe-editor__inserter-panel {
border-right: 1px solid $gray-400;
}

View File

@ -0,0 +1,84 @@
/**
* External dependencies
*/
import { Button, VisuallyHidden } from '@wordpress/components';
import { close } from '@wordpress/icons';
import {
useViewportMatch,
__experimentalUseDialog as useDialog,
} from '@wordpress/compose';
import {
createElement,
useCallback,
useEffect,
useRef,
} from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
store as blockEditorStore,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This is actively used in the GB repo and probably safe to use.
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalLibrary as Library,
} from '@wordpress/block-editor';
type InserterSidebarProps = {
setIsInserterOpened: ( value: boolean ) => void;
};
export default function InserterSidebar( {
setIsInserterOpened,
}: InserterSidebarProps ) {
const isMobileViewport = useViewportMatch( 'medium', '<' );
const { rootClientId } = useSelect( ( select ) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore These selectors are available in the block data store.
const { getBlockRootClientId } = select( blockEditorStore );
return {
rootClientId: getBlockRootClientId(),
};
} );
const closeInserter = useCallback( () => {
return setIsInserterOpened( false );
}, [ setIsInserterOpened ] );
const TagName = ! isMobileViewport ? VisuallyHidden : 'div';
const [ inserterDialogRef, inserterDialogProps ] = useDialog( {
onClose: closeInserter,
focusOnMount: false,
} );
const libraryRef = useRef< Library | null >( null );
useEffect( () => {
libraryRef.current?.focusSearch();
}, [] );
return (
<div
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Types are not provided by useDialog.
ref={ inserterDialogRef }
{ ...inserterDialogProps }
className="woocommerce-iframe-editor__inserter-panel"
>
<TagName className="woocommerce-iframe-editor__inserter-panel-header">
<Button
icon={ close }
onClick={ closeInserter }
label={ __( 'Close block inserter', 'woocommerce' ) }
/>
</TagName>
<div className="woocommerce-iframe-editor__inserter-panel-content">
<Library
showInserterHelpPanel
shouldFocusBlock={ isMobileViewport }
rootClientId={ rootClientId }
ref={ libraryRef }
/>
</div>
</div>
);
}

View File

@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import InserterSidebar from './inserter-sidebar';
type SecondarySidebarProps = {
isInserterOpened: boolean;
setIsInserterOpened: ( value: boolean ) => void;
};
export function SecondarySidebar( {
isInserterOpened,
setIsInserterOpened,
}: SecondarySidebarProps ) {
if ( isInserterOpened ) {
return <InserterSidebar setIsInserterOpened={ setIsInserterOpened } />;
}
return null;
}

View File

@ -1,3 +1,5 @@
@import './iframe-editor.scss';
@import './header-toolbar.scss';
@import './resize-handle.scss';
@import './back-button.scss';
@import './secondary-sidebar/inserter-sidebar.scss';

View File

@ -10,6 +10,8 @@ $modal-editor-height: 60px;
.components-modal__header {
height: $modal-editor-height;
border-bottom: 1px solid $gray-400;
padding-left: 18px;
padding-right: 18px;
.components-modal__header-heading {
font-size: 16px;