Merge pull request woocommerce/woocommerce-blocks#89 from woocommerce/update/individual-products

Changes to Individual Products
This commit is contained in:
Claudiu Lodromanean 2018-04-11 08:47:28 -07:00 committed by GitHub
commit 04ebea5184
8 changed files with 460 additions and 365 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -334,19 +334,36 @@ p.wc-products-display-options__option-description {
overflow: hidden;
box-sizing: border-box;
.wc-products-list-card__search {
width: 100%;
.wc-products-list-card__input-wrapper {
position: relative;
background: #ffffff;
margin: 0 0 1em;
padding: .75em 1.25em;
.dashicon {
position: absolute;
top: calc( 1em - 1px );
left: 1em;
z-index: 1;
}
}
.wc-products-list-card__search {
position: relative;
width: 100%;
margin: 0;
padding: 1em 1.25em 1em 3em;
border-radius: 0;
background: transparent;
border-color: $color__border;
box-shadow: none;
z-index: 2;
}
.wc-products-list-card__results {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
padding: 0 0 1em;
ul {
list-style: none;
@ -571,20 +588,25 @@ p.wc-products-display-options__option-description {
.wc-products-list-card--specific {
overflow: visible;
.wc-products-list-card__content {
padding: .75em;
border-bottom-width: 0;
&:after {
content: none;
}
.wc-products-list-card__item {
position: relative;
border: none;
img {
max-width: 2.5em;
margin: 0 1em 0 0;
margin: 0;
outline: 4px solid $color__link--hover;
outline-offset: -4px;
}
button {
background: #f1f1f1;
border-radius: 100%;
position: absolute;
top: 0;
right: 0;
background: $color__link--hover;
padding: 0;
margin: 0;
border: none;
@ -594,54 +616,138 @@ p.wc-products-display-options__option-description {
}
.dashicon {
color: #555D66;
color: #ffffff;
}
}
.wc-products-list-card__input-wrapper {
margin: 0;
}
.wc-products-list-card__results-wrapper {
@for $i from 1 through 6 {
$width: percentage( 1 / $i );
&--cols-#{$i} {
.wc-products-list-card__item {
width: $width;
}
}
}
}
.wc-products-list-card__results {
max-height: none;
overflow: visible;
h3 {
margin: 0 0 1em;
font-size: 1em;
}
ul {
display: flex;
flex-wrap: wrap;
margin: 0 -.5em -1em;
li {
border: none;
padding: 0 .5em;
margin: 0 0 1em;
}
}
.wc-products-list-card__content {
position: relative;
display: block;
padding: 0;
background: transparent;
border: none;
}
}
}
.wc-products-list-card__search-wrapper {
position: relative;
margin: 0 0 1.5em;
}
.wc-products-list-card__search-results {
position: absolute;
top: calc(100% - 1em - 1px);
width: 100%;
z-index: 999;
list-style: none;
background: #ffffff;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.1);
margin: -1px 0 0;
border: 1px solid $color__border;
box-shadow: 0 1px 3px $color__border;
> div {
max-height: 200px;
max-height: 175px;
overflow-y: auto;
}
.wc-products-list-card__content {
position: relative;
border-width: 1px 0 0;
border-style: solid;
border-color: $color__border;
transition: opacity .7s;
cursor: pointer;
color: $color__link--hover;
&--added {
background-color: lighten( $color__link, 65% );
}
&:hover {
background-color: lighten( $color__link, 65% );
}
&--transition-exit-active {
opacity: 0;
}
&:last-child {
border-bottom-width: 1px;
&:first-child {
border-top-width: 0;
}
img {
max-width: 2.5em;
object-fit: cover;
object-position: center;
width: 2.5em;
height: 2.5em;
margin: 0 1em 0 0;
}
button {
.dashicon {
color: $color__link;
margin-left: auto;
}
}
}
.wc-products-list-card__search-wrapper--with-results +
.wc-products-list-card__results-wrapper {
.wc-products-list-card__item {
img {
outline: none;
}
button {
display: none;
}
}
}
.wc-products-list-card__search-no-results {
display: block;
margin: 1em 0 0;
}
.wc-products-list-card__search-no-selected {
display: block;
margin: -.75em 0 0;
}
.wc-products-list-card__results-wrapper {
position: relative;
overflow: hidden;

View File

@ -569,7 +569,7 @@ var ProductsBlockPreview = withAPIData(function (_ref) {
if ('specific' === display) {
query.include = display_setting.join(',');
query.orderby = 'include';
query.per_page = display_setting.length;
} else if ('category' === display) {
query.category = display_setting.join(',');
} else if ('attribute' === display && display_setting.length) {
@ -585,7 +585,7 @@ var ProductsBlockPreview = withAPIData(function (_ref) {
}
// @todo Add support for orderby by sales, rating, and price to the API.
if ('specific' !== display && ('title' === orderby || 'date' === orderby)) {
if ('title' === orderby || 'date' === orderby) {
query.orderby = orderby;
if ('title' === orderby) {
@ -822,35 +822,45 @@ var ProductsBlock = function (_React$Component5) {
max: wc_product_block_data.max_columns
});
// Orderby settings don't make sense for specific-selected products display.
var orderControl = null;
var orderControl = wp.element.createElement(SelectControl, {
key: 'query-panel-select',
label: __('Order Products By'),
value: orderby,
options: [{
label: __('Newness - newest first'),
value: 'date'
}, {
label: __('Price - low to high'),
value: 'price_asc'
}, {
label: __('Price - high to low'),
value: 'price_desc'
}, {
label: __('Rating - highest first'),
value: 'rating'
}, {
label: __('Sales - most first'),
value: 'popularity'
}, {
label: __('Title - alphabetical'),
value: 'title'
}],
onChange: function onChange(value) {
return setAttributes({ orderby: value });
}
});
// Row settings don't make sense for specific-selected products display.
var rowControl = null;
if ('specific' !== display) {
orderControl = wp.element.createElement(SelectControl, {
key: 'query-panel-select',
label: __('Order Products By'),
value: orderby,
options: [{
label: __('Newness - newest first'),
value: 'date'
}, {
label: __('Price - low to high'),
value: 'price_asc'
}, {
label: __('Price - high to low'),
value: 'price_desc'
}, {
label: __('Rating - highest first'),
value: 'rating'
}, {
label: __('Sales - most first'),
value: 'popularity'
}, {
label: __('Title - alphabetical'),
value: 'title'
}],
rowControl = wp.element.createElement(RangeControl, {
label: __('Rows'),
value: rows,
onChange: function onChange(value) {
return setAttributes({ orderby: value });
}
return setAttributes({ rows: value });
},
min: wc_product_block_data.min_rows,
max: wc_product_block_data.max_rows
});
}
@ -864,15 +874,7 @@ var ProductsBlock = function (_React$Component5) {
__('Layout')
),
columnControl,
wp.element.createElement(RangeControl, {
label: __('Rows'),
value: rows,
onChange: function onChange(value) {
return setAttributes({ rows: value });
},
min: wc_product_block_data.min_rows,
max: wc_product_block_data.max_rows
}),
rowControl,
orderControl
);
}
@ -1015,6 +1017,7 @@ var ProductsBlock = function (_React$Component5) {
};
return wp.element.createElement(ProductsBlockSettingsEditor, {
attributes: attributes,
selected_display: display,
selected_display_setting: display_setting,
update_display_callback: update_display_callback,
@ -1127,7 +1130,9 @@ registerBlockType('woocommerce/products', {
var shortcode_atts = new Map();
shortcode_atts.set('limit', rows * columns);
if ('specific' !== display) {
shortcode_atts.set('limit', rows * columns);
}
shortcode_atts.set('columns', columns);
if ('specific' === display) {
@ -1227,9 +1232,6 @@ var _wp$components = wp.components,
withAPIData = _wp$components.withAPIData,
Dropdown = _wp$components.Dropdown,
Dashicon = _wp$components.Dashicon;
var _ReactTransitionGroup = ReactTransitionGroup,
TransitionGroup = _ReactTransitionGroup.TransitionGroup,
CSSTransition = _ReactTransitionGroup.CSSTransition;
/**
* Product data cache.
@ -1269,11 +1271,17 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
_createClass(ProductsSpecificSelect, [{
key: 'addProduct',
value: function addProduct(id) {
key: 'addOrRemoveProduct',
value: function addOrRemoveProduct(id) {
var selectedProducts = this.state.selectedProducts;
selectedProducts.push(id);
if (!selectedProducts.includes(id)) {
selectedProducts.push(id);
} else {
selectedProducts = selectedProducts.filter(function (product) {
return product !== id;
});
}
this.setState({
selectedProducts: selectedProducts
@ -1288,52 +1296,6 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
this.props.update_display_setting_callback(selectedProducts.slice());
}
/**
* Remove a product from the list of selected products.
*
* @param id int Product ID.
*/
}, {
key: 'removeProduct',
value: function removeProduct(id) {
var oldProducts = this.state.selectedProducts;
var newProducts = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = oldProducts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var productId = _step.value;
if (productId !== id) {
newProducts.push(productId);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
this.setState({
selectedProducts: newProducts
});
this.props.update_display_setting_callback(newProducts);
}
/**
* Render the product specific select screen.
*/
@ -1345,12 +1307,13 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
'div',
{ className: 'wc-products-list-card wc-products-list-card--specific' },
wp.element.createElement(ProductsSpecificSearchField, {
addProductCallback: this.addProduct.bind(this),
addOrRemoveProductCallback: this.addOrRemoveProduct.bind(this),
selectedProducts: this.state.selectedProducts
}),
wp.element.createElement(ProductSpecificSelectedProducts, {
columns: this.props.attributes.columns,
productIds: this.state.selectedProducts,
removeProductCallback: this.removeProduct.bind(this)
addOrRemoveProduct: this.addOrRemoveProduct.bind(this)
})
);
}
@ -1376,12 +1339,14 @@ var ProductsSpecificSearchField = function (_React$Component2) {
var _this2 = _possibleConstructorReturn(this, (ProductsSpecificSearchField.__proto__ || Object.getPrototypeOf(ProductsSpecificSearchField)).call(this, props));
_this2.state = {
searchText: ''
searchText: '',
dropdownOpen: false
};
_this2.updateSearchResults = _this2.updateSearchResults.bind(_this2);
_this2.setWrapperRef = _this2.setWrapperRef.bind(_this2);
_this2.handleClickOutside = _this2.handleClickOutside.bind(_this2);
_this2.isDropdownOpen = _this2.isDropdownOpen.bind(_this2);
return _this2;
}
@ -1431,6 +1396,13 @@ var ProductsSpecificSearchField = function (_React$Component2) {
});
}
}
}, {
key: 'isDropdownOpen',
value: function isDropdownOpen(isOpen) {
this.setState({
dropdownOpen: !!isOpen
});
}
/**
* Event handler for updating results when text is typed into the input.
@ -1453,19 +1425,27 @@ var ProductsSpecificSearchField = function (_React$Component2) {
}, {
key: 'render',
value: function render() {
var divClass = 'wc-products-list-card__search-wrapper';
return wp.element.createElement(
'div',
{ className: 'wc-products-list-card__search-wrapper', ref: this.setWrapperRef },
wp.element.createElement('input', { type: 'search',
className: 'wc-products-list-card__search',
value: this.state.searchText,
placeholder: __('Search for products to display'),
onChange: this.updateSearchResults
}),
{ className: divClass + (this.state.dropdownOpen ? ' ' + divClass + '--with-results' : ''), ref: this.setWrapperRef },
wp.element.createElement(
'div',
{ className: 'wc-products-list-card__input-wrapper' },
wp.element.createElement(Dashicon, { icon: 'search' }),
wp.element.createElement('input', { type: 'search',
className: 'wc-products-list-card__search',
value: this.state.searchText,
placeholder: __('Search for products to display'),
onChange: this.updateSearchResults
})
),
wp.element.createElement(ProductSpecificSearchResults, {
searchString: this.state.searchText,
addProductCallback: this.props.addProductCallback,
selectedProducts: this.props.selectedProducts
addOrRemoveProductCallback: this.props.addOrRemoveProductCallback,
selectedProducts: this.props.selectedProducts,
isDropdownOpenCallback: this.isDropdownOpen
})
);
}
@ -1492,47 +1472,55 @@ var ProductSpecificSearchResults = withAPIData(function (props) {
};
})(function (_ref) {
var products = _ref.products,
addProductCallback = _ref.addProductCallback,
selectedProducts = _ref.selectedProducts;
addOrRemoveProductCallback = _ref.addOrRemoveProductCallback,
selectedProducts = _ref.selectedProducts,
isDropdownOpenCallback = _ref.isDropdownOpenCallback;
if (!products.data) {
return null;
}
if (0 === products.data.length) {
return __('No products found');
return wp.element.createElement(
'span',
{ className: 'wc-products-list-card__search-no-results' },
' ',
__('No products found'),
' '
);
}
// Populate the cache.
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator2 = products.data[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var product = _step2.value;
for (var _iterator = products.data[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var product = _step.value;
PRODUCT_DATA[product.id] = product;
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return wp.element.createElement(ProductSpecificSearchResultsDropdown, {
products: products.data,
addProductCallback: addProductCallback,
selectedProducts: selectedProducts
addOrRemoveProductCallback: addOrRemoveProductCallback,
selectedProducts: selectedProducts,
isDropdownOpenCallback: isDropdownOpenCallback
});
});
@ -1550,66 +1538,75 @@ var ProductSpecificSearchResultsDropdown = function (_React$Component3) {
}
_createClass(ProductSpecificSearchResultsDropdown, [{
key: 'render',
key: 'componentDidMount',
/**
* Set the state of the dropdown to open.
*/
value: function componentDidMount() {
this.props.isDropdownOpenCallback(true);
}
/**
* Set the state of the dropdown to closed.
*/
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.props.isDropdownOpenCallback(false);
}
/**
* Render dropdown.
*/
}, {
key: 'render',
value: function render() {
var _props = this.props,
products = _props.products,
addProductCallback = _props.addProductCallback,
addOrRemoveProductCallback = _props.addOrRemoveProductCallback,
selectedProducts = _props.selectedProducts;
var productElements = [];
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator3 = products[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var product = _step3.value;
for (var _iterator2 = products[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var product = _step2.value;
if (selectedProducts.includes(product.id)) {
continue;
}
productElements.push(wp.element.createElement(
CSSTransition,
{
key: product.slug,
classNames: 'wc-products-list-card__content--transition',
timeout: { exit: 700 }
},
wp.element.createElement(ProductSpecificSearchResultsDropdownElement, {
product: product,
addProductCallback: addProductCallback
})
));
productElements.push(wp.element.createElement(ProductSpecificSearchResultsDropdownElement, {
product: product,
addOrRemoveProductCallback: addOrRemoveProductCallback,
selected: selectedProducts.includes(product.id)
}));
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return wp.element.createElement(
'div',
{ role: 'menu', className: 'wc-products-list-card__search-results', 'aria-orientation': 'vertical', 'aria-label': '{ __( \'Products list\' ) }' },
{ role: 'menu', className: 'wc-products-list-card__search-results', 'aria-orientation': 'vertical', 'aria-label': __('Products list') },
wp.element.createElement(
TransitionGroup,
'div',
null,
productElements
)
@ -1636,10 +1633,6 @@ var ProductSpecificSearchResultsDropdownElement = function (_React$Component4) {
var _this4 = _possibleConstructorReturn(this, (ProductSpecificSearchResultsDropdownElement.__proto__ || Object.getPrototypeOf(ProductSpecificSearchResultsDropdownElement)).call(this, props));
_this4.state = {
clicked: false
};
_this4.handleClick = _this4.handleClick.bind(_this4);
return _this4;
}
@ -1652,8 +1645,7 @@ var ProductSpecificSearchResultsDropdownElement = function (_React$Component4) {
_createClass(ProductSpecificSearchResultsDropdownElement, [{
key: 'handleClick',
value: function handleClick() {
this.setState({ clicked: true });
this.props.addProductCallback(this.props.product.id);
this.props.addOrRemoveProductCallback(this.props.product.id);
}
/**
@ -1664,24 +1656,18 @@ var ProductSpecificSearchResultsDropdownElement = function (_React$Component4) {
key: 'render',
value: function render() {
var product = this.props.product;
var icon = this.props.selected ? wp.element.createElement(Dashicon, { icon: 'yes' }) : null;
return wp.element.createElement(
'div',
{ className: 'wc-products-list-card__content' },
{ className: 'wc-products-list-card__content' + (this.props.selected ? ' wc-products-list-card__content--added' : ''), onClick: this.handleClick },
wp.element.createElement('img', { src: product.images[0].src }),
wp.element.createElement(
'span',
{ className: 'wc-products-list-card__content-item-name' },
this.state.clicked ? __('Added') : product.name
product.name
),
wp.element.createElement(
'button',
{ type: 'button',
className: 'button-link',
id: 'product-' + product.id,
onClick: this.handleClick },
__('Add')
)
icon
);
}
}]);
@ -1695,7 +1681,6 @@ var ProductSpecificSearchResultsDropdownElement = function (_React$Component4) {
var ProductSpecificSelectedProducts = withAPIData(function (props) {
if (!props.productIds.length) {
return {
products: []
@ -1704,29 +1689,29 @@ var ProductSpecificSelectedProducts = withAPIData(function (props) {
// Determine which products are not already in the cache and only fetch uncached products.
var uncachedProducts = [];
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator4 = props.productIds[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var productId = _step4.value;
for (var _iterator3 = props.productIds[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var productId = _step3.value;
if (!PRODUCT_DATA.hasOwnProperty(productId)) {
uncachedProducts.push(productId);
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
@ -1737,41 +1722,38 @@ var ProductSpecificSelectedProducts = withAPIData(function (props) {
})(function (_ref2) {
var productIds = _ref2.productIds,
products = _ref2.products,
removeProductCallback = _ref2.removeProductCallback;
columns = _ref2.columns,
addOrRemoveProduct = _ref2.addOrRemoveProduct;
// Add new products to cache.
if (products.data) {
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator5 = products.data[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var product = _step5.value;
for (var _iterator4 = products.data[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var product = _step4.value;
PRODUCT_DATA[product.id] = product;
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
}
if (0 === productIds.length) {
return __('No products selected');
}
var productElements = [];
var _loop = function _loop(productId) {
@ -1801,7 +1783,7 @@ var ProductSpecificSelectedProducts = withAPIData(function (props) {
type: 'button',
id: 'product-' + productData.id,
onClick: function onClick() {
removeProductCallback(productData.id);
addOrRemoveProduct(productData.id);
} },
wp.element.createElement(Dashicon, { icon: 'no-alt' })
)
@ -1809,39 +1791,44 @@ var ProductSpecificSelectedProducts = withAPIData(function (props) {
));
};
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator6 = productIds[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var productId = _step6.value;
for (var _iterator5 = productIds[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var productId = _step5.value;
var _ret = _loop(productId);
if (_ret === 'continue') continue;
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
return wp.element.createElement(
'div',
{ className: 'wc-products-list-card__results-wrapper' },
{ className: 'wc-products-list-card__results-wrapper wc-products-list-card__results-wrapper--cols-' + columns },
wp.element.createElement(
'div',
{ role: 'menu', className: 'wc-products-list-card__results', 'aria-orientation': 'vertical', 'aria-label': '{ __( \'Products list\' ) }' },
{ role: 'menu', className: 'wc-products-list-card__results', 'aria-orientation': 'vertical', 'aria-label': __('Selected products') },
productElements.length > 0 && wp.element.createElement(
'h3',
null,
__('Selected products')
),
wp.element.createElement(
'ul',
null,
@ -2027,7 +2014,8 @@ var ProductCategoryFilter = function ProductCategoryFilter(_ref) {
return wp.element.createElement(
"div",
null,
{ className: "wc-products-list-card__input-wrapper" },
wp.element.createElement(Dashicon, { icon: "search" }),
wp.element.createElement("input", { className: "wc-products-list-card__search", type: "search", placeholder: __('Search for categories'), onChange: filterResults })
);
};
@ -2277,7 +2265,8 @@ var __ = wp.i18n.__;
var _wp$components = wp.components,
Toolbar = _wp$components.Toolbar,
withAPIData = _wp$components.withAPIData,
Dropdown = _wp$components.Dropdown;
Dropdown = _wp$components.Dropdown,
Dashicon = _wp$components.Dashicon;
/**
* Attribute data cache.
@ -2481,7 +2470,8 @@ var ProductsAttributeSelect = exports.ProductsAttributeSelect = function (_React
var ProductAttributeFilter = function ProductAttributeFilter(props) {
return wp.element.createElement(
'div',
null,
{ className: 'wc-products-list-card__input-wrapper' },
wp.element.createElement(Dashicon, { icon: 'search' }),
wp.element.createElement('input', { className: 'wc-products-list-card__search', type: 'search', placeholder: __('Search for attributes'), onChange: props.updateFilter })
);
};

View File

@ -337,7 +337,7 @@ const ProductsBlockPreview = withAPIData( ( { attributes } ) => {
if ( 'specific' === display ) {
query.include = display_setting.join( ',' );
query.orderby = 'include';
query.per_page = display_setting.length;
} else if ( 'category' === display ) {
query.category = display_setting.join( ',' );
} else if ( 'attribute' === display && display_setting.length ) {
@ -353,7 +353,7 @@ const ProductsBlockPreview = withAPIData( ( { attributes } ) => {
}
// @todo Add support for orderby by sales, rating, and price to the API.
if ( 'specific' !== display && ( 'title' === orderby || 'date' === orderby ) ) {
if ( 'title' === orderby || 'date' === orderby ) {
query.orderby = orderby;
if ( 'title' === orderby ) {
@ -502,41 +502,51 @@ class ProductsBlock extends React.Component {
/>
);
// Orderby settings don't make sense for specific-selected products display.
let orderControl = null;
let orderControl = (
<SelectControl
key="query-panel-select"
label={ __( 'Order Products By' ) }
value={ orderby }
options={ [
{
label: __( 'Newness - newest first' ),
value: 'date',
},
{
label: __( 'Price - low to high' ),
value: 'price_asc',
},
{
label: __( 'Price - high to low' ),
value: 'price_desc',
},
{
label: __( 'Rating - highest first' ),
value: 'rating',
},
{
label: __( 'Sales - most first' ),
value: 'popularity',
},
{
label: __( 'Title - alphabetical' ),
value: 'title',
},
] }
onChange={ ( value ) => setAttributes( { orderby: value } ) }
/>
);
// Row settings don't make sense for specific-selected products display.
let rowControl = null;
if ( 'specific' !== display ) {
orderControl = (
<SelectControl
key="query-panel-select"
label={ __( 'Order Products By' ) }
value={ orderby }
options={ [
{
label: __( 'Newness - newest first' ),
value: 'date',
},
{
label: __( 'Price - low to high' ),
value: 'price_asc',
},
{
label: __( 'Price - high to low' ),
value: 'price_desc',
},
{
label: __( 'Rating - highest first' ),
value: 'rating',
},
{
label: __( 'Sales - most first' ),
value: 'popularity',
},
{
label: __( 'Title - alphabetical' ),
value: 'title',
},
] }
onChange={ ( value ) => setAttributes( { orderby: value } ) }
rowControl = (
<RangeControl
label={ __( 'Rows' ) }
value={ rows }
onChange={ ( value ) => setAttributes( { rows: value } ) }
min={ wc_product_block_data.min_rows }
max={ wc_product_block_data.max_rows }
/>
);
}
@ -546,13 +556,7 @@ class ProductsBlock extends React.Component {
{ this.getBlockDescription() }
<h3>{ __( 'Layout' ) }</h3>
{ columnControl }
<RangeControl
label={ __( 'Rows' ) }
value={ rows }
onChange={ ( value ) => setAttributes( { rows: value } ) }
min={ wc_product_block_data.min_rows }
max={ wc_product_block_data.max_rows }
/>
{ rowControl }
{ orderControl }
</InspectorControls>
);
@ -662,6 +666,7 @@ class ProductsBlock extends React.Component {
return (
<ProductsBlockSettingsEditor
attributes={ attributes }
selected_display={ display }
selected_display_setting={ display_setting }
update_display_callback={ update_display_callback }
@ -759,7 +764,9 @@ registerBlockType( 'woocommerce/products', {
const { rows, columns, display, display_setting, orderby } = props.attributes;
let shortcode_atts = new Map();
shortcode_atts.set( 'limit', rows * columns );
if ( 'specific' !== display ) {
shortcode_atts.set( 'limit', rows * columns );
}
shortcode_atts.set( 'columns', columns );
if ( 'specific' === display ) {

View File

@ -1,5 +1,5 @@
const { __ } = wp.i18n;
const { Toolbar, withAPIData, Dropdown } = wp.components;
const { Toolbar, withAPIData, Dropdown, Dashicon } = wp.components;
/**
* Attribute data cache.
@ -154,7 +154,8 @@ export class ProductsAttributeSelect extends React.Component {
*/
const ProductAttributeFilter = ( props ) => {
return (
<div>
<div className="wc-products-list-card__input-wrapper">
<Dashicon icon="search" />
<input className="wc-products-list-card__search" type="search" placeholder={ __( 'Search for attributes' ) } onChange={ props.updateFilter } />
</div>
);

View File

@ -114,7 +114,8 @@ export class ProductsCategorySelect extends React.Component {
*/
const ProductCategoryFilter = ( { filterResults } ) => {
return (
<div>
<div className="wc-products-list-card__input-wrapper">
<Dashicon icon="search" />
<input className="wc-products-list-card__search" type="search" placeholder={ __( 'Search for categories' ) } onChange={ filterResults } />
</div>
);

View File

@ -1,6 +1,5 @@
const { __ } = wp.i18n;
const { Toolbar, withAPIData, Dropdown, Dashicon } = wp.components;
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
/**
* Product data cache.
@ -31,10 +30,14 @@ export class ProductsSpecificSelect extends React.Component {
*
* @param id int Product ID.
*/
addProduct( id ) {
addOrRemoveProduct( id ) {
let selectedProducts = this.state.selectedProducts;
selectedProducts.push( id );
if ( ! selectedProducts.includes( id ) ) {
selectedProducts.push( id );
} else {
selectedProducts = selectedProducts.filter( product => product !== id );
}
this.setState( {
selectedProducts: selectedProducts
@ -49,28 +52,6 @@ export class ProductsSpecificSelect extends React.Component {
this.props.update_display_setting_callback( selectedProducts.slice() );
}
/**
* Remove a product from the list of selected products.
*
* @param id int Product ID.
*/
removeProduct( id ) {
let oldProducts = this.state.selectedProducts;
let newProducts = [];
for ( let productId of oldProducts ) {
if ( productId !== id ) {
newProducts.push( productId );
}
}
this.setState( {
selectedProducts: newProducts
} );
this.props.update_display_setting_callback( newProducts );
}
/**
* Render the product specific select screen.
*/
@ -78,12 +59,13 @@ export class ProductsSpecificSelect extends React.Component {
return (
<div className="wc-products-list-card wc-products-list-card--specific">
<ProductsSpecificSearchField
addProductCallback={ this.addProduct.bind( this ) }
addOrRemoveProductCallback={ this.addOrRemoveProduct.bind( this ) }
selectedProducts={ this.state.selectedProducts }
/>
<ProductSpecificSelectedProducts
columns={ this.props.attributes.columns }
productIds={ this.state.selectedProducts }
removeProductCallback={ this.removeProduct.bind( this ) }
addOrRemoveProduct={ this.addOrRemoveProduct.bind( this ) }
/>
</div>
);
@ -103,11 +85,13 @@ class ProductsSpecificSearchField extends React.Component {
this.state = {
searchText: '',
dropdownOpen: false,
}
this.updateSearchResults = this.updateSearchResults.bind( this );
this.setWrapperRef = this.setWrapperRef.bind( this );
this.handleClickOutside = this.handleClickOutside.bind( this );
this.isDropdownOpen = this.isDropdownOpen.bind( this );
}
/**
@ -144,6 +128,12 @@ class ProductsSpecificSearchField extends React.Component {
}
}
isDropdownOpen( isOpen ) {
this.setState( {
dropdownOpen: !! isOpen,
} );
}
/**
* Event handler for updating results when text is typed into the input.
*
@ -159,18 +149,24 @@ class ProductsSpecificSearchField extends React.Component {
* Render the product search UI.
*/
render() {
const divClass = 'wc-products-list-card__search-wrapper';
return (
<div className="wc-products-list-card__search-wrapper" ref={ this.setWrapperRef }>
<input type="search"
className="wc-products-list-card__search"
value={ this.state.searchText }
placeholder={ __( 'Search for products to display' ) }
onChange={ this.updateSearchResults }
/>
<div className={ divClass + ( this.state.dropdownOpen ? ' ' + divClass + '--with-results' : '' ) } ref={ this.setWrapperRef }>
<div className="wc-products-list-card__input-wrapper">
<Dashicon icon="search" />
<input type="search"
className="wc-products-list-card__search"
value={ this.state.searchText }
placeholder={ __( 'Search for products to display' ) }
onChange={ this.updateSearchResults }
/>
</div>
<ProductSpecificSearchResults
searchString={ this.state.searchText }
addProductCallback={ this.props.addProductCallback }
addOrRemoveProductCallback={ this.props.addOrRemoveProductCallback }
selectedProducts={ this.props.selectedProducts }
isDropdownOpenCallback={ this.isDropdownOpen }
/>
</div>
);
@ -191,13 +187,13 @@ const ProductSpecificSearchResults = withAPIData( ( props ) => {
return {
products: '/wc/v2/products?per_page=10&search=' + props.searchString,
};
} )( ( { products, addProductCallback, selectedProducts } ) => {
} )( ( { products, addOrRemoveProductCallback, selectedProducts, isDropdownOpenCallback } ) => {
if ( ! products.data ) {
return null;
}
if ( 0 === products.data.length ) {
return __( 'No products found' );
return <span className="wc-products-list-card__search-no-results"> { __( 'No products found' ) } </span>;
}
// Populate the cache.
@ -207,8 +203,9 @@ const ProductSpecificSearchResults = withAPIData( ( props ) => {
return <ProductSpecificSearchResultsDropdown
products={ products.data }
addProductCallback={ addProductCallback }
addOrRemoveProductCallback={ addOrRemoveProductCallback }
selectedProducts={ selectedProducts }
isDropdownOpenCallback={ isDropdownOpenCallback }
/>
}
);
@ -218,39 +215,43 @@ const ProductSpecificSearchResults = withAPIData( ( props ) => {
*/
class ProductSpecificSearchResultsDropdown extends React.Component {
/**
* Set the state of the dropdown to open.
*/
componentDidMount() {
this.props.isDropdownOpenCallback( true );
}
/**
* Set the state of the dropdown to closed.
*/
componentWillUnmount() {
this.props.isDropdownOpenCallback( false );
}
/**
* Render dropdown.
*/
render() {
const { products, addProductCallback, selectedProducts } = this.props;
const { products, addOrRemoveProductCallback, selectedProducts } = this.props;
let productElements = [];
for ( let product of products ) {
if ( selectedProducts.includes( product.id ) ) {
continue;
}
productElements.push(
<CSSTransition
key={ product.slug }
classNames="wc-products-list-card__content--transition"
timeout={ { exit: 700 } }
>
<ProductSpecificSearchResultsDropdownElement
product={product}
addProductCallback={ addProductCallback }
/>
</CSSTransition>
<ProductSpecificSearchResultsDropdownElement
product={product}
addOrRemoveProductCallback={ addOrRemoveProductCallback }
selected={ selectedProducts.includes( product.id ) }
/>
);
}
return (
<div role="menu" className="wc-products-list-card__search-results" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<TransitionGroup>
{ productElements }
</TransitionGroup>
<div role="menu" className="wc-products-list-card__search-results" aria-orientation="vertical" aria-label={ __( 'Products list' ) }>
<div>
{ productElements }
</div>
</div>
);
}
@ -267,10 +268,6 @@ class ProductSpecificSearchResultsDropdownElement extends React.Component {
constructor( props ) {
super( props );
this.state = {
clicked: false,
}
this.handleClick = this.handleClick.bind( this );
}
@ -278,8 +275,7 @@ class ProductSpecificSearchResultsDropdownElement extends React.Component {
* Add product to main list and change UI to show it was added.
*/
handleClick() {
this.setState( { clicked: true } );
this.props.addProductCallback( this.props.product.id );
this.props.addOrRemoveProductCallback( this.props.product.id );
}
/**
@ -287,17 +283,13 @@ class ProductSpecificSearchResultsDropdownElement extends React.Component {
*/
render() {
const product = this.props.product;
let icon = this.props.selected ? <Dashicon icon="yes" /> : null;
return (
<div className="wc-products-list-card__content">
<div className={ 'wc-products-list-card__content' + ( this.props.selected ? ' wc-products-list-card__content--added' : '' ) } onClick={ this.handleClick }>
<img src={ product.images[0].src } />
<span className="wc-products-list-card__content-item-name">{ this.state.clicked ? __( 'Added' ) : product.name }</span>
<button type="button"
className="button-link"
id={ 'product-' + product.id }
onClick={ this.handleClick } >
{ __( 'Add' ) }
</button>
<span className="wc-products-list-card__content-item-name">{ product.name }</span>
{ icon }
</div>
);
}
@ -307,7 +299,6 @@ class ProductSpecificSearchResultsDropdownElement extends React.Component {
* List preview of selected products.
*/
const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
if ( ! props.productIds.length ) {
return {
products: []
@ -325,7 +316,7 @@ const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
return {
products: uncachedProducts.length ? '/wc/v2/products?include=' + uncachedProducts.join( ',' ) : []
};
} )( ( { productIds, products, removeProductCallback } ) => {
} )( ( { productIds, products, columns, addOrRemoveProduct } ) => {
// Add new products to cache.
if ( products.data ) {
@ -334,10 +325,6 @@ const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
}
}
if ( 0 === productIds.length ) {
return __( 'No products selected' );
}
const productElements = [];
for ( const productId of productIds ) {
@ -357,8 +344,8 @@ const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
<button
type="button"
id={ 'product-' + productData.id }
onClick={ function() { removeProductCallback( productData.id ) } } >
<Dashicon icon={ 'no-alt' } />
onClick={ function() { addOrRemoveProduct( productData.id ) } } >
<Dashicon icon="no-alt" />
</button>
</div>
</li>
@ -366,8 +353,11 @@ const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
}
return (
<div className="wc-products-list-card__results-wrapper">
<div role="menu" className="wc-products-list-card__results" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<div className={ 'wc-products-list-card__results-wrapper wc-products-list-card__results-wrapper--cols-' + columns }>
<div role="menu" className="wc-products-list-card__results" aria-orientation="vertical" aria-label={ __( 'Selected products' ) }>
{ productElements.length > 0 && <h3>{ __( 'Selected products' ) }</h3> }
<ul>
{ productElements }
</ul>