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

Site Editor: Add template tab to sidebar #28633

Merged
merged 13 commits into from
Feb 11, 2021
102 changes: 102 additions & 0 deletions packages/e2e-tests/specs/experiments/settings-sidebar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* WordPress dependencies
*/
import {
trashAllPosts,
activateTheme,
getAllBlocks,
selectBlockByClientId,
} from '@wordpress/e2e-test-utils';

/**
* Internal dependencies
*/
import { navigationPanel, siteEditor } from '../../experimental-features';

async function toggleSidebar() {
await page.click(
'.edit-site-header__actions button[aria-label="Settings"]'
);
}

async function getActiveTabLabel() {
return await page.$eval(
'.edit-site-sidebar__panel-tab.is-active',
( element ) => element.getAttribute( 'aria-label' )
);
}

async function getTemplateCard() {
return {
title: await page.$eval(
'.edit-site-template-card__title',
( element ) => element.innerText
),
description: await page.$eval(
'.edit-site-template-card__description',
( element ) => element.innerText
),
};
}

describe( 'Settings sidebar', () => {
beforeAll( async () => {
await activateTheme( 'tt1-blocks' );
await trashAllPosts( 'wp_template' );
await trashAllPosts( 'wp_template_part' );
} );
afterAll( async () => {
await trashAllPosts( 'wp_template' );
await trashAllPosts( 'wp_template_part' );
await activateTheme( 'twentytwentyone' );
} );
beforeEach( async () => {
await siteEditor.visit();
} );

describe( 'Template tab', () => {
it( 'should open template tab by default if no block is selected', async () => {
await toggleSidebar();

expect( await getActiveTabLabel() ).toEqual(
'Template (selected)'
);
} );

it( "should show the currently selected template's title and description", async () => {
await toggleSidebar();

const templateCardBeforeNavigation = await getTemplateCard();
await navigationPanel.open();
await navigationPanel.backToRoot();
await navigationPanel.navigate( 'Templates' );
await navigationPanel.clickItemByText( '404' );
await navigationPanel.close();
const templateCardAfterNavigation = await getTemplateCard();

expect( templateCardBeforeNavigation ).toMatchObject( {
title: 'Index',
description:
'The default template which is used when no other template can be found',
} );
expect( templateCardAfterNavigation ).toMatchObject( {
title: '404',
description: 'Used when the queried content cannot be found',
} );
} );
} );

describe( 'Block tab', () => {
it( 'should open block tab by default if a block is selected', async () => {
const allBlocks = await getAllBlocks();
await selectBlockByClientId( allBlocks[ 0 ].clientId );

await toggleSidebar();
// TODO: Remove when toolbar supports text fields
expect( console ).toHaveWarnedWith(
'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols'
);
expect( await getActiveTabLabel() ).toEqual( 'Block (selected)' );
} );
} );
} );
2 changes: 2 additions & 0 deletions packages/edit-site/src/components/sidebar/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const SIDEBAR_TEMPLATE = 'edit-site/template';
export const SIDEBAR_BLOCK = 'edit-site/block-inspector';
2 changes: 2 additions & 0 deletions packages/edit-site/src/components/sidebar/default-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function DefaultSidebar( {
children,
closeLabel,
header,
headerClassName,
} ) {
return (
<>
Expand All @@ -25,6 +26,7 @@ export default function DefaultSidebar( {
icon={ icon }
closeLabel={ closeLabel }
header={ header }
headerClassName={ headerClassName }
>
{ children }
</ComplementaryArea>
Expand Down
43 changes: 38 additions & 5 deletions packages/edit-site/src/components/sidebar/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
/**
* WordPress dependencies
*/
import { createSlotFill } from '@wordpress/components';
import { createSlotFill, PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { cog, typography } from '@wordpress/icons';
import { useSelect } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import DefaultSidebar from './default-sidebar';
import GlobalStylesSidebar from './global-styles-sidebar';
import { STORE_NAME } from '../../store/constants';
import SettingsHeader from './settings-header';
import TemplateCard from './template-card';
import { SIDEBAR_BLOCK, SIDEBAR_TEMPLATE } from './constants';

const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill(
'EditSiteSidebarInspector'
);
export const SidebarInspectorFill = InspectorFill;

export function SidebarComplementaryAreaFills() {
const { sidebarName } = useSelect( ( select ) => {
let sidebar = select( interfaceStore ).getActiveComplementaryArea(
STORE_NAME
);

if ( ! [ SIDEBAR_BLOCK, SIDEBAR_TEMPLATE ].includes( sidebar ) ) {
sidebar = SIDEBAR_TEMPLATE;
if ( select( blockEditorStore ).getBlockSelectionStart() ) {
sidebar = SIDEBAR_BLOCK;
}
}

return {
sidebarName: sidebar,
};
} );

return (
<>
<DefaultSidebar
identifier="edit-site/block-inspector"
title={ __( 'Block Inspector' ) }
identifier={ sidebarName }
title={ __( 'Settings' ) }
icon={ cog }
closeLabel={ __( 'Close block inspector sidebar' ) }
closeLabel={ __( 'Close settings sidebar' ) }
header={ <SettingsHeader sidebarName={ sidebarName } /> }
headerClassName="edit-site-sidebar__panel-tabs"
>
<InspectorSlot bubblesVirtually />
{ sidebarName === SIDEBAR_TEMPLATE && (
<PanelBody>
<TemplateCard />
</PanelBody>
) }
{ sidebarName === SIDEBAR_BLOCK && (
<InspectorSlot bubblesVirtually />
) }
</DefaultSidebar>
<GlobalStylesSidebar
identifier="edit-site/global-styles"
Expand Down
71 changes: 71 additions & 0 deletions packages/edit-site/src/components/sidebar/settings-header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';

/**
* Internal dependencies
*/
import { STORE_NAME } from '../../../store/constants';
import { SIDEBAR_BLOCK, SIDEBAR_TEMPLATE } from '../constants';

const SettingsHeader = ( { sidebarName } ) => {
const { enableComplementaryArea } = useDispatch( interfaceStore );
const openTemplateSettings = () =>
enableComplementaryArea( STORE_NAME, SIDEBAR_TEMPLATE );
const openBlockSettings = () =>
enableComplementaryArea( STORE_NAME, SIDEBAR_BLOCK );

const [ templateAriaLabel, templateActiveClass ] =
sidebarName === SIDEBAR_TEMPLATE
? // translators: ARIA label for the Template sidebar tab, selected.
[ __( 'Template (selected)' ), 'is-active' ]
: // translators: ARIA label for the Template Settings Sidebar tab, not selected.
[ __( 'Template' ), '' ];

const [ blockAriaLabel, blockActiveClass ] =
sidebarName === SIDEBAR_BLOCK
? // translators: ARIA label for the Block Settings Sidebar tab, selected.
[ __( 'Block (selected)' ), 'is-active' ]
: // translators: ARIA label for the Block Settings Sidebar tab, not selected.
[ __( 'Block' ), '' ];

/* Use a list so screen readers will announce how many tabs there are. */
return (
<ul>
<li>
<Button
onClick={ openTemplateSettings }
className={ `edit-site-sidebar__panel-tab ${ templateActiveClass }` }
aria-label={ templateAriaLabel }
// translators: Data label for the Template Settings Sidebar tab.
data-label={ __( 'Template' ) }
>
{
// translators: Text label for the Template Settings Sidebar tab.
__( 'Template' )
}
</Button>
</li>
<li>
<Button
onClick={ openBlockSettings }
className={ `edit-site-sidebar__panel-tab ${ blockActiveClass }` }
aria-label={ blockAriaLabel }
// translators: Data label for the Block Settings Sidebar tab.
data-label={ __( 'Block' ) }
>
{
// translators: Text label for the Block Settings Sidebar tab.
__( 'Block' )
}
</Button>
</li>
</ul>
);
};

export default SettingsHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
.components-panel__header.edit-site-sidebar__panel-tabs {
justify-content: flex-start;
padding-left: 0;
padding-right: $grid-unit-20;
border-top: 0;
margin-top: 0;

ul {
display: flex;
}
li {
margin: 0;
}

.components-button.has-icon {
display: none;
margin: 0 0 0 auto;
padding: 0;
min-width: $icon-size;
height: $icon-size;

@include break-medium() {
display: flex;
}
}
}

.components-button.edit-site-sidebar__panel-tab {
border-radius: 0;
height: $grid-unit-60;
background: transparent;
border: none;
box-shadow: none;
cursor: pointer;
display: inline-block;
padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode
margin-left: 0;
font-weight: 500;

// This pseudo-element "duplicates" the tab label and sets the text to bold.
// This ensures that the tab doesn't change width when selected.
// See: https://github.com/WordPress/gutenberg/pull/9793
&::after {
content: attr(data-label);
display: block;
font-weight: 600;
height: 0;
overflow: hidden;
speak: none;
visibility: hidden;
}

&.is-active {
// The transparent shadow ensures no jumpiness when focus animates on an active tab.
box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) transparent, inset 0 0 -$border-width-tab 0 0 var(--wp-admin-theme-color);
position: relative;
z-index: z-index(".edit-post-sidebar__panel-tab.is-active");

// This border appears in Windows High Contrast mode instead of the box-shadow.
&::before {
content: "";
position: absolute;
top: 0;
bottom: 1px;
right: 0;
left: 0;
border-bottom: $border-width-tab solid transparent;
}
}

&:focus {
box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
position: relative;
z-index: z-index(".edit-post-sidebar__panel-tab.is-active");
}

&.is-active:focus {
box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color), inset 0 0 -$border-width-tab 0 0 var(--wp-admin-theme-color);
}
}
46 changes: 46 additions & 0 deletions packages/edit-site/src/components/sidebar/template-card/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { Icon } from '@wordpress/components';
import { layout } from '@wordpress/icons';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { store as editSiteStore } from '../../../store';

export default function TemplateCard() {
const { title, description } = useSelect( ( select ) => {
const { getEditedPostType, getEditedPostId } = select( editSiteStore );
const { getEntityRecord } = select( coreStore );
const { __experimentalGetTemplateInfo: getTemplateInfo } = select(
editorStore
);

const postType = getEditedPostType();
const postId = getEditedPostId();
const record = getEntityRecord( 'postType', postType, postId );
const info = record ? getTemplateInfo( record ) : {};

return info;
}, [] );

if ( ! title && ! description ) {
return null;
}

return (
<div className="edit-site-template-card">
<Icon className="edit-site-template-card__icon" icon={ layout } />
<div className="edit-site-template-card__content">
<h2 className="edit-site-template-card__title">{ title }</h2>
<span className="edit-site-template-card__description">
{ description }
</span>
</div>
</div>
);
}
Loading