Add Atomic Category and Tag blocks (https://github.com/woocommerce/woocommerce-blocks/pull/2667)
* Add categories and tags to the API * Add atomic categories block * Add tag list block * Add edit views * Add correct icons * Update styles * Update assets/js/atomic/blocks/product/category-list/style.scss Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com> * Update margin * Use registerExperimentalBlockType Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
parent
cadeb15e29
commit
2c6d8d7f97
|
@ -9,3 +9,5 @@ import './product/button';
|
|||
import './product/summary';
|
||||
import './product/sale-badge';
|
||||
import './product/sku';
|
||||
import './product/category-list';
|
||||
import './product/tag-list';
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
useInnerBlockLayoutContext,
|
||||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Category Block Component.
|
||||
*
|
||||
* @param {Object} props Incoming props.
|
||||
* @param {string} [props.className] CSS Class name for the component.
|
||||
* @param {Object} [props.product] Optional product object. Product from context will be used if
|
||||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const { product } = productDataContext || props;
|
||||
|
||||
if ( ! product || ! product.categories ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-components-product-category-list',
|
||||
`${ parentClassName }__product-category-list`
|
||||
) }
|
||||
>
|
||||
{ __( 'Categories:', 'woo-gutenberg-products-block' ) }{ ' ' }
|
||||
<ul>
|
||||
{ Object.values( product.categories ).map(
|
||||
( { name, link, slug } ) => {
|
||||
return (
|
||||
<li key={ `category-list-item-${ slug }` }>
|
||||
<a href={ link }>{ name }</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Block;
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import EditProductLink from '@woocommerce/block-components/edit-product-link';
|
||||
import { useProductDataContext } from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
|
||||
export default ( { attributes } ) => {
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = productDataContext.product || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditProductLink productId={ product.id } />
|
||||
<Disabled>
|
||||
<Block { ...attributes } />
|
||||
</Disabled>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
|
||||
import { Icon, folder } from '@woocommerce/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import sharedConfig from '../shared-config';
|
||||
import edit from './edit';
|
||||
|
||||
const blockConfig = {
|
||||
title: __( 'Product Category List', 'woo-gutenberg-products-block' ),
|
||||
description: __(
|
||||
'Display a list of categories belonging to a product.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
icon: {
|
||||
src: <Icon srcElement={ folder } />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
edit,
|
||||
};
|
||||
|
||||
registerExperimentalBlockType( 'woocommerce/product-category-list', {
|
||||
...sharedConfig,
|
||||
...blockConfig,
|
||||
} );
|
|
@ -0,0 +1,23 @@
|
|||
.wc-block-layout .wc-block-components-product-category-list {
|
||||
margin-top: 0;
|
||||
margin-bottom: em($gap-small);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li::after {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
li:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, barcode } from '@woocommerce/icons';
|
||||
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -24,7 +24,7 @@ const blockConfig = {
|
|||
edit,
|
||||
};
|
||||
|
||||
registerBlockType( 'woocommerce/product-sku', {
|
||||
registerExperimentalBlockType( 'woocommerce/product-sku', {
|
||||
...sharedConfig,
|
||||
...blockConfig,
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
useInnerBlockLayoutContext,
|
||||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Tag List Block Component.
|
||||
*
|
||||
* @param {Object} props Incoming props.
|
||||
* @param {string} [props.className] CSS Class name for the component.
|
||||
* @param {Object} [props.product] Optional product object. Product from context will be used if
|
||||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const { product } = productDataContext || props;
|
||||
|
||||
if ( ! product || ! product.tags ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-components-product-tag-list',
|
||||
`${ parentClassName }__product-tag-list`
|
||||
) }
|
||||
>
|
||||
{ __( 'Tags:', 'woo-gutenberg-products-block' ) }{ ' ' }
|
||||
<ul>
|
||||
{ Object.values( product.tags ).map(
|
||||
( { name, link, slug } ) => {
|
||||
return (
|
||||
<li key={ `tag-list-item-${ slug }` }>
|
||||
<a href={ link }>{ name }</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Block;
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import EditProductLink from '@woocommerce/block-components/edit-product-link';
|
||||
import { useProductDataContext } from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
|
||||
export default ( { attributes } ) => {
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = productDataContext.product || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditProductLink productId={ product.id } />
|
||||
<Disabled>
|
||||
<Block { ...attributes } />
|
||||
</Disabled>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, tag } from '@woocommerce/icons';
|
||||
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import sharedConfig from '../shared-config';
|
||||
import edit from './edit';
|
||||
|
||||
const blockConfig = {
|
||||
title: __( 'Product Tag List', 'woo-gutenberg-products-block' ),
|
||||
description: __(
|
||||
'Display a list of tags belonging to a product.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
icon: {
|
||||
src: <Icon srcElement={ tag } />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
edit,
|
||||
};
|
||||
|
||||
registerExperimentalBlockType( 'woocommerce/product-tag-list', {
|
||||
...sharedConfig,
|
||||
...blockConfig,
|
||||
} );
|
|
@ -0,0 +1,23 @@
|
|||
.wc-block-layout .wc-block-components-product-tag-list {
|
||||
margin-top: 0;
|
||||
margin-bottom: em($gap-small);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li::after {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
li:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import ProductSaleBadge from '../blocks/product/sale-badge/block';
|
|||
import ProductSummary from '../blocks/product/summary/block';
|
||||
import ProductTitle from '../blocks/product/title/frontend';
|
||||
import ProductSku from '../blocks/product/sku/block';
|
||||
import ProductCategoryList from '../blocks/product/category-list/block';
|
||||
import ProductTagList from '../blocks/product/tag-list/block';
|
||||
|
||||
/**
|
||||
* Map blocks to components suitable for use on the frontend.
|
||||
|
@ -29,5 +31,7 @@ export const getBlockMap = ( blockName ) => ( {
|
|||
'woocommerce/product-summary': ProductSummary,
|
||||
'woocommerce/product-sale-badge': ProductSaleBadge,
|
||||
'woocommerce/product-sku': ProductSku,
|
||||
'woocommerce/product-category-list': ProductCategoryList,
|
||||
'woocommerce/product-tag-list': ProductTagList,
|
||||
...getRegisteredInnerBlocks( blockName ),
|
||||
} );
|
||||
|
|
|
@ -37,6 +37,8 @@ export const DEFAULT_INNER_BLOCKS = [
|
|||
[ 'woocommerce/product-summary' ],
|
||||
[ 'woocommerce/product-button' ],
|
||||
[ 'woocommerce/product-sku' ],
|
||||
[ 'woocommerce/product-category-list' ],
|
||||
[ 'woocommerce/product-tag-list' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -104,6 +104,8 @@ class Library {
|
|||
'product-sale-badge',
|
||||
'product-summary',
|
||||
'product-sku',
|
||||
'product-category-list',
|
||||
'product-tag-list',
|
||||
];
|
||||
foreach ( $atomic_blocks as $atomic_block ) {
|
||||
$instance = new \Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock( $atomic_block );
|
||||
|
|
|
@ -177,6 +177,74 @@ class ProductSchema extends AbstractSchema {
|
|||
'context' => [ 'view', 'edit' ],
|
||||
'items' => 'number',
|
||||
],
|
||||
'categories' => [
|
||||
'description' => __( 'List of categories, if applicable.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'Category ID', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'number',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Category name', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'slug' => [
|
||||
'description' => __( 'Category slug', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'link' => [
|
||||
'description' => __( 'Category link', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'tags' => [
|
||||
'description' => __( 'List of tags, if applicable.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'Tag ID', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'number',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Tag name', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'slug' => [
|
||||
'description' => __( 'Tag slug', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'link' => [
|
||||
'description' => __( 'Tag link', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'has_options' => [
|
||||
'description' => __( 'Does the product have options?', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'boolean',
|
||||
|
@ -247,6 +315,8 @@ class ProductSchema extends AbstractSchema {
|
|||
'review_count' => $product->get_review_count(),
|
||||
'images' => $this->get_images( $product ),
|
||||
'variations' => $product->is_type( 'variable' ) ? $product->get_visible_children() : [],
|
||||
'categories' => $this->get_term_list( $product, 'product_cat' ),
|
||||
'tags' => $this->get_term_list( $product, 'product_tag' ),
|
||||
'has_options' => $product->has_options(),
|
||||
'is_purchasable' => $product->is_purchasable(),
|
||||
'is_in_stock' => $product->is_in_stock(),
|
||||
|
@ -382,4 +452,42 @@ class ProductSchema extends AbstractSchema {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of terms assigned to the product.
|
||||
*
|
||||
* @param \WC_Product $product Product object.
|
||||
* @param string $taxonomy Taxonomy name.
|
||||
* @return array Array of terms (id, name, slug).
|
||||
*/
|
||||
protected function get_term_list( \WC_Product $product, $taxonomy = '' ) {
|
||||
if ( ! $taxonomy ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$terms = get_the_terms( $product->get_id(), $taxonomy );
|
||||
|
||||
if ( ! $terms || is_wp_error( $terms ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$link = get_term_link( $term, $taxonomy );
|
||||
|
||||
if ( is_wp_error( $link ) ) {
|
||||
$link = false;
|
||||
}
|
||||
|
||||
$return[] = (object) [
|
||||
'id' => $term->term_id,
|
||||
'name' => $term->name,
|
||||
'slug' => $term->slug,
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue