Update comparison chart design and show it on the Product report (https://github.com/woocommerce/woocommerce-admin/pull/816)
* Display comparison chart in Product detail report * Make legend to overflow in comparison charts * Show comparison chart also when comparing products * Update comparison chart legend design * Send itemsLabel and comparisonChart as props to the ReportChart component * Update styles and create a legend.scss file * Minor cleanup * Fix legend test * Sort props alphabetically
This commit is contained in:
parent
3637ae5054
commit
5033a1ba5c
|
@ -29,7 +29,15 @@ import ReportError from 'analytics/components/report-error';
|
|||
|
||||
class ReportChart extends Component {
|
||||
render() {
|
||||
const { path, primaryData, secondaryData, selectedChart, query } = this.props;
|
||||
const {
|
||||
comparisonChart,
|
||||
query,
|
||||
itemsLabel,
|
||||
path,
|
||||
primaryData,
|
||||
secondaryData,
|
||||
selectedChart,
|
||||
} = this.props;
|
||||
|
||||
if ( primaryData.isError || secondaryData.isError ) {
|
||||
return <ReportError isError />;
|
||||
|
@ -84,7 +92,9 @@ class ReportChart extends Component {
|
|||
title={ selectedChart.label }
|
||||
interval={ currentInterval }
|
||||
allowedIntervals={ allowedIntervals }
|
||||
mode="time-comparison"
|
||||
itemsLabel={ itemsLabel }
|
||||
layout={ comparisonChart ? 'comparison' : 'standard' }
|
||||
mode={ comparisonChart ? 'item-comparison' : 'time-comparison' }
|
||||
pointLabelFormat={ formats.pointLabelFormat }
|
||||
tooltipTitle={ selectedChart.label }
|
||||
xFormat={ formats.xFormat }
|
||||
|
@ -97,6 +107,8 @@ class ReportChart extends Component {
|
|||
}
|
||||
|
||||
ReportChart.propTypes = {
|
||||
comparisonChart: PropTypes.bool,
|
||||
itemsLabel: PropTypes.string,
|
||||
path: PropTypes.string.isRequired,
|
||||
primaryData: PropTypes.object.isRequired,
|
||||
query: PropTypes.object.isRequired,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -23,6 +24,11 @@ export default class ProductsReport extends Component {
|
|||
render() {
|
||||
const { path, query } = this.props;
|
||||
|
||||
const itemsLabel =
|
||||
'single_product' === query.filter && !! query.products
|
||||
? __( '%s variations', 'wc-admin' )
|
||||
: __( '%s products', 'wc-admin' );
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ReportFilters query={ query } path={ path } filters={ filters } />
|
||||
|
@ -33,8 +39,10 @@ export default class ProductsReport extends Component {
|
|||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
/>
|
||||
<ReportChart
|
||||
comparisonChart
|
||||
charts={ charts }
|
||||
endpoint="products"
|
||||
itemsLabel={ itemsLabel }
|
||||
path={ path }
|
||||
query={ query }
|
||||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
|
|
|
@ -192,6 +192,7 @@ class Chart extends Component {
|
|||
const { orderedKeys, type, visibleData, width } = this.state;
|
||||
const {
|
||||
dateParser,
|
||||
itemsLabel,
|
||||
layout,
|
||||
mode,
|
||||
pointLabelFormat,
|
||||
|
@ -211,12 +212,12 @@ class Chart extends Component {
|
|||
chartHeight = width <= 783 ? 180 : chartHeight;
|
||||
const legend = (
|
||||
<Legend
|
||||
className={ 'woocommerce-chart__legend' }
|
||||
colorScheme={ d3InterpolateViridis }
|
||||
data={ orderedKeys }
|
||||
handleLegendHover={ this.handleLegendHover }
|
||||
handleLegendToggle={ this.handleLegendToggle }
|
||||
legendDirection={ legendDirection }
|
||||
itemsLabel={ itemsLabel }
|
||||
valueType={ valueType }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { Component, createRef } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { sprintf } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
|
@ -14,7 +15,7 @@ import { formatCurrency } from '@woocommerce/currency';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import './legend.scss';
|
||||
import { getColor } from './utils';
|
||||
|
||||
function getFormatedTotal( total, valueType ) {
|
||||
|
@ -36,65 +37,108 @@ function getFormatedTotal( total, valueType ) {
|
|||
* A legend specifically designed for the WooCommerce admin charts.
|
||||
*/
|
||||
class Legend extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.listRef = createRef();
|
||||
|
||||
this.state = {
|
||||
isScrollable: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateListScroll();
|
||||
window.addEventListener( 'resize', this.updateListScroll );
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener( 'resize', this.updateListScroll );
|
||||
}
|
||||
|
||||
updateListScroll = () => {
|
||||
const list = this.listRef.current;
|
||||
const scrolledToEnd = list.scrollHeight - list.scrollTop <= list.offsetHeight;
|
||||
this.setState( {
|
||||
isScrollable: ! scrolledToEnd,
|
||||
} );
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
colorScheme,
|
||||
data,
|
||||
handleLegendHover,
|
||||
handleLegendToggle,
|
||||
itemsLabel,
|
||||
legendDirection,
|
||||
valueType,
|
||||
} = this.props;
|
||||
const { isScrollable } = this.state;
|
||||
const colorParams = {
|
||||
orderedKeys: data,
|
||||
colorScheme,
|
||||
};
|
||||
const numberOfRowsVisible = data.filter( row => row.visible ).length;
|
||||
const showTotalLabel = legendDirection === 'column' && data.length > 5 && itemsLabel;
|
||||
|
||||
return (
|
||||
<ul
|
||||
<div
|
||||
className={ classNames(
|
||||
'woocommerce-legend',
|
||||
`woocommerce-legend__direction-${ legendDirection }`,
|
||||
{
|
||||
'has-total': showTotalLabel,
|
||||
'is-scrollable': isScrollable,
|
||||
},
|
||||
this.props.className
|
||||
) }
|
||||
>
|
||||
{ data.map( row => (
|
||||
<li
|
||||
className={ classNames( 'woocommerce-legend__item', {
|
||||
'woocommerce-legend__item-checked': row.visible,
|
||||
} ) }
|
||||
key={ row.key }
|
||||
id={ row.key }
|
||||
onMouseEnter={ handleLegendHover }
|
||||
onMouseLeave={ handleLegendHover }
|
||||
onBlur={ handleLegendHover }
|
||||
onFocus={ handleLegendHover }
|
||||
>
|
||||
<button
|
||||
onClick={ handleLegendToggle }
|
||||
<ul
|
||||
className="woocommerce-legend__list"
|
||||
ref={ this.listRef }
|
||||
onScroll={ showTotalLabel ? this.updateListScroll : null }
|
||||
>
|
||||
{ data.map( row => (
|
||||
<li
|
||||
className={ classNames( 'woocommerce-legend__item', {
|
||||
'woocommerce-legend__item-checked': row.visible,
|
||||
} ) }
|
||||
key={ row.key }
|
||||
id={ row.key }
|
||||
disabled={ row.visible && numberOfRowsVisible <= 1 }
|
||||
onMouseEnter={ handleLegendHover }
|
||||
onMouseLeave={ handleLegendHover }
|
||||
onBlur={ handleLegendHover }
|
||||
onFocus={ handleLegendHover }
|
||||
>
|
||||
<div className="woocommerce-legend__item-container" id={ row.key }>
|
||||
<span
|
||||
className={ classNames( 'woocommerce-legend__item-checkmark', {
|
||||
'woocommerce-legend__item-checkmark-checked': row.visible,
|
||||
} ) }
|
||||
id={ row.key }
|
||||
style={ { color: getColor( row.key, colorParams ) } }
|
||||
/>
|
||||
<span className="woocommerce-legend__item-title" id={ row.key }>
|
||||
{ row.key }
|
||||
</span>
|
||||
<span className="woocommerce-legend__item-total" id={ row.key }>
|
||||
{ getFormatedTotal( row.total, valueType ) }
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
) ) }
|
||||
</ul>
|
||||
<button
|
||||
onClick={ handleLegendToggle }
|
||||
id={ row.key }
|
||||
disabled={ row.visible && numberOfRowsVisible <= 1 }
|
||||
>
|
||||
<div className="woocommerce-legend__item-container" id={ row.key }>
|
||||
<span
|
||||
className={ classNames( 'woocommerce-legend__item-checkmark', {
|
||||
'woocommerce-legend__item-checkmark-checked': row.visible,
|
||||
} ) }
|
||||
id={ row.key }
|
||||
style={ { color: getColor( row.key, colorParams ) } }
|
||||
/>
|
||||
<span className="woocommerce-legend__item-title" id={ row.key }>
|
||||
{ row.key }
|
||||
</span>
|
||||
<span className="woocommerce-legend__item-total" id={ row.key }>
|
||||
{ getFormatedTotal( row.total, valueType ) }
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
) ) }
|
||||
</ul>
|
||||
{ showTotalLabel && (
|
||||
<div className="woocommerce-legend__total">{ sprintf( itemsLabel, data.length ) }</div>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +168,11 @@ Legend.propTypes = {
|
|||
* Display legend items as a `row` or `column` inside a flex-box.
|
||||
*/
|
||||
legendDirection: PropTypes.oneOf( [ 'row', 'column' ] ),
|
||||
/**
|
||||
* Label to describe the legend items. It will be displayed in the legend of
|
||||
* comparison charts when there are many.
|
||||
*/
|
||||
itemsLabel: PropTypes.string,
|
||||
/**
|
||||
* What type of data is to be displayed? Number, Average, String?
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
/** @format */
|
||||
|
||||
.woocommerce-legend {
|
||||
&.has-total {
|
||||
padding-bottom: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.woocommerce-legend__direction-column {
|
||||
border-right: 1px solid $core-grey-light-700;
|
||||
|
||||
.woocommerce-chart__footer & {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__list {
|
||||
color: $black;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
.woocommerce-legend__direction-column & {
|
||||
flex-direction: column;
|
||||
height: 300px;
|
||||
min-width: 320px;
|
||||
overflow: auto;
|
||||
|
||||
.woocommerce-chart__footer & {
|
||||
border-top: 1px solid $core-grey-light-700;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
min-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.has-total.woocommerce-legend__direction-column & {
|
||||
.woocommerce-chart__footer & {
|
||||
height: auto;
|
||||
max-height: 220px;
|
||||
min-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__direction-row & {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__item {
|
||||
& > button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $white;
|
||||
color: $core-grey-dark-500;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 3px 0 3px 24px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
input {
|
||||
~ .woocommerce-legend__item-checkmark {
|
||||
background-color: $core-grey-light-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-checkmark {
|
||||
border: 1px solid $core-grey-light-900;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: $white;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.woocommerce-legend__item-checkmark-checked {
|
||||
background-color: currentColor;
|
||||
border-color: currentColor;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
width: 3px;
|
||||
height: 6px;
|
||||
border: solid $white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-total {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
.woocommerce-legend__item-checkmark {
|
||||
outline: 2px solid $core-grey-light-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $core-grey-light-100;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__direction-column & {
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
|
||||
& > button {
|
||||
height: 32px;
|
||||
padding: 0 17px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: $gap-small;
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: $gap-small;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__direction-row & {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
& > button {
|
||||
padding: 0 17px;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
|
||||
.woocommerce-legend__item-checkmark {
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-title {
|
||||
margin-right: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__total {
|
||||
align-items: center;
|
||||
background: $white;
|
||||
border-top: 1px solid $core-grey-light-700;
|
||||
bottom: 0;
|
||||
color: $core-grey-dark-500;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-transform: uppercase;
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2));
|
||||
bottom: 100%;
|
||||
content: '';
|
||||
height: 20px;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.is-scrollable &::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -61,15 +61,6 @@
|
|||
|
||||
.woocommerce-chart__footer {
|
||||
width: 100%;
|
||||
|
||||
.woocommerce-legend {
|
||||
&.woocommerce-legend__direction-column {
|
||||
height: 100%;
|
||||
min-height: none;
|
||||
border-right: none;
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -252,146 +243,3 @@
|
|||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend {
|
||||
color: $black;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
&.woocommerce-legend__direction-column {
|
||||
flex-direction: column;
|
||||
border-right: 1px solid $core-grey-light-700;
|
||||
height: 300px;
|
||||
min-width: 320px;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
button {
|
||||
height: 32px;
|
||||
padding: 0 17px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.woocommerce-legend__direction-row {
|
||||
flex-direction: row;
|
||||
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
button {
|
||||
padding: 0 17px;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
|
||||
.woocommerce-legend__item-checkmark {
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-title {
|
||||
margin-right: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
&.woocommerce-legend__item {
|
||||
button {
|
||||
&:hover {
|
||||
background-color: $core-grey-light-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $white;
|
||||
color: $core-grey-dark-500;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 3px 0 3px 24px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
input {
|
||||
~ .woocommerce-legend__item-checkmark {
|
||||
background-color: $core-grey-light-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-checkmark {
|
||||
border: 1px solid $core-grey-light-900;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: $white;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.woocommerce-legend__item-checkmark-checked {
|
||||
background-color: currentColor;
|
||||
border-color: currentColor;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
width: 3px;
|
||||
height: 6px;
|
||||
border: solid $white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-legend__item-total {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
.woocommerce-legend__item-container {
|
||||
.woocommerce-legend__item-checkmark {
|
||||
outline: 2px solid $core-grey-light-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* @format
|
||||
*/
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -26,7 +26,7 @@ const data = [
|
|||
|
||||
describe( 'Legend', () => {
|
||||
test( 'should not disable any button if more than one is active', () => {
|
||||
const topSellingProducts = shallow( <Legend colorScheme={ colorScheme } data={ data } /> );
|
||||
const topSellingProducts = mount( <Legend colorScheme={ colorScheme } data={ data } /> );
|
||||
|
||||
expect( topSellingProducts.find( 'button' ).get( 0 ).props.disabled ).toBeFalsy();
|
||||
expect( topSellingProducts.find( 'button' ).get( 1 ).props.disabled ).toBeFalsy();
|
||||
|
@ -35,7 +35,7 @@ describe( 'Legend', () => {
|
|||
test( 'should disable the last active button', () => {
|
||||
data[ 1 ].visible = false;
|
||||
|
||||
const topSellingProducts = shallow( <Legend colorScheme={ colorScheme } data={ data } /> );
|
||||
const topSellingProducts = mount( <Legend colorScheme={ colorScheme } data={ data } /> );
|
||||
|
||||
expect( topSellingProducts.find( 'button' ).get( 0 ).props.disabled ).toBeTruthy();
|
||||
expect( topSellingProducts.find( 'button' ).get( 1 ).props.disabled ).toBeFalsy();
|
||||
|
|
Loading…
Reference in New Issue