From 4569fda5c189fa2cb7463a0143ede5ecd6eed9ae Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 8 Jun 2023 12:14:25 -0700 Subject: [PATCH] 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 --- .../js/product-editor/changelog/add-38486 | 4 + .../iframe-editor/header-toolbar.scss | 32 +++++++ .../iframe-editor/header-toolbar.tsx | 94 +++++++++++++++++++ .../iframe-editor/iframe-editor.scss | 20 ++-- .../iframe-editor/iframe-editor.tsx | 88 ++++++++++------- .../secondary-sidebar/inserter-sidebar.scss | 3 + .../secondary-sidebar/inserter-sidebar.tsx | 84 +++++++++++++++++ .../secondary-sidebar/secondary-sidebar.tsx | 25 +++++ .../src/components/iframe-editor/style.scss | 2 + .../src/components/modal-editor/style.scss | 2 + 10 files changed, 311 insertions(+), 43 deletions(-) create mode 100644 packages/js/product-editor/changelog/add-38486 create mode 100644 packages/js/product-editor/src/components/iframe-editor/header-toolbar.scss create mode 100644 packages/js/product-editor/src/components/iframe-editor/header-toolbar.tsx create mode 100644 packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.scss create mode 100644 packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.tsx create mode 100644 packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/secondary-sidebar.tsx diff --git a/packages/js/product-editor/changelog/add-38486 b/packages/js/product-editor/changelog/add-38486 new file mode 100644 index 0000000000000..e84cc8e5dd64e --- /dev/null +++ b/packages/js/product-editor/changelog/add-38486 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add iframe editor header toolbar and block inserter diff --git a/packages/js/product-editor/src/components/iframe-editor/header-toolbar.scss b/packages/js/product-editor/src/components/iframe-editor/header-toolbar.scss new file mode 100644 index 0000000000000..c9bf6ab7ed981 --- /dev/null +++ b/packages/js/product-editor/src/components/iframe-editor/header-toolbar.scss @@ -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; + } +} diff --git a/packages/js/product-editor/src/components/iframe-editor/header-toolbar.tsx b/packages/js/product-editor/src/components/iframe-editor/header-toolbar.tsx new file mode 100644 index 0000000000000..995b7af01ba9f --- /dev/null +++ b/packages/js/product-editor/src/components/iframe-editor/header-toolbar.tsx @@ -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 ( + +
+ + ) => { + event.preventDefault(); + } } + onClick={ toggleInserter } + disabled={ ! isInserterEnabled } + icon={ plus } + label={ + ! isInserterOpened + ? __( 'Add', 'woocommerce' ) + : __( 'Close', 'woocommerce' ) + } + showTooltip + /> +
+
+ ); +} diff --git a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.scss b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.scss index 85e0b35f09b35..42506fc79f65c 100644 --- a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.scss +++ b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.scss @@ -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%; diff --git a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx index bba2cba3aacab..3139e637db7e9 100644 --- a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx +++ b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx @@ -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 } > - - ) => { - // 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 */ } - - { onClose && ( - { - setTimeout( onClose, 550 ); - } } - /> - ) } - +
+ + + ) => { + // Clear selected block when clicking on the gray background. + if ( event.target === event.currentTarget ) { + clearSelectedBlock(); + } + } } > - + { onClose && ( + { + setTimeout( onClose, 550 ); + } } + /> + ) } + - { resizeObserver } - - - - - -
- + + { resizeObserver } + + + + + +
+ +
diff --git a/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.scss b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.scss new file mode 100644 index 0000000000000..bac37a66105ff --- /dev/null +++ b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.scss @@ -0,0 +1,3 @@ +.woocommerce-iframe-editor__inserter-panel { + border-right: 1px solid $gray-400; +} diff --git a/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.tsx b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.tsx new file mode 100644 index 0000000000000..7e5da84d436d2 --- /dev/null +++ b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/inserter-sidebar.tsx @@ -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 ( +
+ +
+ ); +} diff --git a/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/secondary-sidebar.tsx b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/secondary-sidebar.tsx new file mode 100644 index 0000000000000..fdfd8bd32003d --- /dev/null +++ b/packages/js/product-editor/src/components/iframe-editor/secondary-sidebar/secondary-sidebar.tsx @@ -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 ; + } + + return null; +} diff --git a/packages/js/product-editor/src/components/iframe-editor/style.scss b/packages/js/product-editor/src/components/iframe-editor/style.scss index 7cc3df367f15a..9764af49899b0 100644 --- a/packages/js/product-editor/src/components/iframe-editor/style.scss +++ b/packages/js/product-editor/src/components/iframe-editor/style.scss @@ -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'; diff --git a/packages/js/product-editor/src/components/modal-editor/style.scss b/packages/js/product-editor/src/components/modal-editor/style.scss index 0e35c3db8918e..1c895eef518a0 100644 --- a/packages/js/product-editor/src/components/modal-editor/style.scss +++ b/packages/js/product-editor/src/components/modal-editor/style.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;