Featured Category Block (https://github.com/woocommerce/woocommerce-blocks/pull/676)
* Block JS * Add block to library and configure build * Update API to return image and link * Limit category selection to 1 * Frontend * Fix variable * Add icon * Rename to isSingle * Standardize naming * fix wrapping issue
This commit is contained in:
parent
f97b5ce800
commit
b528868ea1
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import {
|
||||
AlignmentToolbar,
|
||||
BlockControls,
|
||||
InnerBlocks,
|
||||
InspectorControls,
|
||||
MediaUpload,
|
||||
MediaUploadCheck,
|
||||
PanelColorSettings,
|
||||
withColors,
|
||||
} from '@wordpress/editor';
|
||||
import {
|
||||
Button,
|
||||
FocalPointPicker,
|
||||
IconButton,
|
||||
PanelBody,
|
||||
Placeholder,
|
||||
RangeControl,
|
||||
ResizableBox,
|
||||
Spinner,
|
||||
ToggleControl,
|
||||
Toolbar,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import classnames from 'classnames';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { debounce, isObject } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconFolderStar } from '../../components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductCategoryControl from '../../components/product-category-control';
|
||||
|
||||
/**
|
||||
* The min-height for the block content.
|
||||
*/
|
||||
const MIN_HEIGHT = wc_product_block_data.min_height;
|
||||
|
||||
/**
|
||||
* Get the src from a category object, unless null (no image).
|
||||
*
|
||||
* @param {object|null} category A product category object from the API.
|
||||
* @return {string}
|
||||
*/
|
||||
function getCategoryImageSrc( category ) {
|
||||
if ( isObject( category.image ) ) {
|
||||
return category.image.src;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachment ID from a category object, unless null (no image).
|
||||
*
|
||||
* @param {object|null} category A product category object from the API.
|
||||
* @return {int}
|
||||
*/
|
||||
function getCategoryImageID( category ) {
|
||||
if ( isObject( category.image ) ) {
|
||||
return category.image.id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a style object given either a product category image from the API or URL to an image.
|
||||
*
|
||||
* @param {string} url An image URL.
|
||||
* @return {object} A style object with a backgroundImage set (if a valid image is provided).
|
||||
*/
|
||||
function backgroundImageStyles( url ) {
|
||||
if ( url ) {
|
||||
return { backgroundImage: `url(${ url })` };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the selected ratio to the correct background class.
|
||||
*
|
||||
* @param {number} ratio Selected opacity from 0 to 100.
|
||||
* @return {string} The class name, if applicable (not used for ratio 0 or 50).
|
||||
*/
|
||||
function dimRatioToClass( ratio ) {
|
||||
return ratio === 0 || ratio === 50 ?
|
||||
null :
|
||||
`has-background-dim-${ 10 * Math.round( ratio / 10 ) }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to handle edit mode of "Featured Category".
|
||||
*/
|
||||
class FeaturedCategory extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.state = {
|
||||
category: false,
|
||||
loaded: false,
|
||||
};
|
||||
|
||||
this.debouncedGetCategory = debounce( this.getCategory.bind( this ), 200 );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getCategory();
|
||||
}
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
if ( prevProps.attributes.categoryId !== this.props.attributes.categoryId ) {
|
||||
this.debouncedGetCategory();
|
||||
}
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
const { categoryId } = this.props.attributes;
|
||||
if ( ! categoryId ) {
|
||||
// We've removed the selected product, or no product is selected yet.
|
||||
this.setState( { category: false, loaded: true } );
|
||||
return;
|
||||
}
|
||||
apiFetch( {
|
||||
path: `/wc/blocks/products/categories/${ categoryId }`,
|
||||
} )
|
||||
.then( ( category ) => {
|
||||
this.setState( { category, loaded: true } );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { category: false, loaded: true } );
|
||||
} );
|
||||
}
|
||||
|
||||
getInspectorControls() {
|
||||
const {
|
||||
attributes,
|
||||
setAttributes,
|
||||
overlayColor,
|
||||
setOverlayColor,
|
||||
} = this.props;
|
||||
|
||||
const url =
|
||||
attributes.mediaSrc || getCategoryImageSrc( this.state.category );
|
||||
const { focalPoint = { x: 0.5, y: 0.5 } } = attributes;
|
||||
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody title={ __( 'Content', 'woo-gutenberg-products-block' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Show description', 'woo-gutenberg-products-block' ) }
|
||||
checked={ attributes.showDesc }
|
||||
onChange={ () => setAttributes( { showDesc: ! attributes.showDesc } ) }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelColorSettings
|
||||
title={ __( 'Overlay', 'woo-gutenberg-products-block' ) }
|
||||
colorSettings={ [
|
||||
{
|
||||
value: overlayColor.color,
|
||||
onChange: setOverlayColor,
|
||||
label: __( 'Overlay Color', 'woo-gutenberg-products-block' ),
|
||||
},
|
||||
] }
|
||||
>
|
||||
<RangeControl
|
||||
label={ __( 'Background Opacity', 'woo-gutenberg-products-block' ) }
|
||||
value={ attributes.dimRatio }
|
||||
onChange={ ( ratio ) => setAttributes( { dimRatio: ratio } ) }
|
||||
min={ 0 }
|
||||
max={ 100 }
|
||||
step={ 10 }
|
||||
/>
|
||||
{ !! FocalPointPicker && !! url &&
|
||||
<FocalPointPicker
|
||||
label={ __( 'Focal Point Picker' ) }
|
||||
url={ url }
|
||||
value={ focalPoint }
|
||||
onChange={ ( value ) => setAttributes( { focalPoint: value } ) }
|
||||
/>
|
||||
}
|
||||
</PanelColorSettings>
|
||||
</InspectorControls>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditMode() {
|
||||
const { attributes, debouncedSpeak, setAttributes } = this.props;
|
||||
const onDone = () => {
|
||||
setAttributes( { editMode: false } );
|
||||
debouncedSpeak(
|
||||
__(
|
||||
'Showing Featured Product block preview.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
icon={ <IconFolderStar /> }
|
||||
label={ __( 'Featured Category', 'woo-gutenberg-products-block' ) }
|
||||
className="wc-block-featured-category"
|
||||
>
|
||||
{ __(
|
||||
'Visually highlight a product category and encourage prompt action',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
<div className="wc-block-featured-category__selection">
|
||||
<ProductCategoryControl
|
||||
selected={ [ attributes.categoryId ] }
|
||||
onChange={ ( value = [] ) => {
|
||||
const id = value[ 0 ] ? value[ 0 ].id : 0;
|
||||
setAttributes( { categoryId: id, mediaId: 0, mediaSrc: '' } );
|
||||
} }
|
||||
multiple={ false }
|
||||
/>
|
||||
<Button isDefault onClick={ onDone }>
|
||||
{ __( 'Done', 'woo-gutenberg-products-block' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { attributes, isSelected, overlayColor, setAttributes } = this.props;
|
||||
const {
|
||||
className,
|
||||
contentAlign,
|
||||
dimRatio,
|
||||
editMode,
|
||||
focalPoint,
|
||||
height,
|
||||
showDesc,
|
||||
} = attributes;
|
||||
const { loaded, category } = this.state;
|
||||
const classes = classnames(
|
||||
'wc-block-featured-category',
|
||||
{
|
||||
'is-selected': isSelected,
|
||||
'is-loading': ! category && ! loaded,
|
||||
'is-not-found': ! category && loaded,
|
||||
'has-background-dim': dimRatio !== 0,
|
||||
},
|
||||
dimRatioToClass( dimRatio ),
|
||||
contentAlign !== 'center' && `has-${ contentAlign }-content`,
|
||||
className,
|
||||
);
|
||||
const mediaId = attributes.mediaId || getCategoryImageID( category );
|
||||
const mediaSrc = attributes.mediaSrc || getCategoryImageSrc( this.state.category );
|
||||
const style = !! category ?
|
||||
backgroundImageStyles( mediaSrc ) :
|
||||
{};
|
||||
if ( overlayColor.color ) {
|
||||
style.backgroundColor = overlayColor.color;
|
||||
}
|
||||
if ( focalPoint ) {
|
||||
style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y *
|
||||
100 }%`;
|
||||
}
|
||||
|
||||
const onResizeStop = ( event, direction, elt ) => {
|
||||
setAttributes( { height: parseInt( elt.style.height ) } );
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockControls>
|
||||
<AlignmentToolbar
|
||||
value={ contentAlign }
|
||||
onChange={ ( nextAlign ) => {
|
||||
setAttributes( { contentAlign: nextAlign } );
|
||||
} }
|
||||
/>
|
||||
<MediaUploadCheck>
|
||||
<Toolbar>
|
||||
<MediaUpload
|
||||
onSelect={ ( media ) => {
|
||||
setAttributes( { mediaId: media.id, mediaSrc: media.url } );
|
||||
} }
|
||||
allowedTypes={ [ 'image' ] }
|
||||
value={ mediaId }
|
||||
render={ ( { open } ) => (
|
||||
<IconButton
|
||||
className="components-toolbar__control"
|
||||
label={ __( 'Edit media' ) }
|
||||
icon="format-image"
|
||||
onClick={ open }
|
||||
disabled={ ! this.state.category }
|
||||
/>
|
||||
) }
|
||||
/>
|
||||
</Toolbar>
|
||||
</MediaUploadCheck>
|
||||
</BlockControls>
|
||||
{ ! attributes.editMode && this.getInspectorControls() }
|
||||
{ editMode ? (
|
||||
this.renderEditMode()
|
||||
) : (
|
||||
<Fragment>
|
||||
{ !! category ? (
|
||||
<ResizableBox
|
||||
className={ classes }
|
||||
size={ { height } }
|
||||
minHeight={ MIN_HEIGHT }
|
||||
enable={ { bottom: true } }
|
||||
onResizeStop={ onResizeStop }
|
||||
style={ style }
|
||||
>
|
||||
<div className="wc-block-featured-category__wrapper">
|
||||
<h2
|
||||
className="wc-block-featured-category__title"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: category.name,
|
||||
} }
|
||||
/>
|
||||
{ showDesc && (
|
||||
<div
|
||||
className="wc-block-featured-category__description"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: category.description,
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
<div className="wc-block-featured-category__link">
|
||||
<InnerBlocks
|
||||
template={ [
|
||||
[
|
||||
'core/button',
|
||||
{
|
||||
text: __(
|
||||
'Shop now',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
url: category.permalink,
|
||||
align: 'center',
|
||||
},
|
||||
],
|
||||
] }
|
||||
templateLock="all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableBox>
|
||||
) : (
|
||||
<Placeholder
|
||||
className="wc-block-featured-category"
|
||||
icon={ <IconFolderStar /> }
|
||||
label={ __( 'Featured Category', 'woo-gutenberg-products-block' ) }
|
||||
>
|
||||
{ ! loaded ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
__( 'No product category is selected.', 'woo-gutenberg-products-block' )
|
||||
) }
|
||||
</Placeholder>
|
||||
) }
|
||||
</Fragment>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FeaturedCategory.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
/**
|
||||
* Whether this block is currently active.
|
||||
*/
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
/**
|
||||
* The register block name.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A callback to update attributes.
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
// from withColors
|
||||
overlayColor: PropTypes.object,
|
||||
setOverlayColor: PropTypes.func.isRequired,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default compose( [
|
||||
withColors( { overlayColor: 'background-color' } ),
|
||||
withSpokenMessages,
|
||||
] )( FeaturedCategory );
|
|
@ -0,0 +1,18 @@
|
|||
.wc-block-featured-category {
|
||||
&.components-placeholder {
|
||||
// Reset the background for the placeholders.
|
||||
background-color: rgba( 139, 139, 150, .1 );
|
||||
}
|
||||
|
||||
.components-resizable-box__handle {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.components-placeholder__label svg {
|
||||
fill: currentColor;
|
||||
margin-right: 1ch;
|
||||
}
|
||||
}
|
||||
.wc-block-featured-category__selection {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InnerBlocks } from '@wordpress/editor';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import './editor.scss';
|
||||
import Block from './block';
|
||||
import { IconFolderStar } from '../../components/icons';
|
||||
|
||||
/**
|
||||
* Register and run the "Featured Category" block.
|
||||
*/
|
||||
registerBlockType( 'woocommerce/featured-category', {
|
||||
title: __( 'Featured Category', 'woo-gutenberg-products-block' ),
|
||||
icon: {
|
||||
src: <IconFolderStar />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
||||
description: __(
|
||||
'Visually highlight a product category and encourage prompt action.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
supports: {
|
||||
align: [ 'wide', 'full' ],
|
||||
},
|
||||
attributes: {
|
||||
/**
|
||||
* Alignment of content inside block.
|
||||
*/
|
||||
contentAlign: {
|
||||
type: 'string',
|
||||
default: 'center',
|
||||
},
|
||||
|
||||
/**
|
||||
* Percentage opacity of overlay.
|
||||
*/
|
||||
dimRatio: {
|
||||
type: 'number',
|
||||
default: 50,
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle for edit mode in the block preview.
|
||||
*/
|
||||
editMode: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus point for the background image
|
||||
*/
|
||||
focalPoint: {
|
||||
type: 'object',
|
||||
},
|
||||
|
||||
/**
|
||||
* A fixed height for the block.
|
||||
*/
|
||||
height: {
|
||||
type: 'number',
|
||||
default: wc_product_block_data.default_height,
|
||||
},
|
||||
|
||||
/**
|
||||
* ID for a custom image, overriding the product's featured image.
|
||||
*/
|
||||
mediaId: {
|
||||
type: 'number',
|
||||
default: 0,
|
||||
},
|
||||
|
||||
/**
|
||||
* URL for a custom image, overriding the product's featured image.
|
||||
*/
|
||||
mediaSrc: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* The overlay color, from the color list.
|
||||
*/
|
||||
overlayColor: {
|
||||
type: 'string',
|
||||
},
|
||||
|
||||
/**
|
||||
* The overlay color, if a custom color value.
|
||||
*/
|
||||
customOverlayColor: {
|
||||
type: 'string',
|
||||
},
|
||||
|
||||
/**
|
||||
* Text for the category link.
|
||||
*/
|
||||
linkText: {
|
||||
type: 'string',
|
||||
default: __( 'Shop now', 'woo-gutenberg-products-block' ),
|
||||
},
|
||||
|
||||
/**
|
||||
* The category ID to display.
|
||||
*/
|
||||
categoryId: {
|
||||
type: 'number',
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the category description.
|
||||
*/
|
||||
showDesc: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
return <Block { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Block content is rendered in PHP, not via save function.
|
||||
*/
|
||||
save() {
|
||||
return <InnerBlocks.Content />;
|
||||
},
|
||||
} );
|
|
@ -0,0 +1,130 @@
|
|||
.wc-block-featured-category {
|
||||
position: relative;
|
||||
background-color: $black;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
width: 100%;
|
||||
margin: 0 0 1.5em 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
|
||||
.wc-block-featured-category__wrapper {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
&.has-left-content {
|
||||
justify-content: flex-start;
|
||||
|
||||
.wc-block-featured-category__title,
|
||||
.wc-block-featured-category__description,
|
||||
.wc-block-featured-category__price {
|
||||
margin-left: 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-right-content {
|
||||
justify-content: flex-end;
|
||||
|
||||
.wc-block-featured-category__title,
|
||||
.wc-block-featured-category__description,
|
||||
.wc-block-featured-category__price {
|
||||
margin-right: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-featured-category__title,
|
||||
.wc-block-featured-category__description,
|
||||
.wc-block-featured-category__price {
|
||||
color: $white;
|
||||
line-height: 1.25;
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
|
||||
a,
|
||||
a:hover,
|
||||
a:focus,
|
||||
a:active {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-featured-category__title,
|
||||
.wc-block-featured-category__description,
|
||||
.wc-block-featured-category__price,
|
||||
.wc-block-featured-category__link {
|
||||
width: 100%;
|
||||
padding: 0 48px 16px 48px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wc-block-featured-category__title {
|
||||
margin-top: 0;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-featured-category__description {
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-background-dim::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: inherit;
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
&.has-background-dim.has-background-dim-#{ $i * 10 }::before {
|
||||
opacity: $i * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply max-width to floated items that have no intrinsic width
|
||||
&.alignleft,
|
||||
&.alignright {
|
||||
max-width: $content-width / 2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Using flexbox without an assigned height property breaks vertical center alignment in IE11.
|
||||
// Appending an empty ::after element tricks IE11 into giving the cover image an implicit height, which sidesteps this issue.
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
font-size: 0;
|
||||
min-height: inherit;
|
||||
|
||||
// IE doesn't support flex so omit that.
|
||||
@supports (position: sticky) {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Aligned cover blocks should not use our global alignment rules
|
||||
&.aligncenter,
|
||||
&.alignleft,
|
||||
&.alignright {
|
||||
display: flex;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon } from '@wordpress/components';
|
||||
|
||||
export default () => (
|
||||
<Icon
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M22 7.5H2c-1.2 0-2.1 1-2 2.2l.7 11.1c.1 1.1 1 1.9 2 1.9h18.5c1.1 0 2-.8 2-1.9L24 9.6c.1-1.1-.9-2.1-2-2.1zM13.1 2.8v-.4c0-1.1-.9-2-2-2H4.8c-1.1 0-2 .9-2 2v3.4h18.4v-1c0-1.1-.9-2-2-2h-6.1z" />
|
||||
<path fill="#fff" d="M14.4 18.7L12 17.4l-2.4 1.3.5-2.6-1.9-1.9 2.6-.4 1.2-2.4 1.2 2.4 2.6.4-1.9 1.9z" />
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
);
|
|
@ -2,6 +2,7 @@
|
|||
export { default as IconCheckChecked } from './checkbox-checked';
|
||||
export { default as IconCheckUnchecked } from './checkbox-unchecked';
|
||||
export { default as IconFolder } from './folder';
|
||||
export { default as IconFolderStar } from './folder-star';
|
||||
export { default as IconNewReleases } from './new-releases';
|
||||
export { default as IconRadioSelected } from './radio-selected';
|
||||
export { default as IconRadioUnselected } from './radio-unselected';
|
||||
|
|
|
@ -74,7 +74,7 @@ class ProductCategoryControl extends Component {
|
|||
|
||||
render() {
|
||||
const { list, loading } = this.state;
|
||||
const { onChange, onOperatorChange, operator, selected } = this.props;
|
||||
const { onChange, onOperatorChange, operator, selected, isSingle } = this.props;
|
||||
|
||||
const messages = {
|
||||
clear: __( 'Clear all product categories', 'woo-gutenberg-products-block' ),
|
||||
|
@ -114,6 +114,7 @@ class ProductCategoryControl extends Component {
|
|||
renderItem={ this.renderItem }
|
||||
messages={ messages }
|
||||
isHierarchical
|
||||
isSingle={ isSingle }
|
||||
/>
|
||||
{ ( !! onOperatorChange ) && (
|
||||
<div className={ selected.length < 2 ? 'screen-reader-text' : '' }>
|
||||
|
@ -158,10 +159,15 @@ ProductCategoryControl.propTypes = {
|
|||
* The list of currently selected category IDs.
|
||||
*/
|
||||
selected: PropTypes.array.isRequired,
|
||||
/**
|
||||
* Allow only a single selection. Defaults to false.
|
||||
*/
|
||||
isSingle: PropTypes.bool,
|
||||
};
|
||||
|
||||
ProductCategoryControl.defaultProps = {
|
||||
operator: 'any',
|
||||
isSingle: false,
|
||||
};
|
||||
|
||||
export default ProductCategoryControl;
|
||||
|
|
|
@ -47,6 +47,7 @@ class Assets {
|
|||
self::register_script( 'wc-product-top-rated', plugins_url( 'build/product-top-rated.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) );
|
||||
self::register_script( 'wc-products-by-attribute', plugins_url( 'build/products-by-attribute.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) );
|
||||
self::register_script( 'wc-featured-product', plugins_url( 'build/featured-product.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) );
|
||||
self::register_script( 'wc-featured-category', plugins_url( 'build/featured-category.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) );
|
||||
self::register_script( 'wc-product-categories', plugins_url( 'build/product-categories.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
/**
|
||||
* Featured category block.
|
||||
*
|
||||
* @package WooCommerce\Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* FeaturedCategory class.
|
||||
*/
|
||||
class FeaturedCategory extends AbstractDynamicBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'featured-category';
|
||||
|
||||
/**
|
||||
* Default attribute values, should match what's set in JS `registerBlockType`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
'align' => 'none',
|
||||
'contentAlign' => 'center',
|
||||
'dimRatio' => 50,
|
||||
'focalPoint' => false,
|
||||
'height' => false,
|
||||
'mediaId' => 0,
|
||||
'mediaSrc' => '',
|
||||
'showDesc' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Render the Featured Category block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function render( $attributes = array(), $content = '' ) {
|
||||
$id = isset( $attributes['categoryId'] ) ? (int) $attributes['categoryId'] : 0;
|
||||
$category = get_term( $id, 'product_cat' );
|
||||
if ( ! $category ) {
|
||||
return '';
|
||||
}
|
||||
$attributes = wp_parse_args( $attributes, $this->defaults );
|
||||
if ( ! $attributes['height'] ) {
|
||||
$attributes['height'] = wc_get_theme_support( 'featured_block::default_height', 500 );
|
||||
}
|
||||
|
||||
$title = sprintf(
|
||||
'<h2 class="wc-block-featured-category__title">%s</h2>',
|
||||
wp_kses_post( $category->name )
|
||||
);
|
||||
|
||||
$desc_str = sprintf(
|
||||
'<div class="wc-block-featured-category__description">%s</div>',
|
||||
wc_format_content( $category->description )
|
||||
);
|
||||
|
||||
$output = sprintf( '<div class="%1$s" style="%2$s">', $this->get_classes( $attributes ), $this->get_styles( $attributes, $category ) );
|
||||
|
||||
$output .= $title;
|
||||
if ( $attributes['showDesc'] ) {
|
||||
$output .= $desc_str;
|
||||
}
|
||||
$output .= '<div class="wc-block-featured-category__link">' . $content . '</div>';
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the styles for the wrapper element (background image, color).
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param \WP_Term $category Term object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_styles( $attributes, $category ) {
|
||||
$style = '';
|
||||
$image_size = 'large';
|
||||
if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
|
||||
$image_size = 'full';
|
||||
}
|
||||
|
||||
if ( $attributes['mediaId'] ) {
|
||||
$image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
|
||||
} else {
|
||||
$image = $this->get_image( $category, $image_size );
|
||||
}
|
||||
|
||||
if ( ! empty( $image ) ) {
|
||||
$style .= sprintf( 'background-image:url(%s);', esc_url( $image ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['customOverlayColor'] ) ) {
|
||||
$style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['height'] ) ) {
|
||||
$style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) );
|
||||
}
|
||||
|
||||
if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) {
|
||||
$style .= sprintf(
|
||||
'background-position: %s%% %s%%',
|
||||
$attributes['focalPoint']['x'] * 100,
|
||||
$attributes['focalPoint']['y'] * 100
|
||||
);
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class names for the block container.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
public function get_classes( $attributes ) {
|
||||
$classes = array( 'wc-block-' . $this->block_name );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
|
||||
$classes[] = 'has-background-dim';
|
||||
|
||||
if ( 50 !== $attributes['dimRatio'] ) {
|
||||
$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
|
||||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['overlayColor'] ) ) {
|
||||
$classes[] = "has-{$attributes['overlayColor']}-background-color";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
return implode( $classes, ' ' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main product image URL.
|
||||
*
|
||||
* @param \WP_Term $category Term object.
|
||||
* @param string $size Image size, defaults to 'full'.
|
||||
* @return string
|
||||
*/
|
||||
public function get_image( $category, $size = 'full' ) {
|
||||
$image = '';
|
||||
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
|
||||
|
||||
if ( $image_id ) {
|
||||
$image = wp_get_attachment_image_url( $image_id, $size );
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ class Library {
|
|||
*/
|
||||
public static function register_blocks() {
|
||||
$blocks = [
|
||||
'FeaturedCategory',
|
||||
'FeaturedProduct',
|
||||
'HandpickedProducts',
|
||||
'ProductBestSellers',
|
||||
|
|
|
@ -107,13 +107,33 @@ class ProductCategories extends WC_REST_Product_Categories_Controller {
|
|||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = array(
|
||||
'id' => (int) $item->term_id,
|
||||
'name' => $item->name,
|
||||
'slug' => $item->slug,
|
||||
'parent' => (int) $item->parent,
|
||||
'count' => (int) $item->count,
|
||||
'id' => (int) $item->term_id,
|
||||
'name' => $item->name,
|
||||
'slug' => $item->slug,
|
||||
'parent' => (int) $item->parent,
|
||||
'count' => (int) $item->count,
|
||||
'description' => $item->description,
|
||||
'image' => null,
|
||||
'permalink' => get_term_link( $item->term_id, 'product_cat' ),
|
||||
);
|
||||
|
||||
$image_id = get_term_meta( $item->term_id, 'thumbnail_id', true );
|
||||
|
||||
if ( $image_id ) {
|
||||
$attachment = get_post( $image_id );
|
||||
|
||||
$data['image'] = array(
|
||||
'id' => (int) $image_id,
|
||||
'date_created' => wc_rest_prepare_date_response( $attachment->post_date ),
|
||||
'date_created_gmt' => wc_rest_prepare_date_response( $attachment->post_date_gmt ),
|
||||
'date_modified' => wc_rest_prepare_date_response( $attachment->post_modified ),
|
||||
'date_modified_gmt' => wc_rest_prepare_date_response( $attachment->post_modified_gmt ),
|
||||
'src' => wp_get_attachment_url( $image_id ),
|
||||
'name' => get_the_title( $attachment ),
|
||||
'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ),
|
||||
);
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
@ -138,11 +158,20 @@ class ProductCategories extends WC_REST_Product_Categories_Controller {
|
|||
'properties' => array(),
|
||||
);
|
||||
|
||||
$schema['properties']['id'] = $raw_schema['properties']['id'];
|
||||
$schema['properties']['name'] = $raw_schema['properties']['name'];
|
||||
$schema['properties']['slug'] = $raw_schema['properties']['slug'];
|
||||
$schema['properties']['parent'] = $raw_schema['properties']['parent'];
|
||||
$schema['properties']['count'] = $raw_schema['properties']['count'];
|
||||
$schema['properties']['id'] = $raw_schema['properties']['id'];
|
||||
$schema['properties']['name'] = $raw_schema['properties']['name'];
|
||||
$schema['properties']['slug'] = $raw_schema['properties']['slug'];
|
||||
$schema['properties']['parent'] = $raw_schema['properties']['parent'];
|
||||
$schema['properties']['count'] = $raw_schema['properties']['count'];
|
||||
$schema['properties']['description'] = $raw_schema['properties']['description'];
|
||||
$schema['properties']['image'] = $raw_schema['properties']['image'];
|
||||
$schema['properties']['permalink'] = array(
|
||||
'description' => __( 'Category URL.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ const GutenbergBlocksConfig = {
|
|||
'product-top-rated': './assets/js/blocks/product-top-rated/index.js',
|
||||
'products-by-attribute': './assets/js/blocks/products-by-attribute/index.js',
|
||||
'featured-product': './assets/js/blocks/featured-product/index.js',
|
||||
'featured-category': './assets/js/blocks/featured-category/index.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve( __dirname, './build/' ),
|
||||
|
|
Loading…
Reference in New Issue