Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
ProductAttributeControl: Polish style, screen reader interaction (#412)
Browse files Browse the repository at this point in the history
* Save attribute terms per attribute, so we don’t need to do duplicate API requests for previously fetched terms. Add spinner to loading items.

* Add count to API response

* Use terms count in displaying attribute type

* Update attribute selection code to collapse already-selected attributes

* Add a chevron icon indicating open-able attributes

* Center the loading indicator

* Remove count from attribute type & re-add it to the terms themselves
  • Loading branch information
ryelle committed Feb 8, 2019
1 parent f611788 commit a36db08
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 32 deletions.
69 changes: 45 additions & 24 deletions assets/js/components/product-attribute-control/index.js
Expand Up @@ -5,9 +5,9 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { Component, Fragment } from '@wordpress/element';
import { debounce, filter, find, uniqBy } from 'lodash';
import { debounce, find } from 'lodash';
import PropTypes from 'prop-types';
import { SelectControl } from '@wordpress/components';
import { SelectControl, Spinner } from '@wordpress/components';

/**
* Internal dependencies
Expand All @@ -23,6 +23,7 @@ class ProductAttributeControl extends Component {
list: [],
loading: true,
attribute: 0,
termsList: {},
termsLoading: true,
};

Expand Down Expand Up @@ -58,10 +59,13 @@ class ProductAttributeControl extends Component {
}

getTerms() {
const { attribute } = this.state;
const { attribute, termsList } = this.state;
if ( ! attribute ) {
return;
}
if ( ! termsList[ attribute ] ) {
this.setState( { termsLoading: true } );
}

apiFetch( {
path: addQueryArgs( `/wc-pb/v3/products/attributes/${ attribute }/terms`, {
Expand All @@ -70,8 +74,8 @@ class ProductAttributeControl extends Component {
} )
.then( ( terms ) => {
terms = terms.map( ( term ) => ( { ...term, parent: attribute } ) );
this.setState( ( { list } ) => ( {
list: uniqBy( [ ...list, ...terms ], 'id' ),
this.setState( ( prevState ) => ( {
termsList: { ...prevState.termsList, [ attribute ]: terms },
termsLoading: false,
} ) );
} )
Expand All @@ -82,24 +86,16 @@ class ProductAttributeControl extends Component {

onSelectAttribute( item ) {
return () => {
if ( item.id === this.state.attribute ) {
return;
}
this.props.onChange( [] );
this.setState( ( { list } ) => {
// Remove all other attribute terms from the list.
const updatedList = filter( list, { parent: 0 } );
return {
list: updatedList,
attribute: item.id,
};
this.setState( {
attribute: item.id === this.state.attribute ? 0 : item.id,
} );
};
}

renderItem( args ) {
const { item, search, depth = 0 } = args;
const { attribute } = this.state;
const { attribute, termsLoading } = this.state;
const classes = [
'woocommerce-product-attributes__item',
'woocommerce-search-list__item',
Expand All @@ -112,16 +108,39 @@ class ProductAttributeControl extends Component {
}

if ( ! item.breadcrumbs.length ) {
classes.push( 'is-not-active' );
return (
return [
<SearchListItem
key={ `attr-${ item.id }` }
{ ...args }
className={ classes.join( ' ' ) }
isSingle
isSelected={ attribute === item.id }
onSelect={ this.onSelectAttribute }
/>
);
isSingle
disabled={ '0' === item.count }
aria-expanded={ attribute === item.id }
aria-label={ sprintf(
_n(
'%s, has %d term',
'%s, has %d terms',
item.count,
'woo-gutenberg-products-block'
),
item.name,
item.count
) }
/>,
attribute === item.id && termsLoading && (
<div
key="loading"
className={
'woocommerce-search-list__item woocommerce-product-attributes__item' +
'depth-1 is-loading is-not-active'
}
>
<Spinner />
</div>
),
];
}

return (
Expand All @@ -135,8 +154,10 @@ class ProductAttributeControl extends Component {
}

render() {
const { list, loading } = this.state;
const { attribute, list, loading, termsList } = this.state;
const { onChange, onOperatorChange, operator, selected } = this.props;
const currentTerms = termsList[ attribute ] || [];
const currentList = [ ...list, ...currentTerms ];

const messages = {
clear: __( 'Clear all product attributes', 'woo-gutenberg-products-block' ),
Expand Down Expand Up @@ -169,10 +190,10 @@ class ProductAttributeControl extends Component {
<Fragment>
<SearchListControl
className="woocommerce-product-attributes"
list={ list }
list={ currentList }
isLoading={ loading }
selected={ selected
.map( ( { id } ) => find( list, { id } ) )
.map( ( { id } ) => find( currentList, { id } ) )
.filter( Boolean ) }
onChange={ onChange }
renderItem={ this.renderItem }
Expand Down
31 changes: 30 additions & 1 deletion assets/js/components/product-attribute-control/style.scss
Expand Up @@ -25,7 +25,36 @@

&.is-not-active {
@include hover-state {
background: transparent;
background: $white;
}
}

&.is-loading {
justify-content: center;

.components-spinner {
margin-bottom: $gap-small;
}
}

&.depth-0::after {
margin-left: $gap-smaller;
content: '';
height: $gap-large;
width: $gap-large;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="#{$core-grey-dark-300}" /></svg>');
background-repeat: no-repeat;
background-position: center right;
background-size: contain;
}

&.depth-0[aria-expanded="true"]::after {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" fill="#{$core-grey-dark-300}" /></svg>');
}

&[disabled].depth-0::after {
margin-left: 0;
width: auto;
background: none;
}
}
1 change: 1 addition & 0 deletions assets/js/components/search-list-control/style.scss
Expand Up @@ -70,6 +70,7 @@

.components-spinner {
float: none;
margin: 0 auto;
}

.components-menu-group__label {
Expand Down
22 changes: 15 additions & 7 deletions includes/class-wgpb-product-attributes-controller.php
Expand Up @@ -138,10 +138,12 @@ protected function check_permissions( $request, $context = 'read' ) {
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => (int) $item->attribute_id,
'name' => $item->attribute_label,
'slug' => wc_attribute_taxonomy_name( $item->attribute_name ),
$taxonomy = wc_attribute_taxonomy_name( $item->attribute_name );
$data = array(
'id' => (int) $item->attribute_id,
'name' => $item->attribute_label,
'slug' => $taxonomy,
'count' => wp_count_terms( $taxonomy ),
);

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
Expand Down Expand Up @@ -169,9 +171,15 @@ public function get_item_schema() {
'properties' => array(),
);

$schema['properties']['id'] = $raw_schema['properties']['id'];
$schema['properties']['name'] = $raw_schema['properties']['name'];
$schema['properties']['slug'] = $raw_schema['properties']['slug'];
$schema['properties']['id'] = $raw_schema['properties']['id'];
$schema['properties']['name'] = $raw_schema['properties']['name'];
$schema['properties']['slug'] = $raw_schema['properties']['slug'];
$schema['properties']['count'] = array(
'description' => __( 'Number of terms in the attribute taxonomy.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
);

return $this->add_additional_fields_schema( $schema );
}
Expand Down

0 comments on commit a36db08

Please sign in to comment.