Add ProductAttributeControl for selecting product attributes (https://github.com/woocommerce/woocommerce-blocks/pull/266)

* Add initial attribute selector control

* Make the attribute group name unselectable

* Update selected map to reflect attribute syntax

* Add the attributes selector to the product category block

* Update copy-paste comment

* Update variable name to be more clear

* Move & rename the attribute selector in Products by Category

* Reorganize the API calls to fetch attributes + attribute terms
This commit is contained in:
Kelly Dwan 2018-12-20 17:26:51 -05:00 committed by GitHub
parent 0b91be551e
commit 32b5b07c23
5 changed files with 183 additions and 1 deletions

View File

@ -0,0 +1,147 @@
/**
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { Component } from '@wordpress/element';
import { find } from 'lodash';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import './style.scss';
import SearchListControl from '../search-list-control';
import SearchListItem from '../search-list-control/item';
class ProductAttributeControl extends Component {
constructor() {
super( ...arguments );
this.state = {
list: [],
loading: true,
};
}
componentDidMount() {
const getTermsInAttribute = ( { id } ) => {
return apiFetch( {
path: addQueryArgs( `/wc-pb/v3/products/attributes/${ id }/terms`, {
per_page: -1,
} ),
} ).then( ( terms ) => terms.map( ( t ) => ( { ...t, parent: id } ) ) );
};
apiFetch( {
path: addQueryArgs( '/wc-pb/v3/products/attributes', { per_page: -1 } ),
} )
.then( ( attributes ) => {
// Fetch the terms list for each attribute group, then flatten them into one list.
Promise.all( attributes.map( getTermsInAttribute ) ).then( ( results ) => {
const list = attributes.map( ( a ) => ( { ...a, parent: 0 } ) );
results.forEach( ( terms ) => {
list.push( ...terms );
} );
this.setState( { list, loading: false } );
} );
} )
.catch( () => {
this.setState( { list: [], loading: false } );
} );
}
renderItem( args ) {
const { item, search, depth = 0 } = args;
const classes = [
'woocommerce-product-attributes__item',
'woocommerce-search-list__item',
];
if ( search.length ) {
classes.push( 'is-searching' );
}
if ( depth === 0 && item.parent !== 0 ) {
classes.push( 'is-skip-level' );
}
if ( ! item.breadcrumbs.length ) {
classes.push( 'is-not-active' );
return (
<div className={ classes.join( ' ' ) }>
<span className="woocommerce-search-list__item-label">
<span className="woocommerce-search-list__item-name">
{ item.name }
</span>
</span>
</div>
);
}
return (
<SearchListItem
className={ classes.join( ' ' ) }
{ ...args }
showCount
aria-label={ `${ item.breadcrumbs[ 0 ] }: ${ item.name }` }
/>
);
}
render() {
const { list, loading } = this.state;
const { selected, onChange } = this.props;
const messages = {
clear: __( 'Clear all product attributes', 'woo-gutenberg-products-block' ),
list: __( 'Product Attributes', 'woo-gutenberg-products-block' ),
noItems: __(
"Your store doesn't have any product attributes.",
'woo-gutenberg-products-block'
),
search: __(
'Search for product attributes',
'woo-gutenberg-products-block'
),
selected: ( n ) =>
sprintf(
_n(
'%d attribute selected',
'%d attributes selected',
n,
'woo-gutenberg-products-block'
),
n
),
updated: __(
'Product attribute search results updated.',
'woo-gutenberg-products-block'
),
};
return (
<SearchListControl
className="woocommerce-product-attributes"
list={ list }
isLoading={ loading }
selected={ selected.map( ( { id } ) => find( list, { id } ) ).filter( Boolean ) }
onChange={ onChange }
renderItem={ this.renderItem }
messages={ messages }
isHierarchical
/>
);
}
}
ProductAttributeControl.propTypes = {
/**
* Callback to update the selected product attributes.
*/
onChange: PropTypes.func.isRequired,
/**
* The list of currently selected attribute slug/ID pairs.
*/
selected: PropTypes.array.isRequired,
};
export default ProductAttributeControl;

View File

@ -0,0 +1,14 @@
.woocommerce-search-list__item.woocommerce-product-attributes__item {
&.is-searching,
&.is-skip-level {
.woocommerce-search-list__item-prefix:after {
content: ":";
}
}
&.is-not-active {
@include hover-state {
background: transparent;
}
}
}

View File

@ -95,7 +95,7 @@
background: $core-grey-light-100;
}
&:last-of-type {
&:last-child {
border-bottom: none !important;
}

View File

@ -26,6 +26,7 @@ import PropTypes from 'prop-types';
* Internal dependencies
*/
import getQuery from './utils/get-query';
import ProductAttributeControl from './components/product-attribute-control';
import ProductCategoryControl from './components/product-category-control';
import ProductOrderbyControl from './components/product-orderby-control';
import ProductPreview from './components/product-preview';
@ -130,6 +131,18 @@ class ProductByCategoryBlock extends Component {
value={ orderby }
/>
</PanelBody>
<PanelBody
title={ __( 'Filter by Attribute', 'woo-gutenberg-products-block' ) }
initialOpen={ false }
>
<ProductAttributeControl
selected={ attributes.attributes }
onChange={ ( value = [] ) => {
const selected = value.map( ( { id, attr_slug } ) => ( { id, attr_slug } ) ); // eslint-disable-line camelcase
setAttributes( { attributes: selected } );
} }
/>
</PanelBody>
</InspectorControls>
);
}

View File

@ -37,4 +37,12 @@ export default {
type: 'string',
default: 'any',
},
/**
* Product attributes, used to display only products with the given attributes.
*/
attributes: {
type: 'array',
default: [],
},
};