Add the list view component and button to the modal editor (#38809)
* Add document overview opened state to the EditorContext * Create document overview toolbar button * Add document overview button to the header toolbar * Create document overview sidebar * Register document overview sidebar styles * Set document overview sidebar visible when document overview button is pressed * Add changelog file
This commit is contained in:
parent
7860415be5
commit
ecd1795b44
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add the list view component and button to the modal editor
|
|
@ -6,8 +6,10 @@ import { createContext } from '@wordpress/element';
|
||||||
type EditorContextType = {
|
type EditorContextType = {
|
||||||
hasRedo: boolean;
|
hasRedo: boolean;
|
||||||
hasUndo: boolean;
|
hasUndo: boolean;
|
||||||
|
isDocumentOverviewOpened: boolean;
|
||||||
isInserterOpened: boolean;
|
isInserterOpened: boolean;
|
||||||
redo: () => void;
|
redo: () => void;
|
||||||
|
setIsDocumentOverviewOpened: ( value: boolean ) => void;
|
||||||
setIsInserterOpened: ( value: boolean ) => void;
|
setIsInserterOpened: ( value: boolean ) => void;
|
||||||
undo: () => void;
|
undo: () => void;
|
||||||
};
|
};
|
||||||
|
@ -15,8 +17,10 @@ type EditorContextType = {
|
||||||
export const EditorContext = createContext< EditorContextType >( {
|
export const EditorContext = createContext< EditorContextType >( {
|
||||||
hasRedo: false,
|
hasRedo: false,
|
||||||
hasUndo: false,
|
hasUndo: false,
|
||||||
|
isDocumentOverviewOpened: false,
|
||||||
isInserterOpened: false,
|
isInserterOpened: false,
|
||||||
redo: () => {},
|
redo: () => {},
|
||||||
|
setIsDocumentOverviewOpened: () => {},
|
||||||
setIsInserterOpened: () => {},
|
setIsInserterOpened: () => {},
|
||||||
undo: () => {},
|
undo: () => {},
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Button } from '@wordpress/components';
|
||||||
|
import { createElement, forwardRef, useContext } from '@wordpress/element';
|
||||||
|
import { listView as listViewIcon } from '@wordpress/icons';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Ref } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { DocumentOverviewProps } from './types';
|
||||||
|
import { EditorContext } from '../../context';
|
||||||
|
|
||||||
|
export const DocumentOverview = forwardRef(
|
||||||
|
function ForwardedRefDocumentOverview(
|
||||||
|
props: DocumentOverviewProps,
|
||||||
|
ref: Ref< HTMLButtonElement >
|
||||||
|
) {
|
||||||
|
const { isDocumentOverviewOpened, setIsDocumentOverviewOpened } =
|
||||||
|
useContext( EditorContext );
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
setIsDocumentOverviewOpened( ! isDocumentOverviewOpened );
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{ ...props }
|
||||||
|
ref={ ref }
|
||||||
|
icon={ listViewIcon }
|
||||||
|
isPressed={ isDocumentOverviewOpened }
|
||||||
|
/* translators: button label text should, if possible, be under 16 characters. */
|
||||||
|
label={ __( 'Document overview', 'woocommerce' ) }
|
||||||
|
onClick={ handleClick }
|
||||||
|
className="document-overview"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './document-overview';
|
||||||
|
export * from './types';
|
|
@ -0,0 +1 @@
|
||||||
|
export type DocumentOverviewProps = { [ key: string ]: unknown };
|
|
@ -31,6 +31,7 @@ import { Button, ToolbarItem } from '@wordpress/components';
|
||||||
import { EditorContext } from '../context';
|
import { EditorContext } from '../context';
|
||||||
import EditorHistoryRedo from './editor-history-redo';
|
import EditorHistoryRedo from './editor-history-redo';
|
||||||
import EditorHistoryUndo from './editor-history-undo';
|
import EditorHistoryUndo from './editor-history-undo';
|
||||||
|
import { DocumentOverview } from './document-overview';
|
||||||
|
|
||||||
export function HeaderToolbar() {
|
export function HeaderToolbar() {
|
||||||
const { isInserterOpened, setIsInserterOpened } =
|
const { isInserterOpened, setIsInserterOpened } =
|
||||||
|
@ -114,6 +115,7 @@ export function HeaderToolbar() {
|
||||||
) }
|
) }
|
||||||
<ToolbarItem as={ EditorHistoryUndo } />
|
<ToolbarItem as={ EditorHistoryUndo } />
|
||||||
<ToolbarItem as={ EditorHistoryRedo } />
|
<ToolbarItem as={ EditorHistoryRedo } />
|
||||||
|
<ToolbarItem as={ DocumentOverview } />
|
||||||
</>
|
</>
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -53,6 +53,7 @@ export function IframeEditor( {
|
||||||
setBlocks,
|
setBlocks,
|
||||||
} );
|
} );
|
||||||
const [ isInserterOpened, setIsInserterOpened ] = useState( false );
|
const [ isInserterOpened, setIsInserterOpened ] = useState( false );
|
||||||
|
const [ isListViewOpened, setIsListViewOpened ] = useState( false );
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore This action exists in the block editor store.
|
// @ts-ignore This action exists in the block editor store.
|
||||||
const { clearSelectedBlock, updateSettings } =
|
const { clearSelectedBlock, updateSettings } =
|
||||||
|
@ -80,8 +81,10 @@ export function IframeEditor( {
|
||||||
hasRedo,
|
hasRedo,
|
||||||
hasUndo,
|
hasUndo,
|
||||||
isInserterOpened,
|
isInserterOpened,
|
||||||
|
isDocumentOverviewOpened: isListViewOpened,
|
||||||
redo,
|
redo,
|
||||||
setIsInserterOpened,
|
setIsInserterOpened,
|
||||||
|
setIsDocumentOverviewOpened: setIsListViewOpened,
|
||||||
undo,
|
undo,
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Button, TabPanel } from '@wordpress/components';
|
||||||
|
import {
|
||||||
|
useFocusOnMount,
|
||||||
|
useFocusReturn,
|
||||||
|
useMergeRefs,
|
||||||
|
} from '@wordpress/compose';
|
||||||
|
import {
|
||||||
|
createElement,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
} from '@wordpress/element';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { closeSmall } from '@wordpress/icons';
|
||||||
|
import {
|
||||||
|
// @ts-expect-error Module "@wordpress/block-editor" has no exported member '__experimentalListView'
|
||||||
|
__experimentalListView as ListView,
|
||||||
|
} from '@wordpress/block-editor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { EditorContext } from '../../context';
|
||||||
|
|
||||||
|
export function DocumentOverviewSidebar() {
|
||||||
|
const { setIsDocumentOverviewOpened: setIsListViewOpened } =
|
||||||
|
useContext( EditorContext );
|
||||||
|
|
||||||
|
// This hook handles focus when the sidebar first renders.
|
||||||
|
const focusOnMountRef = useFocusOnMount( 'firstElement' );
|
||||||
|
// The next 2 hooks handle focus for when the sidebar closes and returning focus to the element that had focus before sidebar opened.
|
||||||
|
const headerFocusReturnRef = useFocusReturn();
|
||||||
|
const contentFocusReturnRef = useFocusReturn();
|
||||||
|
|
||||||
|
function closeOnEscape( event: React.KeyboardEvent< HTMLDivElement > ) {
|
||||||
|
if ( event.code === 'Escape' && ! event.defaultPrevented ) {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsListViewOpened( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use internal state instead of a ref to make sure that the component
|
||||||
|
// re-renders when the dropZoneElement updates.
|
||||||
|
const [ dropZoneElement, setDropZoneElement ] = useState( null );
|
||||||
|
// Tracks our current tab.
|
||||||
|
const [ tab, setTab ] = useState( 'list-view' );
|
||||||
|
|
||||||
|
// This ref refers to the list view application area.
|
||||||
|
const listViewRef = useRef< HTMLDivElement >( null );
|
||||||
|
|
||||||
|
// Must merge the refs together so focus can be handled properly in the next function.
|
||||||
|
const listViewContainerRef = useMergeRefs( [
|
||||||
|
contentFocusReturnRef,
|
||||||
|
focusOnMountRef,
|
||||||
|
listViewRef,
|
||||||
|
setDropZoneElement,
|
||||||
|
] );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render tab content for a given tab name.
|
||||||
|
*
|
||||||
|
* @param tabName The name of the tab to render.
|
||||||
|
*/
|
||||||
|
function renderTabContent( tabName: string ) {
|
||||||
|
if ( tabName === 'list-view' ) {
|
||||||
|
return <ListView dropZoneElement={ dropZoneElement } />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
|
<div
|
||||||
|
className="woocommerce-iframe-editor__document-overview-sidebar"
|
||||||
|
onKeyDown={ closeOnEscape }
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="woocommerce-iframe-editor__document-overview-sidebar-close-button"
|
||||||
|
ref={ headerFocusReturnRef }
|
||||||
|
icon={ closeSmall }
|
||||||
|
label={ __( 'Close', 'woocommerce' ) }
|
||||||
|
onClick={ () => setIsListViewOpened( false ) }
|
||||||
|
/>
|
||||||
|
<TabPanel
|
||||||
|
className="woocommerce-iframe-editor__document-overview-sidebar-tab-panel"
|
||||||
|
initialTabName={ tab }
|
||||||
|
onSelect={ setTab }
|
||||||
|
tabs={ [
|
||||||
|
{
|
||||||
|
name: 'list-view',
|
||||||
|
title: 'List View',
|
||||||
|
className:
|
||||||
|
'woocommerce-iframe-editor__document-overview-sidebar-tab-item',
|
||||||
|
},
|
||||||
|
] }
|
||||||
|
>
|
||||||
|
{ ( currentTab ) => (
|
||||||
|
<div
|
||||||
|
className="woocommerce-iframe-editor__document-overview-sidebar-tab-content"
|
||||||
|
ref={ listViewContainerRef }
|
||||||
|
>
|
||||||
|
{ renderTabContent( currentTab.name ) }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</TabPanel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './document-overview-sidebar';
|
|
@ -0,0 +1,39 @@
|
||||||
|
.woocommerce-iframe-editor__document-overview-sidebar {
|
||||||
|
position: relative;
|
||||||
|
width: 350px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid $gray-400;
|
||||||
|
|
||||||
|
&-close-button {
|
||||||
|
position: absolute;
|
||||||
|
right: calc($grid-unit - 2px);
|
||||||
|
top: calc($grid-unit - 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tab-panel {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.components-tab-panel__tabs {
|
||||||
|
padding-right: $grid-unit-60;
|
||||||
|
border-bottom: 1px solid $gray-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-tab-panel__tab-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100% - $grid-unit-60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tab-content {
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: $grid-unit calc($grid-unit - 2px);
|
||||||
|
|
||||||
|
.block-editor-list-view-tree {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,13 +8,19 @@ import { createElement, useContext } from '@wordpress/element';
|
||||||
*/
|
*/
|
||||||
import { EditorContext } from '../context';
|
import { EditorContext } from '../context';
|
||||||
import InserterSidebar from './inserter-sidebar';
|
import InserterSidebar from './inserter-sidebar';
|
||||||
|
import { DocumentOverviewSidebar } from './document-overview-sidebar';
|
||||||
|
|
||||||
export function SecondarySidebar() {
|
export function SecondarySidebar() {
|
||||||
const { isInserterOpened } = useContext( EditorContext );
|
const { isInserterOpened, isDocumentOverviewOpened: isListViewOpened } =
|
||||||
|
useContext( EditorContext );
|
||||||
|
|
||||||
if ( isInserterOpened ) {
|
if ( isInserterOpened ) {
|
||||||
return <InserterSidebar />;
|
return <InserterSidebar />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( isListViewOpened ) {
|
||||||
|
return <DocumentOverviewSidebar />;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
@import './iframe-editor.scss';
|
@import "./iframe-editor.scss";
|
||||||
@import './header-toolbar/header-toolbar.scss';
|
@import "./header-toolbar/header-toolbar.scss";
|
||||||
@import './resize-handle.scss';
|
@import "./resize-handle.scss";
|
||||||
@import './back-button.scss';
|
@import "./back-button.scss";
|
||||||
@import './secondary-sidebar/inserter-sidebar.scss';
|
@import "./secondary-sidebar/inserter-sidebar.scss";
|
||||||
|
@import "./secondary-sidebar/document-overview-sidebar/style.scss";
|
||||||
|
|
Loading…
Reference in New Issue