Create a reusable search + list control for category selection (https://github.com/woocommerce/woocommerce-blocks/pull/166)
* Add new components for ProductCategoryControl, using a reusable SearchListControl * Add sass variables from wc-admin * Finish styling * Save selected categories as selected for the block * Style sidebar version of control * Filter the categories list, highlight search term in result * Filter out selected items in the filter list function * Add spacing in the placeholder, remove unnecessary stylesheet * Add a more descriptive label for screen readers * Remove category references from list item options * Switch to a configurable object of messages, so SearchListControl can be more customizable * Add screen-reader message for toggling “done” and moving into preview * Remove call to getProductCategoryControl The component is simple enough to just drop in now :) * Add documentation for all props * Add padding to placeholder * Rename fallbackrenderItem to defaultRenderItem * Add a variable to save the selected count * Add `isDestructive` to make link red * Update item style * Add a hover/focus background color
This commit is contained in:
parent
fbdc17d532
commit
4ac52b3cf3
|
@ -34,3 +34,16 @@
|
|||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide an element from sighted users, but availble to screen reader users.
|
||||
@mixin visually-hidden() {
|
||||
border: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
/* Many screen reader and browser combinations announce broken words as they would appear visually. */
|
||||
word-wrap: normal !important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
$gap-largest: 40px;
|
||||
$gap-larger: 36px;
|
||||
$gap-large: 24px;
|
||||
$gap: 16px;
|
||||
$gap-small: 12px;
|
||||
$gap-smaller: 8px;
|
||||
$gap-smallest: 4px;
|
|
@ -4,15 +4,28 @@
|
|||
|
||||
.wc-block-products-category {
|
||||
overflow: hidden;
|
||||
|
||||
&.components-placeholder {
|
||||
padding: 2em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-products-category__selection {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
border-top: 1px solid $core-grey-light-500;
|
||||
}
|
||||
|
||||
.components-spinner {
|
||||
float: none;
|
||||
.components-panel {
|
||||
.woocommerce-search-list {
|
||||
padding: 0;
|
||||
}
|
||||
.woocommerce-search-list__selected {
|
||||
margin: 0 0 $gap;
|
||||
padding: 0;
|
||||
border-top: none;
|
||||
}
|
||||
.woocommerce-search-list__search {
|
||||
margin: 0 0 $gap;
|
||||
padding: 0;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* 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 { MenuItem } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import SearchListControl from '../search-list-control';
|
||||
|
||||
class ProductCategoryControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.state = {
|
||||
list: [],
|
||||
};
|
||||
this.renderItem = this.renderItem.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
apiFetch( {
|
||||
path: addQueryArgs( '/wc/v3/products/categories', { per_page: -1 } ),
|
||||
} )
|
||||
.then( ( list ) => {
|
||||
this.setState( { list } );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { list: [] } );
|
||||
} );
|
||||
}
|
||||
|
||||
renderItem( { getHighlightedName, item, onSelect, search } ) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ item.id }
|
||||
className="woocommerce-product-categories__item woocommerce-search-list__item"
|
||||
onClick={ onSelect( item ) }
|
||||
aria-label={ sprintf(
|
||||
_n(
|
||||
'%s, has %d product',
|
||||
'%s, has %d products',
|
||||
item.count,
|
||||
'woocommerce'
|
||||
),
|
||||
item.name,
|
||||
item.count
|
||||
) }
|
||||
>
|
||||
<span
|
||||
className="woocommerce-product-categories__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
<span className="woocommerce-product-categories__item-count">
|
||||
{ item.count }
|
||||
</span>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { list } = this.state;
|
||||
const { selected, onChange } = this.props;
|
||||
|
||||
const messages = {
|
||||
clear: __( 'Clear all product categories', 'woocommerce' ),
|
||||
list: __( 'Product Categories', 'woocommerce' ),
|
||||
search: __( 'Search for product categories', 'woocommerce' ),
|
||||
selected: ( n ) =>
|
||||
sprintf(
|
||||
_n(
|
||||
'%d category selected',
|
||||
'%d categories selected',
|
||||
n,
|
||||
'woocommerce'
|
||||
),
|
||||
n
|
||||
),
|
||||
updated: __( 'Category search results updated.', 'woocommerce' ),
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchListControl
|
||||
className="woocommerce-product-categories"
|
||||
list={ list }
|
||||
selected={ selected.map( ( id ) => find( list, { id } ) ).filter( Boolean ) }
|
||||
onChange={ onChange }
|
||||
renderItem={ this.renderItem }
|
||||
messages={ messages }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProductCategoryControl.propTypes = {
|
||||
/**
|
||||
* Callback to update the selected product categories.
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
/**
|
||||
* The list of currently selected category IDs.
|
||||
*/
|
||||
selected: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default ProductCategoryControl;
|
|
@ -0,0 +1,21 @@
|
|||
.woocommerce-product-categories {
|
||||
.woocommerce-product-categories__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.woocommerce-product-categories__item-count {
|
||||
flex: 0;
|
||||
padding: $gap-smallest/2 $gap-smaller;
|
||||
border: 1px solid $core-grey-light-500;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.4;
|
||||
color: $core-grey-dark-300;
|
||||
background: $white;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
Button,
|
||||
MenuItem,
|
||||
MenuGroup,
|
||||
TextControl,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose, withInstanceId, withState } from '@wordpress/compose';
|
||||
import { escapeRegExp, findIndex } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tag } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const defaultMessages = {
|
||||
clear: __( 'Clear all selected items', 'woocommerce' ),
|
||||
list: __( 'Results', 'woocommerce' ),
|
||||
noResults: __( 'No results for %s', 'woocommerce' ),
|
||||
search: __( 'Search for items', 'woocommerce' ),
|
||||
selected: ( n ) =>
|
||||
sprintf( _n( '%d item selected', '%d items selected', n, 'woocommerce' ), n ),
|
||||
updated: __( 'Search results updated.', 'woocommerce' ),
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display a searchable, selectable list of items.
|
||||
*/
|
||||
export class SearchListControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.onSelect = this.onSelect.bind( this );
|
||||
this.onRemove = this.onRemove.bind( this );
|
||||
this.onClear = this.onClear.bind( this );
|
||||
this.defaultRenderItem = this.defaultRenderItem.bind( this );
|
||||
}
|
||||
|
||||
onRemove( id ) {
|
||||
const { selected, onChange } = this.props;
|
||||
return () => {
|
||||
const i = findIndex( selected, { id } );
|
||||
onChange( [ ...selected.slice( 0, i ), ...selected.slice( i + 1 ) ] );
|
||||
};
|
||||
}
|
||||
|
||||
onSelect( item ) {
|
||||
const { selected, onChange } = this.props;
|
||||
return () => {
|
||||
onChange( [ ...selected, item ] );
|
||||
};
|
||||
}
|
||||
|
||||
onClear() {
|
||||
this.props.onChange( [] );
|
||||
}
|
||||
|
||||
isSelected( item ) {
|
||||
return -1 !== findIndex( this.props.selected, { id: item.id } );
|
||||
}
|
||||
|
||||
getFilteredList( list, search ) {
|
||||
if ( ! search ) {
|
||||
return list.filter( ( item ) => item && ! this.isSelected( item ) );
|
||||
}
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
const re = new RegExp( escapeRegExp( search ), 'i' );
|
||||
this.props.debouncedSpeak( messages.updated );
|
||||
return list
|
||||
.map( ( item ) => ( re.test( item.name ) ? item : false ) )
|
||||
.filter( ( item ) => item && ! this.isSelected( item ) );
|
||||
}
|
||||
|
||||
getHighlightedName( name, search ) {
|
||||
if ( ! search ) {
|
||||
return name;
|
||||
}
|
||||
const re = new RegExp( escapeRegExp( search ), 'ig' );
|
||||
return name.replace( re, '<strong>$&</strong>' );
|
||||
}
|
||||
|
||||
defaultRenderItem( { getHighlightedName, item, onSelect, search } ) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ item.id }
|
||||
className="woocommerce-search-list__item"
|
||||
onClick={ onSelect( item ) }
|
||||
>
|
||||
<span
|
||||
className="woocommerce-search-list__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, search, selected, setState } = this.props;
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
const list = this.getFilteredList( this.props.list, search );
|
||||
const noResults = search ? sprintf( messages.noResults, search ) : null;
|
||||
const renderItem = this.props.renderItem || this.defaultRenderItem;
|
||||
const selectedCount = selected.length;
|
||||
|
||||
return (
|
||||
<div className={ `woocommerce-search-list ${ className }` }>
|
||||
{ selectedCount > 0 ? (
|
||||
<div className="woocommerce-search-list__selected">
|
||||
<div className="woocommerce-search-list__selected-header">
|
||||
<strong>{ messages.selected( selectedCount ) }</strong>
|
||||
<Button isLink isDestructive onClick={ this.onClear } aria-label={ messages.clear }>
|
||||
{ __( 'Clear all', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
{ selected.map( ( item, i ) => (
|
||||
<Tag
|
||||
key={ i }
|
||||
label={ item.name }
|
||||
id={ item.id }
|
||||
remove={ this.onRemove }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
<div className="woocommerce-search-list__search">
|
||||
<TextControl
|
||||
label={ messages.search }
|
||||
type="search"
|
||||
value={ search }
|
||||
onChange={ ( value ) => setState( { search: value } ) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ ! list.length ? (
|
||||
noResults
|
||||
) : (
|
||||
<MenuGroup
|
||||
label={ messages.list }
|
||||
className="woocommerce-search-list__list"
|
||||
>
|
||||
{ list.map( ( item ) =>
|
||||
renderItem( {
|
||||
getHighlightedName: this.getHighlightedName,
|
||||
item,
|
||||
onSelect: this.onSelect,
|
||||
search,
|
||||
} )
|
||||
) }
|
||||
</MenuGroup>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SearchListControl.propTypes = {
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* A complete list of item objects, each with id, name properties. This is displayed as a
|
||||
* clickable/keyboard-able list, and possibly filtered by the search term (searches name).
|
||||
*/
|
||||
list: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
id: PropTypes.number,
|
||||
name: PropTypes.string,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* Messages displayed or read to the user. Configure these to reflect your object type.
|
||||
* See `defaultMessages` above for examples.
|
||||
*/
|
||||
messages: PropTypes.shape( {
|
||||
/**
|
||||
* A more detailed label for the "Clear all" button, read to screen reader users.
|
||||
*/
|
||||
clear: PropTypes.string,
|
||||
/**
|
||||
* Label for the list of selectable items, only read to screen reader users.
|
||||
*/
|
||||
list: PropTypes.string,
|
||||
/**
|
||||
* Message to display when no matching results are found. %s is the search term.
|
||||
*/
|
||||
noResults: PropTypes.string,
|
||||
/**
|
||||
* Label for the search input
|
||||
*/
|
||||
search: PropTypes.string,
|
||||
/**
|
||||
* Label for the selected items. This is actually a function, so that we can pass
|
||||
* through the count of currently selected items.
|
||||
*/
|
||||
selected: PropTypes.func,
|
||||
/**
|
||||
* Label indicating that search results have changed, read to screen reader users.
|
||||
*/
|
||||
updated: PropTypes.string,
|
||||
} ),
|
||||
/**
|
||||
* Callback fired when selected items change, whether added, cleared, or removed.
|
||||
* Passed an array of item objects (as passed in via props.list).
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Callback to render each item in the selection list, allows any custom object-type rendering.
|
||||
*/
|
||||
renderItem: PropTypes.func,
|
||||
/**
|
||||
* The list of currently selected items.
|
||||
*/
|
||||
selected: PropTypes.array.isRequired,
|
||||
// from withState
|
||||
search: PropTypes.string,
|
||||
setState: PropTypes.func,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func,
|
||||
// from withInstanceId
|
||||
instanceId: PropTypes.number,
|
||||
};
|
||||
|
||||
export default compose( [
|
||||
withState( {
|
||||
search: '',
|
||||
} ),
|
||||
withSpokenMessages,
|
||||
withInstanceId,
|
||||
] )( SearchListControl );
|
|
@ -0,0 +1,67 @@
|
|||
.woocommerce-search-list {
|
||||
width: 100%;
|
||||
padding: 0 0 $gap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__selected {
|
||||
margin: $gap 0;
|
||||
padding: $gap 0 0;
|
||||
border-top: 1px solid $core-grey-light-500;
|
||||
|
||||
.woocommerce-search-list__selected-header {
|
||||
margin-bottom: $gap-smaller;
|
||||
|
||||
button {
|
||||
margin-left: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-tag__text {
|
||||
max-width: 13em;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-search-list__search {
|
||||
margin: $gap 0;
|
||||
padding: $gap 0 0;
|
||||
border-top: 1px solid $core-grey-light-500;
|
||||
|
||||
.components-base-control__field {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-search-list__list {
|
||||
padding: 0;
|
||||
max-height: 20em;
|
||||
overflow-x: scroll;
|
||||
border-top: 1px solid $core-grey-light-500;
|
||||
border-bottom: 1px solid $core-grey-light-500;
|
||||
|
||||
.components-menu-group__label {
|
||||
@include visually-hidden;
|
||||
}
|
||||
|
||||
& > [role="menu"] {
|
||||
border: 1px solid $core-grey-light-500;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.woocommerce-search-list__item {
|
||||
margin-bottom: 0;
|
||||
padding: $gap;
|
||||
background: $white;
|
||||
// !important to keep the border around on hover
|
||||
border-bottom: 1px solid $core-grey-light-500 !important;
|
||||
color: $core-grey-dark-500;
|
||||
|
||||
@include hover-state {
|
||||
background: $core-grey-light-100;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { Component, Fragment, RawHTML } from '@wordpress/element';
|
||||
import {
|
||||
BlockAlignmentToolbar,
|
||||
|
@ -18,7 +18,7 @@ import {
|
|||
SelectControl,
|
||||
Spinner,
|
||||
Toolbar,
|
||||
TreeSelect,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
@ -29,6 +29,7 @@ import { registerBlockType } from '@wordpress/blocks';
|
|||
import '../css/product-category-block.scss';
|
||||
import getQuery from './utils/get-query';
|
||||
import getShortcode from './utils/get-shortcode';
|
||||
import ProductCategoryControl from './components/product-category-control';
|
||||
import ProductPreview from './components/product-preview';
|
||||
import sharedAttributes from './utils/shared-attributes';
|
||||
|
||||
|
@ -38,26 +39,16 @@ const validAlignments = [ 'center', 'wide', 'full' ];
|
|||
/**
|
||||
* Component to handle edit mode of "Products by Category".
|
||||
*/
|
||||
class ProductByCategoryBlock extends Component {
|
||||
export default class ProductByCategoryBlock extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.state = {
|
||||
categoriesList: [],
|
||||
products: [],
|
||||
loaded: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
apiFetch( {
|
||||
path: addQueryArgs( '/wc/v3/products/categories', { per_page: -1 } ),
|
||||
} )
|
||||
.then( ( categoriesList ) => {
|
||||
this.setState( { categoriesList } );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { categoriesList: [] } );
|
||||
} );
|
||||
if ( this.props.attributes.categories ) {
|
||||
this.getProducts();
|
||||
}
|
||||
|
@ -90,19 +81,16 @@ class ProductByCategoryBlock extends Component {
|
|||
|
||||
getInspectorControls() {
|
||||
const { attributes, setAttributes } = this.props;
|
||||
const { columns, orderby, rows, categories } = attributes;
|
||||
const { categoriesList } = this.state;
|
||||
const { columns, orderby, rows } = attributes;
|
||||
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody title={ __( 'Product Category', 'woocommerce' ) } initialOpen>
|
||||
<TreeSelect
|
||||
label={ __( 'Product Category', 'woocommerce' ) }
|
||||
tree={ categoriesList }
|
||||
selectedId={ categories }
|
||||
multiple
|
||||
onChange={ ( value ) => {
|
||||
setAttributes( { categories: value ? value : [] } );
|
||||
<ProductCategoryControl
|
||||
selected={ attributes.categories }
|
||||
onChange={ ( value = [] ) => {
|
||||
const ids = value.map( ( { id } ) => id );
|
||||
setAttributes( { categories: ids } );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
|
@ -164,42 +152,37 @@ class ProductByCategoryBlock extends Component {
|
|||
}
|
||||
|
||||
renderEditMode() {
|
||||
const { setAttributes } = this.props;
|
||||
const { categories } = this.props.attributes;
|
||||
const { categoriesList } = this.state;
|
||||
const { attributes, debouncedSpeak, setAttributes } = this.props;
|
||||
const onDone = () => {
|
||||
setAttributes( { editMode: false } );
|
||||
debouncedSpeak( __( 'Showing product block preview.', 'woocommerce' ) );
|
||||
};
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
icon="category"
|
||||
label={ __( 'Products by Category', 'woocommerce' ) }
|
||||
className="wc-block-products-category"
|
||||
>
|
||||
{ __(
|
||||
'Display a grid of products from your selected categories',
|
||||
'woocommerce'
|
||||
) }
|
||||
{ categoriesList.length ? (
|
||||
<div className="wc-block-products-category__selection">
|
||||
<TreeSelect
|
||||
label={ __( 'Product Category', 'woocommerce' ) }
|
||||
tree={ categoriesList }
|
||||
selectedId={ categories }
|
||||
multiple
|
||||
onChange={ ( value ) => {
|
||||
setAttributes( { categories: value ? value : [] } );
|
||||
} }
|
||||
/>
|
||||
<Button
|
||||
isDefault
|
||||
onClick={ () => setAttributes( { editMode: false } ) }
|
||||
>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="wc-block-products-category__selection">
|
||||
<Spinner />
|
||||
</div>
|
||||
) }
|
||||
<div className="wc-block-products-category__selection">
|
||||
<ProductCategoryControl
|
||||
selected={ attributes.categories }
|
||||
onChange={ ( value = [] ) => {
|
||||
const ids = value.map( ( { id } ) => id );
|
||||
setAttributes( { categories: ids } );
|
||||
} }
|
||||
/>
|
||||
<Button
|
||||
isDefault
|
||||
onClick={ onDone }
|
||||
>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
@ -265,9 +248,11 @@ ProductByCategoryBlock.propTypes = {
|
|||
* A callback to update attributes
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ProductByCategoryBlock;
|
||||
const WrappedProductByCategoryBlock = withSpokenMessages( ProductByCategoryBlock );
|
||||
|
||||
/**
|
||||
* Register and run the "Products by Category" block.
|
||||
|
@ -303,7 +288,7 @@ registerBlockType( 'woocommerce/product-category', {
|
|||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
return <ProductByCategoryBlock { ...props } />;
|
||||
return <WrappedProductByCategoryBlock { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,6 +58,7 @@ const GutenbergBlocksConfig = {
|
|||
includePaths: [ 'assets/css/abstracts' ],
|
||||
data:
|
||||
'@import "_colors"; ' +
|
||||
'@import "_variables"; ' +
|
||||
'@import "_breakpoints"; ' +
|
||||
'@import "_mixins"; ',
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue