Merge pull request woocommerce/woocommerce-blocks#24 from woocommerce/product-search

Products search and select
This commit is contained in:
Claudiu Lodromanean 2018-02-16 11:43:00 -08:00 committed by GitHub
commit e681f39609
7 changed files with 387 additions and 145 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

@ -166,15 +166,6 @@
}
}
.product-specific-select {
.add-new {
background: #ddd;
display: inline-block;
margin-bottom: 20px;
padding: 60px 30px;
}
}
.product-category-select {
margin: 0 auto;
position: relative;
@ -222,6 +213,68 @@
}
}
.product-specific-select {
margin: 0 auto;
position: relative;
input[type=text] {
width: 100%;
padding-right: 3em;
}
.cancel {
position: absolute;
top: 0.5em;
right: 1em;
cursor: pointer;
}
button {
display: flex;
justify-content: space-between;
width: 100%;
background-color: #FFFFFF;
border: 1px solid #e6eaee;
text-align: left;
padding: .5em;
img {
padding-top: .5em;
}
a {
line-height: 3em;
margin-right: .5em;
}
.product-name {
line-height: 3em;
width: 300px;
white-space: nowrap;
overflow: hidden;
}
}
ul {
list-style-type: none;
}
li {
margin: 0;
}
.product-search-results {
position: absolute;
width: 100%;
z-index: 999;
}
.selected-products {
margin-top: 1em;
margin-bottom: 1em;
}
}
.block-footer {
margin: 0 -1em;
padding-top: 1em;
@ -237,6 +290,11 @@
@media only screen and (min-width: 700px) {
.wc-product-display-settings {
.product-specific-select {
width: 400px;
}
.product-category-select {
width: 400px;

View File

@ -293,12 +293,12 @@ var ProductsBlockSettingsEditor = function (_React$Component3) {
var is_group = 'undefined' !== PRODUCTS_BLOCK_DISPLAY_SETTINGS[value].group_container && PRODUCTS_BLOCK_DISPLAY_SETTINGS[value].group_container;
if (is_group) {
// If a group and it has not been expanded, expand it.
// If the group has not been expanded, expand it.
new_state = {
menu_visible: true,
expanded_group: value
// If a group and it has already been expanded, collapse it.
// If the group has already been expanded, collapse it.
};if (this.state.expanded_group === PRODUCTS_BLOCK_DISPLAY_SETTINGS[value].group_container) {
new_state.expanded_group = '';
}
@ -323,7 +323,7 @@ var ProductsBlockSettingsEditor = function (_React$Component3) {
var extra_settings = null;
if ('specific' === this.state.display) {
extra_settings = wp.element.createElement(_specificSelect.ProductsSpecificSelect, null);
extra_settings = wp.element.createElement(_specificSelect.ProductsSpecificSelect, this.props);
} else if ('category' === this.state.display) {
extra_settings = wp.element.createElement(_categorySelect.ProductsCategorySelect, this.props);
} else if ('attribute' === this.state.display) {
@ -933,21 +933,61 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
var _this = _possibleConstructorReturn(this, (ProductsSpecificSelect.__proto__ || Object.getPrototypeOf(ProductsSpecificSelect)).call(this, props));
_this.state = {
selectedProducts: props.selected_display_setting
selectedProducts: props.selected_display_setting || []
};
return _this;
}
_createClass(ProductsSpecificSelect, [{
key: "selectProduct",
value: function selectProduct(evt) {
evt.preventDefault();
var selectProduct = this.state.selectProduct;
key: "addProduct",
value: function addProduct(id) {
var selectedProducts = this.state.selectedProducts;
selectedProducts.push(id);
this.setState({
selectProduct: selectProduct
selectedProducts: selectedProducts
});
this.props.update_display_setting_callback(selectedProducts);
}
}, {
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);
}
}, {
key: "render",
@ -955,32 +995,8 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
return wp.element.createElement(
"div",
{ className: "product-specific-select" },
wp.element.createElement(
"div",
{ className: "add-new" },
wp.element.createElement(Dropdown, {
className: "my-container-class-name",
contentClassName: "my-popover-content-classname",
position: "bottom right",
renderToggle: function renderToggle(_ref) {
var isOpen = _ref.isOpen,
onToggle = _ref.onToggle;
return wp.element.createElement(
"button",
{ className: "button button-large", onClick: onToggle, "aria-expanded": isOpen },
__('Add product')
);
},
renderContent: function renderContent() {
return wp.element.createElement(
"div",
null,
wp.element.createElement(ProductSpecifcSearch, null)
);
}
})
),
wp.element.createElement(ProductsSpecificList, { selectedProducts: this.state.selectedProducts })
wp.element.createElement(ProductsSpecificSearchField, { addProductCallback: this.addProduct.bind(this) }),
wp.element.createElement(ProductSpecificSelectedProducts, { products: this.state.selectedProducts, removeProductCallback: this.removeProduct.bind(this) })
);
}
}]);
@ -988,28 +1004,152 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
return ProductsSpecificSelect;
}(React.Component);
var ProductSpecifcSearch = withAPIData(function (props) {
/**
* Product search area
*/
var ProductsSpecificSearchField = function (_React$Component2) {
_inherits(ProductsSpecificSearchField, _React$Component2);
function ProductsSpecificSearchField(props) {
_classCallCheck(this, ProductsSpecificSearchField);
var _this2 = _possibleConstructorReturn(this, (ProductsSpecificSearchField.__proto__ || Object.getPrototypeOf(ProductsSpecificSearchField)).call(this, props));
_this2.state = {
searchText: ''
};
_this2.updateSearchResults = _this2.updateSearchResults.bind(_this2);
return _this2;
}
_createClass(ProductsSpecificSearchField, [{
key: "updateSearchResults",
value: function updateSearchResults(evt) {
this.setState({
searchText: evt.target.value
});
}
}, {
key: "render",
value: function render() {
var _this3 = this;
return wp.element.createElement(
"div",
{ className: "product-search" },
wp.element.createElement("input", { type: "text", value: this.state.searchText, placeholder: __('Search for products to display'), onChange: this.updateSearchResults }),
wp.element.createElement(
"span",
{ className: "cancel", onClick: function onClick() {
_this3.setState({ searchText: '' });
} },
"X"
),
wp.element.createElement(ProductSpecificSearchResults, { searchString: this.state.searchText, addProductCallback: this.props.addProductCallback })
);
}
}]);
return ProductsSpecificSearchField;
}(React.Component);
/**
* Product search results based on the text entered into the textbox.
*/
var ProductSpecificSearchResults = withAPIData(function (props) {
if (!props.searchString.length) {
return {
products: []
};
}
return {
products: '/wc/v2/products?per_page=10'
products: '/wc/v2/products?per_page=10&search=' + props.searchString
};
})(function (_ref2) {
var products = _ref2.products;
})(function (_ref) {
var products = _ref.products,
addProductCallback = _ref.addProductCallback;
if (!products.data) {
return __('Loading');
return null;
}
if (0 === products.data.length) {
return __('No products found');
}
var ProductsList = function ProductsList(_ref3) {
var products = _ref3.products;
return products.length > 0 && wp.element.createElement(
return wp.element.createElement(
"div",
{ role: "menu", className: "product-search-results", "aria-orientation": "vertical", "aria-label": "{ __( 'Products list' ) }" },
wp.element.createElement(
"ul",
null,
products.map(function (product) {
products.data.map(function (product) {
return wp.element.createElement(
"li",
null,
wp.element.createElement(
"button",
{ type: "button", className: "components-button", id: 'product-' + product.id, onClick: function onClick() {
addProductCallback(product.id);
} },
wp.element.createElement("img", { src: product.images[0].src, width: "30px" }),
wp.element.createElement(
"span",
{ className: "product-name" },
product.name
),
wp.element.createElement(
"a",
null,
__('Add')
)
)
);
})
)
);
});
/**
* List preview of selected products.
*/
var ProductSpecificSelectedProducts = withAPIData(function (props) {
if (!props.products.length) {
return {
products: []
};
}
return {
products: '/wc/v2/products?include=' + props.products.join(',')
};
})(function (_ref2) {
var products = _ref2.products,
removeProductCallback = _ref2.removeProductCallback;
if (!products.data) {
return null;
}
if (0 === products.data.length) {
return __('No products selected');
}
return wp.element.createElement(
"div",
{ role: "menu", className: "selected-products", "aria-orientation": "vertical", "aria-label": "{ __( 'Products list' ) }" },
wp.element.createElement(
"ul",
null,
products.data.map(function (product) {
return wp.element.createElement(
"li",
null,
@ -1017,42 +1157,25 @@ var ProductSpecifcSearch = withAPIData(function (props) {
"button",
{ type: "button", className: "components-button", id: 'product-' + product.id },
wp.element.createElement("img", { src: product.images[0].src, width: "30px" }),
" ",
product.name
wp.element.createElement(
"span",
{ className: "product-name" },
product.name
),
wp.element.createElement(
"a",
{ onClick: function onClick() {
removeProductCallback(product.id);
} },
__('X')
)
)
);
})
);
};
var productsData = products.data;
return wp.element.createElement(
"div",
{ role: "menu", "aria-orientation": "vertical", "aria-label": "{ __( 'Products list' ) }" },
wp.element.createElement(ProductsList, { products: productsData })
)
);
});
var ProductsSpecificList = function ProductsSpecificList(_ref4) {
var selectedProducts = _ref4.selectedProducts;
if (!selectedProducts || 0 === selectedProducts.length) {
return __('No products selected found');
}
var classes = "wc-products-block-preview";
var attributes = {};
return wp.element.createElement(
"div",
{ className: classes },
selectedProducts.data.map(function (product) {
return wp.element.createElement(ProductPreview, { product: product, attributes: attributes });
})
);
};
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

View File

@ -169,7 +169,7 @@ class ProductsBlockSettingsEditor extends React.Component {
render() {
let extra_settings = null;
if ( 'specific' === this.state.display ) {
extra_settings = <ProductsSpecificSelect />;
extra_settings = <ProductsSpecificSelect { ...this.props } />;
} else if ( 'category' === this.state.display ) {
extra_settings = <ProductsCategorySelect { ...this.props } />;
} else if ( 'attribute' === this.state.display ) {

View File

@ -15,96 +15,158 @@ export class ProductsSpecificSelect extends React.Component {
super( props );
this.state = {
selectedProducts: props.selected_display_setting,
selectedProducts: props.selected_display_setting || [],
}
}
selectProduct( evt ) {
evt.preventDefault();
let selectProduct = this.state.selectProduct;
addProduct( id ) {
let selectedProducts = this.state.selectedProducts;
selectedProducts.push( id );
this.setState( {
selectProduct: selectProduct
selectedProducts: selectedProducts
} );
this.props.update_display_setting_callback( selectedProducts );
}
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() {
return (
<div className="product-specific-select">
<div className="add-new">
<Dropdown
className="my-container-class-name"
contentClassName="my-popover-content-classname"
position="bottom right"
renderToggle={ ( { isOpen, onToggle } ) => (
<button className="button button-large" onClick={ onToggle } aria-expanded={ isOpen }>
{ __( 'Add product' ) }
</button>
) }
renderContent={ () => (
<div>
<ProductSpecifcSearch />
</div>
) }
/>
</div>
<ProductsSpecificList selectedProducts={ this.state.selectedProducts } />
<ProductsSpecificSearchField addProductCallback={ this.addProduct.bind( this ) } />
<ProductSpecificSelectedProducts products={ this.state.selectedProducts } removeProductCallback={ this.removeProduct.bind( this ) } />
</div>
);
}
}
const ProductSpecifcSearch = withAPIData( ( props ) => {
/**
* Product search area
*/
class ProductsSpecificSearchField extends React.Component {
constructor( props ) {
super( props );
this.state = {
searchText: '',
}
this.updateSearchResults = this.updateSearchResults.bind( this );
}
updateSearchResults( evt ) {
this.setState( {
searchText: evt.target.value,
} );
}
render() {
return (
<div className="product-search">
<input type="text" value={ this.state.searchText } placeholder={ __( 'Search for products to display' ) } onChange={ this.updateSearchResults } />
<span className="cancel" onClick={ () => { this.setState( { searchText: '' } ); } } >X</span>
<ProductSpecificSearchResults searchString={ this.state.searchText } addProductCallback={ this.props.addProductCallback } />
</div>
);
}
}
/**
* Product search results based on the text entered into the textbox.
*/
const ProductSpecificSearchResults = withAPIData( ( props ) => {
if ( ! props.searchString.length ) {
return {
products: []
};
}
return {
products: '/wc/v2/products?per_page=10'
products: '/wc/v2/products?per_page=10&search=' + props.searchString
};
} )( ( { products } ) => {
} )( ( { products, addProductCallback } ) => {
if ( ! products.data ) {
return __( 'Loading' );
return null;
}
if ( 0 === products.data.length ) {
return __( 'No products found' );
}
const ProductsList = ( { products } ) => {
return ( products.length > 0 ) && (
return (
<div role="menu" className="product-search-results" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<ul>
{ products.map( ( product ) => (
{ products.data.map( ( product ) => (
<li>
<button type="button" className="components-button" id={ 'product-' + product.id }>
<img src={ product.images[0].src } width="30px" /> { product.name }
<button type="button" className="components-button" id={ 'product-' + product.id } onClick={ function() { addProductCallback( product.id ) } } >
<img src={ product.images[0].src } width="30px" />
<span className="product-name">{ product.name }</span>
<a>{ __( 'Add' ) }</a>
</button>
</li>
) ) }
</ul>
);
};
let productsData = products.data;
return (
<div role="menu" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<ProductsList products={ productsData } />
</div>
);
}
);
const ProductsSpecificList = ( { selectedProducts } ) => {
if ( ! selectedProducts || 0 === selectedProducts.length ) {
return __( 'No products selected found' );
/**
* List preview of selected products.
*/
const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
if ( ! props.products.length ) {
return {
products: []
};
}
return {
products: '/wc/v2/products?include=' + props.products.join( ',' )
};
} )( ( { products, removeProductCallback } ) => {
if ( ! products.data ) {
return null;
}
if ( 0 === products.data.length ) {
return __( 'No products selected' );
}
return (
<div role="menu" className="selected-products" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<ul>
{ products.data.map( ( product ) => (
<li>
<button type="button" className="components-button" id={ 'product-' + product.id } >
<img src={ product.images[0].src } width="30px" />
<span className="product-name">{ product.name }</span>
<a onClick={ function() { removeProductCallback( product.id ) } } >{ __( 'X' ) }</a>
</button>
</li>
) ) }
</ul>
</div>
);
}
const classes = "wc-products-block-preview";
const attributes = {};
return (
<div className={ classes }>
{ selectedProducts.data.map( ( product ) => (
<ProductPreview product={ product } attributes={ attributes } />
) ) }
</div>
);
}
);

View File

@ -3,7 +3,6 @@
*/
var GutenbergBlocksConfig = {
entry: {
// 'products-block-specific-select': './assets/js/products-block-specific-select.jsx',
'products-block': './assets/js/products-block.jsx',
// 'next-block-name': './assets/js/gutenberg/some-other-block.jsx', <-- How to add more gutenblocks to this.
},