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:
parent
338bf6e45e
commit
4569fda5c1
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add iframe editor header toolbar and block inserter
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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%;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.woocommerce-iframe-editor__inserter-panel {
|
||||
border-right: 1px solid $gray-400;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue