From ad4fb0122807e16467c4f44d07e47eb0a1940a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alba=20Rinc=C3=B3n?= Date: Tue, 19 Jul 2022 15:09:46 +0200 Subject: [PATCH] Start using `block.json` and convert to TS the `Product by Category` block (https://github.com/woocommerce/woocommerce-blocks/pull/6680) * Start using `block.json` and convert to TS the `Product by Category` block * Address feedback to remove some TS errors * Remove unnecessary prop --- .../js/blocks/product-category/block.js | 327 ------------------ .../js/blocks/product-category/block.json | 97 ++++++ .../js/blocks/product-category/block.tsx | 50 +++ .../js/blocks/product-category/edit-mode.tsx | 104 ++++++ .../js/blocks/product-category/edit.tsx | 73 ++++ .../js/blocks/product-category/index.js | 95 ----- .../js/blocks/product-category/index.tsx | 57 +++ .../product-category/inspector-controls.tsx | 116 +++++++ .../js/blocks/product-category/types.ts | 42 +++ .../grid-layout-control/index.js | 2 +- .../product-category-control/index.js | 13 + 11 files changed, 553 insertions(+), 423 deletions(-) delete mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/block.js create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/block.json create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/block.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/edit-mode.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/edit.tsx delete mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/index.js create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/index.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/inspector-controls.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/product-category/types.ts diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.js b/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.js deleted file mode 100644 index 0d087284993..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.js +++ /dev/null @@ -1,327 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { BlockControls, InspectorControls } from '@wordpress/block-editor'; -import ServerSideRender from '@wordpress/server-side-render'; -import { - Button, - Disabled, - PanelBody, - Placeholder, - ToolbarGroup, - withSpokenMessages, -} from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import PropTypes from 'prop-types'; -import GridContentControl from '@woocommerce/editor-components/grid-content-control'; -import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; -import ProductCategoryControl from '@woocommerce/editor-components/product-category-control'; -import ProductOrderbyControl from '@woocommerce/editor-components/product-orderby-control'; -import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; -import { gridBlockPreview } from '@woocommerce/resource-previews'; -import { Icon, file } from '@wordpress/icons'; -import { getSetting } from '@woocommerce/settings'; - -const EmptyPlaceholder = () => ( - } - label={ __( 'Products by Category', 'woo-gutenberg-products-block' ) } - className="wc-block-products-grid wc-block-products-category" - > - { __( - 'No products were found that matched your selection.', - 'woo-gutenberg-products-block' - ) } - -); - -/** - * Component to handle edit mode of "Products by Category". - */ -class ProductByCategoryBlock extends Component { - static propTypes = { - /** - * The attributes for this block - */ - attributes: PropTypes.object.isRequired, - /** - * The register block name. - */ - name: PropTypes.string.isRequired, - /** - * A callback to update attributes - */ - setAttributes: PropTypes.func.isRequired, - - // from withSpokenMessages - debouncedSpeak: PropTypes.func.isRequired, - }; - - state = { - changedAttributes: {}, - isEditing: false, - }; - - componentDidMount() { - const { attributes } = this.props; - - if ( ! attributes.categories.length ) { - // We've removed all selected categories, or no categories have been selected yet. - this.setState( { isEditing: true } ); - } - } - - startEditing = () => { - this.setState( { - isEditing: true, - changedAttributes: {}, - } ); - }; - - stopEditing = () => { - this.setState( { - isEditing: false, - changedAttributes: {}, - } ); - }; - - /** - * Set changed attributes to state. - * - * @param {Object} attributes List of attributes to set. - */ - setChangedAttributes = ( attributes ) => { - this.setState( ( prevState ) => { - return { - changedAttributes: { - ...prevState.changedAttributes, - ...attributes, - }, - }; - } ); - }; - - save = () => { - const { changedAttributes } = this.state; - const { setAttributes } = this.props; - - setAttributes( changedAttributes ); - this.stopEditing(); - }; - - getInspectorControls() { - const { attributes, setAttributes } = this.props; - const { isEditing } = this.state; - const { - columns, - catOperator, - contentVisibility, - orderby, - rows, - alignButtons, - stockStatus, - } = attributes; - - return ( - - - { - const ids = value.map( ( { id } ) => id ); - const changes = { categories: ids }; - - // Changes in the sidebar save instantly and overwrite any unsaved changes. - setAttributes( changes ); - this.setChangedAttributes( changes ); - } } - operator={ catOperator } - onOperatorChange={ ( value = 'any' ) => { - const changes = { catOperator: value }; - setAttributes( changes ); - this.setChangedAttributes( changes ); - } } - isCompact={ true } - /> - - - - - - - setAttributes( { contentVisibility: value } ) - } - /> - - - - - - - - - ); - } - - renderEditMode() { - const { attributes, debouncedSpeak } = this.props; - const { changedAttributes } = this.state; - const currentAttributes = { ...attributes, ...changedAttributes }; - const onDone = () => { - this.save(); - debouncedSpeak( - __( - 'Showing Products by Category block preview.', - 'woo-gutenberg-products-block' - ) - ); - }; - const onCancel = () => { - this.stopEditing(); - debouncedSpeak( - __( - 'Showing Products by Category block preview.', - 'woo-gutenberg-products-block' - ) - ); - }; - - return ( - } - label={ __( - 'Products by Category', - 'woo-gutenberg-products-block' - ) } - className="wc-block-products-grid wc-block-products-category" - > - { __( - 'Display a grid of products from your selected categories.', - 'woo-gutenberg-products-block' - ) } -
- { - const ids = value.map( ( { id } ) => id ); - this.setChangedAttributes( { categories: ids } ); - } } - operator={ currentAttributes.catOperator } - onOperatorChange={ ( value = 'any' ) => - this.setChangedAttributes( { catOperator: value } ) - } - /> - - -
-
- ); - } - - renderViewMode() { - const { attributes, name } = this.props; - const hasCategories = attributes.categories.length; - - return ( - - { hasCategories ? ( - - ) : ( - __( - 'Select at least one category to display its products.', - 'woo-gutenberg-products-block' - ) - ) } - - ); - } - - render() { - const { isEditing } = this.state; - const { attributes } = this.props; - - if ( attributes.isPreview ) { - return gridBlockPreview; - } - - return ( - <> - - - isEditing - ? this.stopEditing() - : this.startEditing(), - isActive: isEditing, - }, - ] } - /> - - { this.getInspectorControls() } - { isEditing ? this.renderEditMode() : this.renderViewMode() } - - ); - } -} - -export default withSpokenMessages( ProductByCategoryBlock ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.json new file mode 100644 index 00000000000..1aa2d8d01bd --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.json @@ -0,0 +1,97 @@ +{ + "name": "woocommerce/product-category", + "title": "Products by Category", + "category": "woocommerce", + "keywords": [ "WooCommerce" ], + "description": "Display a grid of products from your selected categories.", + "supports": { + "align": [ "wide", "full" ], + "html": false + }, + "example": { + "attributes": { + "isPreview": true + } + }, + "attributes": { + "columns": { + "type": "number", + "default": 3 + }, + "rows": { + "type": "number", + "default": 3 + }, + "alignButtons": { + "type": "boolean", + "default": false + }, + "contentVisibility": { + "type": "object", + "default": { + "image": true, + "title": true, + "price": true, + "rating": true, + "button": true + }, + "properties": { + "image": { + "type": "boolean", + "default": true + }, + "title": { + "type": "boolean", + "default": true + }, + "price": { + "type": "boolean", + "default": true + }, + "rating": { + "type": "boolean", + "default": true + }, + "button": { + "type": "boolean", + "default": true + } + } + }, + "categories": { + "type": "array", + "default": [] + }, + "catOperator": { + "type": "string", + "default": "any" + }, + "isPreview": { + "type": "boolean", + "default": false + }, + "stockStatus": { + "type": "array" + }, + "editMode": { + "type": "boolean", + "default": true + }, + "orderby": { + "type": "string", + "enum": [ + "date", + "popularity", + "price_asc", + "price_desc", + "rating", + "title", + "menu_order" + ], + "default": "date" + } + }, + "textdomain": "woo-gutenberg-products-block", + "apiVersion": 2, + "$schema": "https://schemas.wp.org/trunk/block.json" +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.tsx new file mode 100644 index 00000000000..c4c11862573 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/block.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import ServerSideRender from '@wordpress/server-side-render'; +import { Placeholder } from '@wordpress/components'; +import { Icon, file } from '@wordpress/icons'; +import { gridBlockPreview } from '@woocommerce/resource-previews'; + +/** + * Internal dependencies + */ +import { Props } from './types'; + +const EmptyPlaceholder = () => ( + } + label={ __( 'Products by Category', 'woo-gutenberg-products-block' ) } + className="wc-block-products-grid wc-block-products-category" + > + { __( + 'No products were found that matched your selection.', + 'woo-gutenberg-products-block' + ) } + +); + +export const ProductByCategoryBlock = ( props: Props ): JSX.Element => { + const { name, attributes } = props; + const hasCategories = attributes.categories.length; + + if ( attributes.isPreview ) { + return gridBlockPreview; + } + + return hasCategories ? ( + + ) : ( + <> + { __( + 'Select at least one category to display its products.', + 'woo-gutenberg-products-block' + ) } + + ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit-mode.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit-mode.tsx new file mode 100644 index 00000000000..e2de7e09069 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit-mode.tsx @@ -0,0 +1,104 @@ +/** + * External dependencies + */ +import { Button, Placeholder } from '@wordpress/components'; +import { Icon, file } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import ProductCategoryControl from '@woocommerce/editor-components/product-category-control'; + +/** + * Internal dependencies + */ +import { Attributes, Props } from './types'; + +export interface EditModeProps extends Props { + isEditing: boolean; + setIsEditing: ( isEditing: boolean ) => void; + changedAttributes: Partial< Attributes >; + setChangedAttributes: ( changedAttributes: Partial< Attributes > ) => void; +} + +export const ProductsByCategoryEditMode = ( + props: EditModeProps +): JSX.Element => { + const { + debouncedSpeak, + setIsEditing, + changedAttributes, + setChangedAttributes, + attributes, + } = props; + + const currentAttributes = { ...attributes, ...changedAttributes }; + + const stopEditing = () => { + setIsEditing( false ); + setChangedAttributes( {} ); + }; + + const save = () => { + const { setAttributes } = props; + + setAttributes( changedAttributes ); + stopEditing(); + }; + + const onDone = () => { + save(); + debouncedSpeak( + __( + 'Showing Products by Category block preview.', + 'woo-gutenberg-products-block' + ) + ); + }; + + const onCancel = () => { + stopEditing(); + debouncedSpeak( + __( + 'Showing Products by Category block preview.', + 'woo-gutenberg-products-block' + ) + ); + }; + + return ( + } + label={ __( + 'Products by Category', + 'woo-gutenberg-products-block' + ) } + className="wc-block-products-grid wc-block-products-category" + > + { __( + 'Display a grid of products from your selected categories.', + 'woo-gutenberg-products-block' + ) } +
+ { + const ids = value.map( ( { id } ) => id ); + setChangedAttributes( { categories: ids } ); + } } + operator={ currentAttributes.catOperator } + onOperatorChange={ ( value = 'any' ) => + setChangedAttributes( { catOperator: value } ) + } + /> + + +
+
+ ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit.tsx new file mode 100644 index 00000000000..5f0d3fae66c --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/edit.tsx @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import { BlockControls, useBlockProps } from '@wordpress/block-editor'; +import { useState } from '@wordpress/element'; +import { + Disabled, + ToolbarGroup, + withSpokenMessages, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { ProductByCategoryBlock } from './block'; +import { Attributes, Props } from './types'; +import { ProductsByCategoryInspectorControls } from './inspector-controls'; +import { ProductsByCategoryEditMode } from './edit-mode'; + +const EditBlock = ( props: Props ): JSX.Element => { + const blockProps = useBlockProps(); + + const { attributes } = props; + + const [ isEditing, setIsEditing ] = useState( + ! attributes.categories.length + ); + + const [ changedAttributes, setChangedAttributes ] = useState< + Partial< Attributes > + >( {} ); + + return ( +
+ + setIsEditing( ! isEditing ), + isActive: isEditing, + }, + ] } + /> + + + { isEditing ? ( + + ) : ( + + + + ) } +
+ ); +}; + +export const Edit = withSpokenMessages( EditBlock ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.js b/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.js deleted file mode 100644 index cdb4fd848af..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { createBlock, registerBlockType } from '@wordpress/blocks'; -import { without } from 'lodash'; -import { Icon, file } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import './editor.scss'; -import Block from './block'; -import sharedAttributes, { - sharedAttributeBlockTypes, -} from '../../utils/shared-attributes'; - -/** - * Register and run the "Products by Category" block. - */ -registerBlockType( 'woocommerce/product-category', { - title: __( 'Products by Category', 'woo-gutenberg-products-block' ), - icon: { - src: ( - - ), - }, - category: 'woocommerce', - keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ], - description: __( - 'Display a grid of products from your selected categories.', - 'woo-gutenberg-products-block' - ), - supports: { - align: [ 'wide', 'full' ], - html: false, - }, - example: { - attributes: { - isPreview: true, - }, - }, - attributes: { - ...sharedAttributes, - - /** - * Toggle for edit mode in the block preview. - */ - editMode: { - type: 'boolean', - default: true, - }, - - /** - * How to order the products: 'date', 'popularity', 'price_asc', 'price_desc' 'rating', 'title'. - */ - orderby: { - type: 'string', - default: 'date', - }, - }, - - transforms: { - from: [ - { - type: 'block', - blocks: without( - sharedAttributeBlockTypes, - 'woocommerce/product-category' - ), - transform: ( attributes ) => - createBlock( 'woocommerce/product-category', { - ...attributes, - editMode: false, - } ), - }, - ], - }, - - /** - * Renders and manages the block. - * - * @param {Object} props Props to pass to block. - */ - edit( props ) { - return ; - }, - - save() { - return null; - }, -} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.tsx new file mode 100644 index 00000000000..d51136c3326 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/index.tsx @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { createBlock, registerBlockType } from '@wordpress/blocks'; +import { without } from 'lodash'; +import { Icon, file } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import './editor.scss'; +import metadata from './block.json'; +import sharedAttributes, { + sharedAttributeBlockTypes, +} from '../../utils/shared-attributes'; +import { Edit } from './edit'; + +/** + * Register and run the "Products by Category" block. + */ +registerBlockType( metadata, { + icon: { + src: ( + + ), + }, + attributes: { + ...metadata.attributes, + ...sharedAttributes, + }, + + transforms: { + from: [ + { + type: 'block', + blocks: without( + sharedAttributeBlockTypes, + 'woocommerce/product-category' + ), + transform: ( attributes ) => + createBlock( 'woocommerce/product-category', { + ...attributes, + editMode: false, + } ), + }, + ], + }, + + edit: Edit, + + save: () => { + return null; + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/inspector-controls.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-category/inspector-controls.tsx new file mode 100644 index 00000000000..f69504b18e0 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/inspector-controls.tsx @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import ProductCategoryControl from '@woocommerce/editor-components/product-category-control'; +import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; +import { getSetting } from '@woocommerce/settings'; +import GridContentControl from '@woocommerce/editor-components/grid-content-control'; +import ProductOrderbyControl from '@woocommerce/editor-components/product-orderby-control'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; + +/** + * Internal dependencies + */ +import { Attributes, Props } from './types'; + +export interface InspectorControlsProps extends Props { + isEditing: boolean; + setChangedAttributes: ( changedAttributes: Partial< Attributes > ) => void; +} + +export const ProductsByCategoryInspectorControls = ( + props: InspectorControlsProps +): JSX.Element => { + const { isEditing, attributes, setAttributes, setChangedAttributes } = + props; + const { + columns, + catOperator, + contentVisibility, + orderby, + rows, + alignButtons, + stockStatus, + } = attributes; + + return ( + + + { + const ids = value.map( ( { id } ) => id ); + const changes = { categories: ids }; + + // Changes in the sidebar save instantly and overwrite any unsaved changes. + setAttributes( changes ); + setChangedAttributes( changes ); + } } + operator={ catOperator } + onOperatorChange={ ( value = 'any' ) => { + const changes = { catOperator: value }; + setAttributes( changes ); + setChangedAttributes( changes ); + } } + isCompact={ true } + /> + + + + + + + setAttributes( { contentVisibility: value } ) + } + /> + + + + + + + + + ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-category/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-category/types.ts new file mode 100644 index 00000000000..ae3004bcb64 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-category/types.ts @@ -0,0 +1,42 @@ +export interface Attributes { + columns: number; + rows: number; + alignButtons: boolean; + contentVisibility: { + image: boolean; + title: boolean; + price: boolean; + rating: boolean; + button: boolean; + }; + categories: Array< number >; + catOperator: string; + isPreview: boolean; + stockStatus: Array< string >; + editMode: boolean; + orderby: + | 'date' + | 'popularity' + | 'price_asc' + | 'price_desc' + | 'rating' + | 'title' + | 'menu_order'; +} + +export interface Props { + /** + * The attributes for this block + */ + attributes: Attributes; + /** + * The register block name. + */ + name: string; + /** + * A callback to update attributes + */ + setAttributes: ( attributes: Partial< Attributes > ) => void; + // from withSpokenMessages + debouncedSpeak: ( message: string ) => void; +} diff --git a/plugins/woocommerce-blocks/assets/js/editor-components/grid-layout-control/index.js b/plugins/woocommerce-blocks/assets/js/editor-components/grid-layout-control/index.js index 3a906b8f618..ff269f75ccb 100644 --- a/plugins/woocommerce-blocks/assets/js/editor-components/grid-layout-control/index.js +++ b/plugins/woocommerce-blocks/assets/js/editor-components/grid-layout-control/index.js @@ -13,7 +13,7 @@ import { RangeControl, ToggleControl } from '@wordpress/components'; * @param {number} props.columns * @param {number} props.rows * @param {function(any):any} props.setAttributes Setter for block attributes. - * @param {string} props.alignButtons + * @param {boolean} props.alignButtons * @param {number} props.minColumns * @param {number} props.maxColumns * @param {number} props.minRows diff --git a/plugins/woocommerce-blocks/assets/js/editor-components/product-category-control/index.js b/plugins/woocommerce-blocks/assets/js/editor-components/product-category-control/index.js index a25c763e20d..e083db8b122 100644 --- a/plugins/woocommerce-blocks/assets/js/editor-components/product-category-control/index.js +++ b/plugins/woocommerce-blocks/assets/js/editor-components/product-category-control/index.js @@ -17,6 +17,19 @@ import classNames from 'classnames'; */ import './style.scss'; +/** + * @param {Object} props + * @param {string=} props.categories + * @param {boolean=} props.isLoading + * @param {string=} props.error + * @param {Function} props.onChange + * @param {Function=} props.onOperatorChange + * @param {string=} props.operator + * @param {number[]} props.selected + * @param {boolean=} props.isCompact + * @param {boolean=} props.isSingle + * @param {boolean=} props.showReviewCount + */ const ProductCategoryControl = ( { categories, error,