Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customize widgets inserter #29549

Merged
merged 22 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/customize-widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/data": "file:../data",
"@wordpress/dom": "file:../dom",
"@wordpress/element": "file:../element",
"@wordpress/i18n": "file:../i18n",
"@wordpress/icons": "file:../icons",
"@wordpress/keycodes": "file:../keycodes",
"classnames": "^2.2.6",
"lodash": "^4.17.19"
},
Expand Down
48 changes: 48 additions & 0 deletions packages/customize-widgets/src/components/header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* WordPress dependencies
*/
import { createPortal } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { Button, ToolbarItem } from '@wordpress/components';
import { NavigableToolbar } from '@wordpress/block-editor';
import { plus } from '@wordpress/icons';

/**
* Internal dependencies
*/
import Inserter from '../inserter';

function Header( { inserter, isInserterOpened, setIsInserterOpened } ) {
return (
<>
<div className="customize-widgets-header">
<NavigableToolbar
className="customize-widgets-header-toolbar"
aria-label={ __( 'Document tools' ) }
>
<ToolbarItem
as={ Button }
className="customize-widgets-header-toolbar__inserter-toggle"
isPressed={ isInserterOpened }
isPrimary
icon={ plus }
label={ _x(
'Add block',
'Generic label for block inserter button'
) }
onClick={ () => {
setIsInserterOpened( ( isOpen ) => ! isOpen );
} }
/>
</NavigableToolbar>
</div>

{ createPortal(
<Inserter setIsOpened={ setIsInserterOpened } />,
inserter.contentContainer[ 0 ]
) }
</>
);
}

export default Header;
21 changes: 21 additions & 0 deletions packages/customize-widgets/src/components/header/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.customize-widgets-header-toolbar {
display: flex;
border: none;

.customize-widgets-header-toolbar__inserter-toggle.components-button.has-icon {
border-radius: 2px;
color: $white;
padding: 0;
min-width: $grid-unit-30;
height: $grid-unit-30;
margin-left: auto;

&::before {
content: none;
}

&.is-pressed {
background: $gray-900;
}
}
}
45 changes: 45 additions & 0 deletions packages/customize-widgets/src/components/inserter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __experimentalLibrary as Library } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { closeSmall } from '@wordpress/icons';

function Inserter( { setIsOpened } ) {
const inserterTitleId = useInstanceId(
Inserter,
'customize-widget-layout__inserter-panel-title'
);

return (
<div
className="customize-widgets-layout__inserter-panel"
aria-labelledby={ inserterTitleId }
>
<div className="customize-widgets-layout__inserter-panel-header">
<h2
id={ inserterTitleId }
className="customize-widgets-layout__inserter-panel-header-title"
>
{ __( 'Add a block' ) }
</h2>
<Button
className="customize-widgets-layout__inserter-panel-header-close-button"
icon={ closeSmall }
onClick={ () => setIsOpened( false ) }
aria-label={ __( 'Close inserter' ) }
/>
</div>
<div className="customize-widgets-layout__inserter-panel-content">
<Library
showInserterHelpPanel
onSelect={ () => setIsOpened( false ) }
/>
</div>
</div>
);
}

export default Inserter;
40 changes: 40 additions & 0 deletions packages/customize-widgets/src/components/inserter/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#customize-sidebar-outer-content {
// To override the default style of width: 100%,
// so that the inspector won't be cropped.
width: auto;
min-width: 100%;
}

#customize-outer-theme-controls #sub-accordion-section-widgets-inserter {
padding: 0;

.customize-section-description-container {
display: none;
}
}

.customize-widgets-layout__inserter-panel {
background: $white;
}

.customize-widgets-layout__inserter-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: $grid-unit-20;
// Match the "wp-full-overlay-header" height in core, plus the 1px border.
height: 46px;
box-sizing: border-box;
border-bottom: 1px solid $gray-300;

.customize-widgets-layout__inserter-panel-header-title {
margin: 0;
}
}

.block-editor-inserter__quick-inserter {
.block-editor-inserter__panel-content {
// To fix quick inserter doesn't have background.
background: $white;
}
}
33 changes: 33 additions & 0 deletions packages/customize-widgets/src/components/inserter/use-inserter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* WordPress dependencies
*/
import { useState, useEffect, useCallback } from '@wordpress/element';

export default function useInserter( inserter ) {
const [ isInserterOpened, setIsInserterOpened ] = useState(
() => inserter.isOpen
);

useEffect( () => {
return inserter.subscribe( setIsInserterOpened );
}, [ inserter ] );

return [
isInserterOpened,
useCallback(
kevin940726 marked this conversation as resolved.
Show resolved Hide resolved
( updater ) => {
let isOpen = updater;
if ( typeof updater === 'function' ) {
isOpen = updater( inserter.isOpen );
}

if ( isOpen ) {
inserter.open();
} else {
inserter.close();
}
},
[ inserter ]
),
];
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useReducer, createPortal } from '@wordpress/element';
import { useReducer, createPortal, useMemo } from '@wordpress/element';
import {
BlockEditorProvider,
BlockList,
Expand All @@ -21,7 +21,9 @@ import {
* Internal dependencies
*/
import Inspector, { BlockInspectorButton } from '../inspector';
import Header from '../header';
import useSidebarBlockEditor from './use-sidebar-block-editor';
import useInserter from '../inserter/use-inserter';

const inspectorOpenStateReducer = ( state, action ) => {
switch ( action ) {
Expand All @@ -45,7 +47,7 @@ const inspectorOpenStateReducer = ( state, action ) => {
}
};

export default function SidebarBlockEditor( { sidebar } ) {
export default function SidebarBlockEditor( { sidebar, inserter } ) {
const [ blocks, onInput, onChange ] = useSidebarBlockEditor( sidebar );
const [
{ open: isInspectorOpened, busy: isInspectorAnimating },
Expand All @@ -54,6 +56,13 @@ export default function SidebarBlockEditor( { sidebar } ) {
const parentContainer = document.getElementById(
'customize-theme-controls'
);
const [ isInserterOpened, setIsInserterOpened ] = useInserter( inserter );
const settings = useMemo(
() => ( {
__experimentalSetIsInserterOpened: setIsInserterOpened,
} ),
[]
);

return (
<>
Expand All @@ -65,10 +74,17 @@ export default function SidebarBlockEditor( { sidebar } ) {
value={ blocks }
onInput={ onInput }
onChange={ onChange }
settings={ settings }
useSubRegistry={ false }
>
<BlockEditorKeyboardShortcuts />

<Header
inserter={ inserter }
isInserterOpened={ isInserterOpened }
setIsInserterOpened={ setIsInserterOpened }
/>

<BlockSelectionClearer>
<WritingFlow>
<ObserveTyping>
Expand Down
118 changes: 118 additions & 0 deletions packages/customize-widgets/src/controls/inserter-outer-section.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* WordPress dependencies
*/
import { ESCAPE } from '@wordpress/keycodes';
import { focus } from '@wordpress/dom';

const {
wp: { customize },
} = window;

const OuterSection = customize.OuterSection;
// Override the OuterSection class to handle multiple outer sections.
// It closes all the other outer sections whenever one is opened.
// The result is that at most one outer section can be opened at the same time.
customize.OuterSection = class extends OuterSection {
onChangeExpanded( expanded, args ) {
if ( expanded ) {
customize.section.each( ( section ) => {
if (
section.params.type === 'outer' &&
section.id !== this.id
) {
if ( section.expanded() ) {
section.collapse();
}
}
} );
}

return super.onChangeExpanded( expanded, args );
}
};
// Handle constructor so that "params.type" can be correctly pointed to "outer".
customize.sectionConstructor.outer = customize.OuterSection;

class InserterOuterSection extends customize.OuterSection {
constructor( ...args ) {
super( ...args );

// This is necessary since we're creating a new class which is not identical to the original OuterSection.
// @See https://github.com/WordPress/wordpress-develop/blob/42b05c397c50d9dc244083eff52991413909d4bd/src/js/_enqueues/wp/customize/controls.js#L1427-L1436
this.params.type = 'outer';

this.activeElementBeforeExpanded = null;

const ownerWindow = this.contentContainer[ 0 ].ownerDocument
.defaultView;

// Handle closing the inserter when pressing the Escape key.
ownerWindow.addEventListener(
'keydown',
( event ) => {
if (
this.isOpen &&
( event.keyCode === ESCAPE || event.code === 'Escape' )
) {
event.stopPropagation();

this.close();
}
},
// Use capture mode to make this run before other event listeners.
true
);
}
get isOpen() {
return this.expanded();
}
subscribe( handler ) {
this.expanded.bind( handler );
return () => this.expanded.unbind( handler );
}
open() {
if ( ! this.isOpen ) {
const contentContainer = this.contentContainer[ 0 ];
this.activeElementBeforeExpanded =
contentContainer.ownerDocument.activeElement;

this.expand( {
completeCallback() {
// We have to do this in a "completeCallback" or else the elements will not yet be visible/tabbable.
// The first one should be the close button,
// we want to skip it and choose the second one instead, which is the search box.
const searchBox = focus.tabbable.find(
contentContainer
)[ 1 ];
if ( searchBox ) {
searchBox.focus();
}
},
} );
}
}
close() {
if ( this.isOpen ) {
const contentContainer = this.contentContainer[ 0 ];
const activeElement = contentContainer.ownerDocument.activeElement;

this.collapse( {
completeCallback() {
// Return back the focus when closing the inserter.
// Only do this if the active element which triggers the action is inside the inserter,
// (the close button for instance). In that case the focus will be lost.
// Otherwise, we don't hijack the focus when the user is focusing on other elements
// (like the quick inserter).
if ( contentContainer.contains( activeElement ) ) {
// Return back the focus when closing the inserter.
if ( this.activeElementBeforeExpanded ) {
this.activeElementBeforeExpanded.focus();
}
}
},
} );
}
}
}

export default InserterOuterSection;
Loading