From 6a82db326e4f44bfc2152aa119e2d57745e101a3 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 2 Aug 2018 18:20:48 -0400 Subject: [PATCH] SummaryNumber: Switch to dropdown display on small screens (https://github.com/woocommerce/woocommerce-admin/pull/265) * Move isMobileViewport to a helper function in lib * Switch SummaryList to use navigable menu to support up/down (or left/right) arrow key navigation * Switch to a dropdown menu/button combo when on a smaller screen * Ensure aria role & href are only added if the item is a link * Wrap the entire SummaryNumber in a link to match non-mobile use * Update card content to be single line on mobile * Add label to the popover title * Make SummaryNumbers edge-to-edge on smaller screens * Switch to the collapsed/dropdown view on screens <1100px * Adjust offset of arrow icon --- .../client/components/calendar/index.js | 7 +-- .../client/components/summary/README.md | 1 + .../client/components/summary/index.js | 58 +++++++++++++++---- .../client/components/summary/item.js | 28 ++++++++- .../client/components/summary/style.scss | 53 ++++++++++++++++- plugins/woocommerce-admin/client/lib/ui.js | 12 ++++ 6 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 plugins/woocommerce-admin/client/lib/ui.js diff --git a/plugins/woocommerce-admin/client/components/calendar/index.js b/plugins/woocommerce-admin/client/components/calendar/index.js index da5761293ab..e9194198564 100644 --- a/plugins/woocommerce-admin/client/components/calendar/index.js +++ b/plugins/woocommerce-admin/client/components/calendar/index.js @@ -18,15 +18,12 @@ import 'react-dates/lib/css/_datepicker.css'; /** * Internal dependencies */ -import { validateDateInputForRange } from 'lib/date'; import DateInput from './input'; +import { isMobileViewport } from 'lib/ui'; import phrases from './phrases'; +import { validateDateInputForRange } from 'lib/date'; import './style.scss'; -// 782px is the width designated by Gutenberg's `` component. -// * https://github.com/WordPress/gutenberg/blob/c8f8806d4465a83c1a0bc62d5c61377b56fa7214/components/popover/utils.js#L6 -const isMobileViewport = () => window.innerWidth < 782; - class DateRange extends Component { constructor( props ) { super( props ); diff --git a/plugins/woocommerce-admin/client/components/summary/README.md b/plugins/woocommerce-admin/client/components/summary/README.md index 5d992905877..020ca03bf43 100644 --- a/plugins/woocommerce-admin/client/components/summary/README.md +++ b/plugins/woocommerce-admin/client/components/summary/README.md @@ -30,6 +30,7 @@ render: function() { * `value` (required): A string or number value to display - a string is allowed so we can accept currency formatting. * `href` (required): An internal link to the report focused on this number. * `delta`: A number to represent the percentage change since the last comparison period - positive numbers will show a green up arrow, negative numbers will show a red down arrow. If omitted, no change value will display. +* `onToggle`: A function used to switch the given SummaryNumber to a button, and called on click. * `prevLabel`: A string description of the previous value's timeframe, ex "Previous Year:". Defaults to "Previous Period:". * `prevValue`: A string or number value to display - a string is allowed so we can accept currency formatting. If omitted, this section won't display. * `selected`: A boolean used to show a highlight style on this number. Defaults to false. diff --git a/plugins/woocommerce-admin/client/components/summary/index.js b/plugins/woocommerce-admin/client/components/summary/index.js index 07d0b1bd60d..7a4ee0a823a 100644 --- a/plugins/woocommerce-admin/client/components/summary/index.js +++ b/plugins/woocommerce-admin/client/components/summary/index.js @@ -4,35 +4,69 @@ */ import { __ } from '@wordpress/i18n'; import classnames from 'classnames'; +import { Children, cloneElement } from '@wordpress/element'; +import { Dropdown, NavigableMenu } from '@wordpress/components'; import PropTypes from 'prop-types'; import { uniqueId } from 'lodash'; /** * Internal dependencies */ +import { isMobileViewport, isTabletViewport } from 'lib/ui'; import './style.scss'; const SummaryList = ( { children, label } ) => { if ( ! label ) { label = __( 'Performance Indicators', 'wc-admin' ); } - // We default to "one" because we can't have empty children. If `children` is just one item, - // it's not an array and .length is undefined. - let hasItemsClass = 'has-one-item'; - if ( children && children.length ) { - const length = children.filter( Boolean ).length; - hasItemsClass = length < 10 ? `has-${ length }-items` : 'has-10-items'; - } - const classes = classnames( 'woocommerce-summary', hasItemsClass ); + const isDropdownBreakpoint = isTabletViewport() || isMobileViewport(); + + // We default to "one" because we can't have empty children. + const itemCount = Children.count( children ) || 1; + const hasItemsClass = itemCount < 10 ? `has-${ itemCount }-items` : 'has-10-items'; + const classes = classnames( 'woocommerce-summary', { + [ hasItemsClass ]: ! isDropdownBreakpoint, + } ); const instanceId = uniqueId( 'woocommerce-summary-helptext-' ); - return ( - + + ); + + // On large screens, or if there are not multiple SummaryNumbers, we'll display the plain list. + if ( ! isDropdownBreakpoint || itemCount < 2 ) { + return menu; + } + + const items = Children.toArray( children ); + const selected = items.find( item => !! item.props.selected ); + if ( ! selected ) { + return menu; + } + + return ( + cloneElement( selected, { onToggle } ) } + renderContent={ () => menu } + /> ); }; diff --git a/plugins/woocommerce-admin/client/components/summary/item.js b/plugins/woocommerce-admin/client/components/summary/item.js index 69757645438..36ce31f131d 100644 --- a/plugins/woocommerce-admin/client/components/summary/item.js +++ b/plugins/woocommerce-admin/client/components/summary/item.js @@ -3,6 +3,7 @@ * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; import classnames from 'classnames'; import Gridicon from 'gridicons'; import { isUndefined } from 'lodash'; @@ -17,12 +18,16 @@ const SummaryNumber = ( { delta, href, label, + onToggle, prevLabel, prevValue, reverseTrend, selected, value, } ) => { + const liClasses = classnames( 'woocommerce-summary__item-container', { + 'is-dropdown-button': onToggle, + } ); const classes = classnames( 'woocommerce-summary__item', { 'is-selected': selected, 'is-good-trend': reverseTrend ? delta < 0 : delta > 0, @@ -40,9 +45,21 @@ const SummaryNumber = ( { screenReaderLabel = sprintf( __( 'No change from %s', 'wc-admin' ), prevLabel ); } + const Container = onToggle ? Button : Link; + const containerProps = { + className: classes, + 'aria-current': selected ? 'page' : null, + }; + if ( ! onToggle ) { + containerProps.href = href; + containerProps.role = 'menuitem'; + } else { + containerProps.onClick = onToggle; + } + return ( -
  • - +
  • + { label } @@ -65,7 +82,11 @@ const SummaryNumber = ( { { ! isUndefined( prevValue ) ? prevValue : __( 'N/A', 'wc-admin' ) } - + + { onToggle ? ( + + ) : null } +
  • ); }; @@ -74,6 +95,7 @@ SummaryNumber.propTypes = { delta: PropTypes.number, href: PropTypes.string.isRequired, label: PropTypes.string.isRequired, + onToggle: PropTypes.func, prevLabel: PropTypes.string, prevValue: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ), reverseTrend: PropTypes.bool, diff --git a/plugins/woocommerce-admin/client/components/summary/style.scss b/plugins/woocommerce-admin/client/components/summary/style.scss index bf39ffaf0c3..e8a5c00a0f9 100644 --- a/plugins/woocommerce-admin/client/components/summary/style.scss +++ b/plugins/woocommerce-admin/client/components/summary/style.scss @@ -38,6 +38,7 @@ $inner-border: $core-grey-light-500; } .woocommerce-summary { + margin: $gap 0; display: grid; border-width: 1px 0 0 1px; border-style: solid; @@ -45,6 +46,14 @@ $inner-border: $core-grey-light-500; background-color: $core-grey-light-300; box-shadow: inset -1px -1px 0 $outer-border; + .components-popover__content & { + max-height: 100%; + margin-top: 0; + margin-bottom: 0; + overflow-y: scroll; + border: none; + } + .woocommerce-summary__item-data { display: flex; flex-wrap: wrap; @@ -116,6 +125,19 @@ $inner-border: $core-grey-light-500; } } } + + @include breakpoint( '<782px' ) { + .woocommerce-summary__item-container:only-child { + margin-left: -16px; + margin-right: -16px; + width: auto; + + & .woocommerce-summary__item { + // Remove the border when the button is edge-to-edge + border-right: none; + } + } + } } .woocommerce-summary__item-container { @@ -126,6 +148,15 @@ $inner-border: $core-grey-light-500; // Make sure the last item always uses the outer-border color. border-right-color: $outer-border !important; } + + &.is-dropdown-button { + padding: 0; + list-style: none; + + .components-button { + text-align: left; + } + } } .woocommerce-summary__item { @@ -146,7 +177,8 @@ $inner-border: $core-grey-light-500; } &:focus { - box-shadow: inset -2px -2px 0 $black, inset 2px 2px 0 $black; + // !important to override button styles + box-shadow: inset -2px -2px 0 $black, inset 2px 2px 0 $black !important; } &.is-selected { @@ -154,7 +186,18 @@ $inner-border: $core-grey-light-500; height: calc(100% + 1px); // Hack to avoid double border &:focus { - box-shadow: inset -2px -2px 0 $black, inset 2px 2px 0 $black, inset 0 4px 0 $woocommerce; + // !important to override button styles + box-shadow: inset -2px -2px 0 $black, inset 2px 2px 0 $black, inset 0 4px 0 $woocommerce !important; + } + } + + .is-dropdown-button & { + position: relative; + width: 100%; + padding-right: 2 * $gap + 24px; + + @include breakpoint( '<782px' ) { + border-right: none; } } @@ -220,6 +263,12 @@ $inner-border: $core-grey-light-500; @include font-size( 13 ); color: $core-grey-dark-500; } + + .woocommerce-summary__toggle { + position: absolute; + top: 44px; + right: $gap; + } } .woocommerce-card { diff --git a/plugins/woocommerce-admin/client/lib/ui.js b/plugins/woocommerce-admin/client/lib/ui.js new file mode 100644 index 00000000000..d9102d6c429 --- /dev/null +++ b/plugins/woocommerce-admin/client/lib/ui.js @@ -0,0 +1,12 @@ +/** @format */ + +// 782px is the width designated by Gutenberg's `` component. +// * https://github.com/WordPress/gutenberg/blob/c8f8806d4465a83c1a0bc62d5c61377b56fa7214/components/popover/utils.js#L6 +export function isMobileViewport() { + return window.innerWidth <= 782; +} + +// Most screens at 1100px or lower are tablets +export function isTabletViewport() { + return window.innerWidth > 782 && window.innerWidth <= 1100; +}