Allow each page to specify their breadcrumbs without having to render the Header component (https://github.com/woocommerce/woocommerce-admin/pull/2491)

* Simplify Header rendering, remove the use of react-slot-fill

* Remove the useless "/analytics" route

* Move all the <Header> renders to the new, declarative way of specifying breadcrumbs

* Re-render the Layout when a report is added using the REPORTS_FILTER, since that affects the breadcrumbs output

* Fix the base breadcrumb link and breadcrumbs on embedded pages

* Expanded Layout.propTypes to specify the breadcrumbs' shape
This commit is contained in:
Daniel Rey López 2019-07-05 09:15:49 +01:00 committed by GitHub
parent d640b15d09
commit 08417da553
13 changed files with 57 additions and 120 deletions

View File

@ -1,33 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
/**
* Internal dependencies
*/
import Header from 'header';
export default class extends Component {
constructor( props ) {
super( props );
this.state = {
selected: [],
};
this.onChange = this.onChange.bind( this );
}
onChange( selected ) {
this.setState( { selected } );
}
render() {
return (
<Fragment>
<Header sections={ [ __( 'Analytics', 'woocommerce-admin' ) ] } />
</Fragment>
);
}
}

View File

@ -4,7 +4,7 @@
*/
import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { Component, Fragment } from '@wordpress/element';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { find } from 'lodash';
@ -19,7 +19,6 @@ import { getQuery, getSearchWords } from '@woocommerce/navigation';
* Internal dependencies
*/
import './style.scss';
import Header from 'header';
import OrdersReport from './orders';
import ProductsReport from './products';
import RevenueReport from './revenue';
@ -33,9 +32,9 @@ import ReportError from 'analytics/components/report-error';
import { searchItemsByString } from 'wc-api/items/utils';
import withSelect from 'wc-api/with-select';
const REPORTS_FILTER = 'woocommerce_admin_reports_list';
export const REPORTS_FILTER = 'woocommerce_admin_reports_list';
const getReports = () => {
export const getReports = () => {
const reports = [
{
report: 'revenue',
@ -128,17 +127,7 @@ class Report extends Component {
return null;
}
const Container = report.component;
return (
<Fragment>
<Header
sections={ [
[ '/analytics/revenue', __( 'Analytics', 'woocommerce-admin' ) ],
report.title,
] }
/>
<Container { ...this.props } />
</Fragment>
);
return <Container { ...this.props } />;
}
}

View File

@ -19,7 +19,6 @@ import { SectionHeader, useFilters } from '@woocommerce/components';
*/
import './index.scss';
import { analyticsSettings } from './config';
import Header from 'header';
import Setting from './setting';
import HistoricalData from './historical-data';
import withSelect from 'wc-api/with-select';
@ -160,12 +159,6 @@ class Settings extends Component {
return (
<Fragment>
<Header
sections={ [
[ '/analytics/revenue', __( 'Analytics', 'woocommerce-admin' ) ],
__( 'Settings', 'woocommerce-admin' ),
] }
/>
<SectionHeader title={ __( 'Analytics Settings', 'woocommerce-admin' ) } />
<div className="woocommerce-settings__wrapper">
{ analyticsSettings.map( setting => (

View File

@ -2,7 +2,6 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
@ -12,7 +11,6 @@ import { compose } from '@wordpress/compose';
import './style.scss';
import CustomizableDashboard from './customizable';
import DashboardCharts from './dashboard-charts';
import Header from 'header';
import Leaderboards from './leaderboards';
import { ReportFilters } from '@woocommerce/components';
import StorePerformance from './store-performance';
@ -21,7 +19,7 @@ import ProfileWizard from './profile-wizard';
import withSelect from 'wc-api/with-select';
class Dashboard extends Component {
renderDashboardOutput() {
render() {
const { path, profileItems, query } = this.props;
if ( window.wcAdminFeatures.onboarding && ! profileItems.skipped && ! profileItems.completed ) {
@ -49,15 +47,6 @@ class Dashboard extends Component {
</Fragment>
);
}
render() {
return (
<Fragment>
<Header sections={ [ __( 'Dashboard', 'woocommerce-admin' ) ] } />
{ this.renderDashboardOutput() }
</Fragment>
);
}
}
export default compose(

View File

@ -13,7 +13,6 @@ import ComponentExample from './example';
import ComponentDocs from './docs';
import { Card, Link } from '@woocommerce/components';
import examples from './examples.json';
import Header from 'header';
import './style.scss';
const camelCaseToSlug = name => {
@ -41,16 +40,13 @@ export default class extends Component {
} );
let exampleList = examples;
let breadcrumbs = [ 'Documentation' ];
if ( component ) {
const example = find( examples, ex => component === camelCaseToSlug( ex.component ) );
breadcrumbs = [ [ '/devdocs', 'Documentation' ], example.component ];
exampleList = [ example ];
}
return (
<div className={ className }>
<Header sections={ breadcrumbs } />
{ exampleList.map( example => {
const { componentName, filePath, render, docPath } = getExampleData( example );
const cardClasses = classnames(

View File

@ -3,7 +3,6 @@
* External dependencies
*/
import { render } from '@wordpress/element';
import { Provider as SlotFillProvider } from 'react-slot-fill';
/**
* Internal dependencies
@ -16,12 +15,7 @@ import 'wc-api/wp-data-store';
const embeddedRoot = document.getElementById( 'woocommerce-embedded-root' );
// Render the header.
render(
<SlotFillProvider>
<EmbedLayout />
</SlotFillProvider>,
embeddedRoot
);
render( <EmbedLayout />, embeddedRoot );
embeddedRoot.classList.remove( 'is-embed-loading' );

View File

@ -1,7 +1,7 @@
Header
======
A basic component for the app header. The header outputs breadcrumbs via the `sections` prop (required) and access to the activity panel. It also sets the document title. The Header component used in each section automatically fills into the "header" slot defined in `<Layout />`. We're using [react-slot-fill](https://github.com/camwest/react-slot-fill) to avoid a duplicated `div` wrapper from Gutenberg's implementation.
A basic component for the app header. The header outputs breadcrumbs via the `sections` prop (required) and access to the activity panel. It also sets the document title.
## How to use:

View File

@ -6,7 +6,6 @@ import { __, sprintf } from '@wordpress/i18n';
import { Component, findDOMNode } from '@wordpress/element';
import classnames from 'classnames';
import { decodeEntities } from '@wordpress/html-entities';
import { Fill } from 'react-slot-fill';
import PropTypes from 'prop-types';
/**
@ -84,12 +83,14 @@ class Header extends Component {
<div className={ className }>
<h1 className="woocommerce-layout__header-breadcrumbs">
<span>
<Link href="/">WooCommerce</Link>
<Link href={ 'admin.php?page=wc-admin' } type={ isEmbedded ? 'wp-admin' : 'wc-admin' }>
WooCommerce
</Link>
</span>
{ _sections.map( ( section, i ) => {
const sectionPiece = Array.isArray( section ) ? (
<Link
href={ getNewPath( {}, section[ 0 ], {} ) }
href={ isEmbedded ? section[ 0 ] : getNewPath( {}, section[ 0 ], {} ) }
type={ isEmbedded ? 'wp-admin' : 'wc-admin' }
>
{ section[ 1 ] }
@ -115,10 +116,4 @@ Header.defaultProps = {
isEmbedded: false,
};
export default function( props ) {
return (
<Fill name="header">
<Header { ...props } />
</Fill>
);
}
export default Header;

View File

@ -3,7 +3,6 @@
* External dependencies
*/
import { render } from '@wordpress/element';
import { Provider as SlotFillProvider } from 'react-slot-fill';
/**
* Internal dependencies
@ -13,9 +12,4 @@ import { PageLayout } from './layout';
import 'store';
import 'wc-api/wp-data-store';
render(
<SlotFillProvider>
<PageLayout />
</SlotFillProvider>,
document.getElementById( 'root' )
);
render( <PageLayout />, document.getElementById( 'root' ) );

View File

@ -4,8 +4,9 @@
*/
import { Component, createElement } from '@wordpress/element';
import { parse, stringify } from 'qs';
import { isEqual, last } from 'lodash';
import { find, isEqual, last } from 'lodash';
import { applyFilters } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';
/**
* WooCommerce dependencies
@ -15,8 +16,7 @@ import { getNewPath, getPersistedQuery, getHistory } from '@woocommerce/navigati
/**
* Internal dependencies
*/
import Analytics from 'analytics';
import AnalyticsReport from 'analytics/report';
import AnalyticsReport, { getReports } from 'analytics/report';
import AnalyticsSettings from 'analytics/settings';
import Dashboard from 'dashboard';
import DevDocs from 'devdocs';
@ -32,11 +32,13 @@ export const getPages = () => {
pages.push( {
container: DevDocs,
path: '/devdocs',
breadcrumbs: [ 'Documentation' ],
wpOpenMenu: 'toplevel_page_woocommerce',
} );
pages.push( {
container: DevDocs,
path: '/devdocs/:component',
breadcrumbs: ( { match } ) => [ [ '/devdocs', 'Documentation' ], match.params.component ],
wpOpenMenu: 'toplevel_page_woocommerce',
} );
}
@ -45,24 +47,31 @@ export const getPages = () => {
pages.push( {
container: Dashboard,
path: '/',
breadcrumbs: [ __( 'Dashboard', 'woocommerce-admin' ) ],
wpOpenMenu: 'toplevel_page_woocommerce',
} );
}
if ( window.wcAdminFeatures.analytics ) {
pages.push( {
container: Analytics,
path: '/analytics',
wpOpenMenu: 'toplevel_page_wc-admin-path--analytics-revenue',
} );
pages.push( {
container: AnalyticsSettings,
path: '/analytics/settings',
breadcrumbs: [
[ '/analytics/revenue', __( 'Analytics', 'woocommerce-admin' ) ],
__( 'Settings', 'woocommerce-admin' ),
],
wpOpenMenu: 'toplevel_page_wc-admin-path--analytics-revenue',
} );
pages.push( {
container: AnalyticsReport,
path: '/analytics/:report',
breadcrumbs: ( { match } ) => {
const report = find( getReports(), { report: match.params.report } );
if ( ! report ) {
return [];
}
return [ [ '/analytics/revenue', __( 'Analytics', 'woocommerce-admin' ) ], report.title ];
},
wpOpenMenu: 'toplevel_page_wc-admin-path--analytics-revenue',
} );
}

View File

@ -2,12 +2,11 @@
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import { Component } from '@wordpress/element';
import { useFilters } from '@woocommerce/components';
import { Router, Route, Switch } from 'react-router-dom';
import { Slot } from 'react-slot-fill';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { get, isFunction } from 'lodash';
/**
* WooCommerce dependencies
@ -24,6 +23,7 @@ import Notices from './notices';
import { recordPageView } from 'lib/tracks';
import TransientNotices from './transient-notices';
import StoreAlerts from './store-alerts';
import { REPORTS_FILTER } from 'analytics/report';
export class PrimaryLayout extends Component {
render() {
@ -76,9 +76,13 @@ class Layout extends Component {
render() {
const { isEmbedded, ...restProps } = this.props;
const { breadcrumbs } = this.props.page;
return (
<div className="woocommerce-layout">
<Slot name="header" />
<Header
sections={ isFunction( breadcrumbs ) ? breadcrumbs( this.props ) : breadcrumbs }
isEmbedded={ isEmbedded }
/>
<TransientNotices />
{ ! isEmbedded && (
<PrimaryLayout>
@ -94,6 +98,17 @@ class Layout extends Component {
Layout.propTypes = {
isEmbedded: PropTypes.bool,
page: PropTypes.shape( {
container: PropTypes.func.isRequired,
path: PropTypes.string.isRequired,
breadcrumbs: PropTypes.oneOfType( [
PropTypes.func,
PropTypes.arrayOf(
PropTypes.oneOfType( [ PropTypes.arrayOf( PropTypes.string ), PropTypes.string ] )
),
] ).isRequired,
wpOpenMenu: PropTypes.string.isRequired,
} ).isRequired,
};
class _PageLayout extends Component {
@ -116,16 +131,18 @@ class _PageLayout extends Component {
);
}
}
// Use the useFilters HoC so PageLayout is re-rendered when the filter is used to add new pages
export const PageLayout = useFilters( PAGES_FILTER )( _PageLayout );
// Use the useFilters HoC so PageLayout is re-rendered when filters are used to add new pages or reports
export const PageLayout = useFilters( [ PAGES_FILTER, REPORTS_FILTER ] )( _PageLayout );
export class EmbedLayout extends Component {
render() {
return (
<Fragment>
<Header sections={ wcSettings.embedBreadcrumbs } isEmbedded />
<Layout isEmbedded />
</Fragment>
<Layout
page={ {
breadcrumbs: wcSettings.embedBreadcrumbs,
} }
isEmbedded
/>
);
}
}

View File

@ -18349,11 +18349,6 @@
"tiny-warning": "^1.0.0"
}
},
"react-slot-fill": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/react-slot-fill/-/react-slot-fill-2.0.1.tgz",
"integrity": "sha512-jZzE+vbYAblPXSPFlir+aL5ljxwB0dJ62O2pR74OS/TUVDTW95msrdszJKLNp4lxzNcoHnCRIzLT6crBgTolGg=="
},
"react-test-renderer": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz",

View File

@ -104,7 +104,6 @@
"react-dates": "17.2.0",
"react-live": "1.12.0",
"react-router-dom": "5.0.1",
"react-slot-fill": "2.0.1",
"react-transition-group": "2.9.0",
"redux": "4.0.1"
},