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:
Maikel David Pérez Gómez 2023-06-27 10:08:08 -04:00 committed by GitHub
parent 7860415be5
commit ecd1795b44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 221 additions and 6 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add the list view component and button to the modal editor

View File

@ -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: () => {},
} ); } );

View File

@ -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"
/>
);
}
);

View File

@ -0,0 +1,2 @@
export * from './document-overview';
export * from './types';

View File

@ -0,0 +1 @@
export type DocumentOverviewProps = { [ key: string ]: unknown };

View File

@ -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>

View File

@ -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,
} } } }
> >

View File

@ -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>
);
}

View File

@ -0,0 +1 @@
export * from './document-overview-sidebar';

View File

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

View File

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

View File

@ -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";