diff --git a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js index a983e95b55e..488d49ca885 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js @@ -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 ; @@ -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, diff --git a/plugins/woocommerce-admin/client/analytics/report/products/index.js b/plugins/woocommerce-admin/client/analytics/report/products/index.js index c274276bc6f..24b94ddbf9b 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/index.js @@ -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 ( @@ -33,8 +39,10 @@ export default class ProductsReport extends Component { selectedChart={ getSelectedChart( query.chart, charts ) } /> ); diff --git a/plugins/woocommerce-admin/client/components/chart/legend.js b/plugins/woocommerce-admin/client/components/chart/legend.js index 8db8e9248ec..feab9362b93 100644 --- a/plugins/woocommerce-admin/client/components/chart/legend.js +++ b/plugins/woocommerce-admin/client/components/chart/legend.js @@ -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 ( -
    - { data.map( row => ( -
  • - -
  • - ) ) } -
+ + + ) ) } + + { showTotalLabel && ( +
{ sprintf( itemsLabel, data.length ) }
+ ) } + ); } } @@ -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? */ diff --git a/plugins/woocommerce-admin/client/components/chart/legend.scss b/plugins/woocommerce-admin/client/components/chart/legend.scss new file mode 100644 index 00000000000..469c4a2e076 --- /dev/null +++ b/plugins/woocommerce-admin/client/components/chart/legend.scss @@ -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; + } +} diff --git a/plugins/woocommerce-admin/client/components/chart/style.scss b/plugins/woocommerce-admin/client/components/chart/style.scss index ad1919bd3fc..2856bb47ca6 100644 --- a/plugins/woocommerce-admin/client/components/chart/style.scss +++ b/plugins/woocommerce-admin/client/components/chart/style.scss @@ -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; - } - } - } - } - } -} diff --git a/plugins/woocommerce-admin/client/components/chart/test/legend.js b/plugins/woocommerce-admin/client/components/chart/test/legend.js index 9be1db292ac..e072fd0dfc6 100644 --- a/plugins/woocommerce-admin/client/components/chart/test/legend.js +++ b/plugins/woocommerce-admin/client/components/chart/test/legend.js @@ -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( ); + const topSellingProducts = mount( ); 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( ); + const topSellingProducts = mount( ); expect( topSellingProducts.find( 'button' ).get( 0 ).props.disabled ).toBeTruthy(); expect( topSellingProducts.find( 'button' ).get( 1 ).props.disabled ).toBeFalsy();