* 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:
Mike Jolley 2020-06-10 16:56:13 +01:00 committed by GitHub
parent cadeb15e29
commit 2c6d8d7f97
14 changed files with 404 additions and 2 deletions

View File

@ -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';

View File

@ -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;

View File

@ -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>
</>
);
};

View File

@ -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,
} );

View File

@ -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: "";
}
}
}

View File

@ -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,
} );

View File

@ -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;

View File

@ -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>
</>
);
};

View File

@ -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,
} );

View File

@ -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: "";
}
}
}

View File

@ -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 ),
} );

View File

@ -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' ],
],
],
],

View File

@ -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 );

View File

@ -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;
}
}