Skip to content

Commit 88f8689

Browse files
nosoloswjeherve
andcommitted
Add ratings block (#14091)
* [not verified] Add initial ratings block * [not verified] Tell Jetpack to load assets * [not verified] Improve code * [not verified] Fix load name: it should be the folder * Refactor to extract the code that is general and can be potentially used to create other rating blocks. * Document and split Jetpack specific and general code * Fix assets name: it uses the folder * Remove spiciness and priciness from client side * Rename to rating-star * Address code climate issues * Add jetpack textdomain + extract name and settings to index.js * Remove any trace of spiciness and priciness * Fix translations and add keywords * Use jetpack variable for margin bottom * Use gutenberg variable for radius * Fix name * Check that functions dont exist before declaring them * Fix wrong call * Make rating-meta the same in jetpack and wpcom-blocks * Remove any trace of price/spice * Update comments to remove mentions of the Business Hours block Co-authored-by: Jeremy Herve <jeremy@jeremy.hu>
1 parent 22a5558 commit 88f8689

File tree

12 files changed

+523
-0
lines changed

12 files changed

+523
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { range } from 'lodash';
5+
6+
/**
7+
* WordPress dependencies
8+
*/
9+
import { __ } from '@wordpress/i18n';
10+
import {
11+
AlignmentToolbar,
12+
BlockControls,
13+
InspectorControls,
14+
PanelColorSettings,
15+
} from '@wordpress/block-editor';
16+
import { PanelBody, RangeControl } from '@wordpress/components';
17+
import { ENTER } from '@wordpress/keycodes';
18+
19+
const Rating = ( { id, setRating, children } ) => {
20+
const setNewRating = newRating => () => setRating( newRating );
21+
const maybeSetNewRating = newRating => ( { keyCode } ) =>
22+
keyCode === ENTER ? setRating( newRating ) : null;
23+
24+
return (
25+
<span
26+
className="jetpack-ratings-button"
27+
tabIndex={ 0 }
28+
role="button"
29+
onKeyDown={ maybeSetNewRating( id ) }
30+
onClick={ setNewRating( id ) }
31+
>
32+
{ children }
33+
</span>
34+
);
35+
};
36+
37+
export default Symbol =>
38+
function( { className, setAttributes, attributes: { align, color, rating, maxRating } } ) {
39+
const setNewMaxRating = newMaxRating => setAttributes( { maxRating: newMaxRating } );
40+
const setNewColor = newColor => setAttributes( { color: newColor } );
41+
const setNewRating = newRating => {
42+
if ( newRating === rating ) {
43+
// Same number clicked twice.
44+
// Check if a half rating.
45+
if ( Math.ceil( rating ) === rating ) {
46+
// Whole number.
47+
newRating = newRating - 0.5;
48+
}
49+
}
50+
setAttributes( { rating: newRating } );
51+
};
52+
53+
return (
54+
<>
55+
<BlockControls>
56+
<AlignmentToolbar
57+
value={ align }
58+
onChange={ nextAlign => setAttributes( { align: nextAlign } ) }
59+
/>
60+
</BlockControls>
61+
<div className={ className } style={ { textAlign: align } }>
62+
{ range( 1, maxRating + 1 ).map( position => (
63+
<Rating key={ position } id={ position } setRating={ setNewRating }>
64+
<span>
65+
<Symbol
66+
className={ rating >= position - 0.5 ? null : 'is-rating-unfilled' }
67+
color={ color }
68+
/>
69+
</span>
70+
<span>
71+
<Symbol
72+
className={ rating >= position ? null : 'is-rating-unfilled' }
73+
color={ color }
74+
/>
75+
</span>
76+
</Rating>
77+
) ) }
78+
</div>
79+
<InspectorControls>
80+
<PanelBody title={ __( 'Settings', 'jetpack' ) }>
81+
<RangeControl
82+
label={ __( 'Highest rating', 'jetpack' ) }
83+
value={ maxRating }
84+
onChange={ setNewMaxRating }
85+
min={ 2 }
86+
max={ 10 }
87+
/>
88+
<PanelColorSettings
89+
title={ __( 'Color Settings', 'jetpack' ) }
90+
initialOpen
91+
colorSettings={ [
92+
{
93+
value: color,
94+
onChange: setNewColor,
95+
label: __( 'Color', 'jetpack' ),
96+
},
97+
] }
98+
/>
99+
</PanelBody>
100+
</InspectorControls>
101+
</>
102+
);
103+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Internal dependencies
3+
*/
4+
import registerJetpackBlock from '../../shared/register-jetpack-block';
5+
import { name, settings } from '.';
6+
7+
registerJetpackBlock( name, settings );
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Editor only styles
2+
.jetpack-ratings-button {
3+
cursor: pointer;
4+
}
5+
6+
// Control symbol outline when focus and hover.
7+
.jetpack-ratings-button {
8+
&:focus {
9+
border: none;
10+
outline: none;
11+
}
12+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { Path, SVG } from '@wordpress/components';
5+
6+
export const StarBlockIcon = () => {
7+
return (
8+
<SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
9+
<Path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4V6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" />
10+
</SVG>
11+
);
12+
};
13+
14+
const getColor = props => ( props && props.color ? props.color : 'currentColor' );
15+
const getClassName = props => ( props && props.className ? props.className : '' );
16+
17+
export const StarIcon = props => {
18+
const color = getColor( props );
19+
const className = getClassName( props );
20+
21+
return (
22+
<SVG
23+
xmlns="http://www.w3.org/2000/svg"
24+
width="24"
25+
height="24"
26+
viewBox="0 0 24 24"
27+
color={ color } // this is to fix the stroke color in the ".is-style-filled svg:hover .is-rating-unfilled" rule
28+
>
29+
<Path
30+
className={ className }
31+
fill={ color }
32+
stroke={ color }
33+
d="M12,17.3l6.2,3.7l-1.6-7L22,9.2l-7.2-0.6L12,2L9.2,8.6L2,9.2L7.5,14l-1.6,7L12,17.3z"
34+
/>
35+
</SVG>
36+
);
37+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { __, _x } from '@wordpress/i18n';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import edit from './edit';
10+
import save from './save';
11+
import { StarIcon, StarBlockIcon } from './icon';
12+
import './editor.scss';
13+
import './style.scss';
14+
15+
export const name = 'rating-star';
16+
17+
export const settings = {
18+
title: 'Star Rating',
19+
description: __(
20+
'Rate movies, books, songs, recipes — anything you can put a number on.',
21+
'jetpack'
22+
),
23+
icon: StarBlockIcon,
24+
keywords: [
25+
_x( 'star', 'block search term', 'jetpack' ),
26+
_x( 'rating', 'block search term', 'jetpack' ),
27+
_x( 'review', 'block search term', 'jetpack' ),
28+
],
29+
category: 'jetpack',
30+
example: {},
31+
styles: [
32+
{
33+
name: 'default',
34+
label: _x( 'Default', 'block style', 'jetpack' ),
35+
isDefault: true,
36+
},
37+
{
38+
name: 'outlined',
39+
label: _x( 'Outlined', 'block style', 'jetpack' ),
40+
},
41+
],
42+
attributes: {
43+
rating: {
44+
type: 'number',
45+
default: 1,
46+
},
47+
maxRating: {
48+
type: 'number',
49+
default: 5,
50+
},
51+
color: {
52+
type: 'string',
53+
},
54+
align: {
55+
type: 'string',
56+
default: 'left',
57+
},
58+
},
59+
edit: edit( StarIcon ),
60+
save: save( '★' ), // Fallback symbol if the block is removed or the render_callback deactivated.
61+
};
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/**
3+
* Utilities for the rating block.
4+
*
5+
* @since 8.0.0
6+
*
7+
* @package Jetpack
8+
*/
9+
10+
if ( ! function_exists( 'jetpack_rating_meta_get_symbol_low_fidelity' ) ) {
11+
/**
12+
* Returns the low fidelity symbol for the block.
13+
*
14+
* @return string
15+
*/
16+
function jetpack_rating_meta_get_symbol_low_fidelity() {
17+
return '';
18+
}
19+
}
20+
21+
if ( ! function_exists( 'jetpack_rating_star_get_symbol_high_fidelity' ) ) {
22+
/**
23+
* Return the high fidelity symbol for the block.
24+
*
25+
* @param string $classname_whole Name of the whole symbol class.
26+
* @param string $classname_half Name of the half symbol class.
27+
* @param string $color Color of the block.
28+
*
29+
* @return string
30+
*/
31+
function jetpack_rating_star_get_symbol_high_fidelity( $classname_whole, $classname_half, $color ) {
32+
return <<<ELO
33+
<span>
34+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
35+
<path class="{$classname_whole}" fill="{$color}" stroke="{$color}" d="M12,17.3l6.2,3.7l-1.6-7L22,9.2l-7.2-0.6L12,2L9.2,8.6L2,9.2L7.5,14l-1.6,7L12,17.3z" />
36+
</svg>
37+
</span>
38+
<span>
39+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
40+
<path class="{$classname_half}" fill="{$color}" stroke="{$color}" d="M12,17.3l6.2,3.7l-1.6-7L22,9.2l-7.2-0.6L12,2L9.2,8.6L2,9.2L7.5,14l-1.6,7L12,17.3z" />
41+
</svg>
42+
</span>
43+
ELO;
44+
}
45+
}
46+
47+
if ( ! function_exists( 'jetpack_rating_meta_get_symbol_high_fidelity' ) ) {
48+
/**
49+
* Returns the high fidelity symbol for the block.
50+
*
51+
* @param array $attributes Array containing the block attributes.
52+
* @param integer $pos Value to render whole and half symbols.
53+
* @return string
54+
*/
55+
function jetpack_rating_meta_get_symbol_high_fidelity( $attributes, $pos ) {
56+
$classname_whole = ( $attributes['rating'] >= ( $pos - 0.5 ) ) ? '' : 'is-rating-unfilled';
57+
$classname_half = ( $attributes['rating'] >= $pos ) ? '' : 'is-rating-unfilled';
58+
$color = empty( $attributes['color'] ) ? 'currentColor' : esc_attr( $attributes['color'] );
59+
60+
return jetpack_rating_star_get_symbol_high_fidelity( $classname_whole, $classname_half, $color );
61+
}
62+
}
63+
64+
if ( ! function_exists( 'jetpack_rating_meta_get_symbols' ) ) {
65+
/**
66+
* Returns the symbol for the block.
67+
*
68+
* @param array $attributes Array containing the block attributes.
69+
*
70+
* @return string
71+
*/
72+
function jetpack_rating_meta_get_symbols( $attributes ) {
73+
// Output SVGs for high fidelity contexts, then color them according to rating.
74+
// These are hidden by default, then unhid when CSS loads.
75+
$symbols_hifi = array();
76+
for ( $pos = 1; $pos <= $attributes['maxRating']; $pos++ ) {
77+
$symbols_hifi[] = '<span style="display: none;">' . jetpack_rating_meta_get_symbol_high_fidelity( $attributes, $pos ) . '</span>';
78+
}
79+
80+
// Output fallback symbols for low fidelity contexts, like AMP,
81+
// where CSS is not loaded so the high-fidelity symbols won't be rendered.
82+
$symbols_lofi = '';
83+
for ( $i = 0; $i < $attributes['rating']; $i++ ) {
84+
$symbols_lofi .= jetpack_rating_meta_get_symbol_low_fidelity();
85+
}
86+
87+
return '<p>' . $symbols_lofi . '</p>' . implode( $symbols_hifi );
88+
}
89+
}
90+
91+
if ( ! function_exists( 'jetpack_rating_meta_render_block' ) ) {
92+
/**
93+
* Dynamic rendering of the block.
94+
*
95+
* @param array $attributes Array containing the block attributes.
96+
*
97+
* @return string
98+
*/
99+
function jetpack_rating_meta_render_block( $attributes ) {
100+
$classname = empty( $attributes['className'] ) ? '' : ' ' . $attributes['className'];
101+
return sprintf(
102+
'<div class="%1$s" style="text-align:%3$s">%2$s</div>',
103+
esc_attr( 'wp-block-jetpack-rating-' . $attributes['ratingStyle'] . $classname ),
104+
jetpack_rating_meta_get_symbols( $attributes ),
105+
( isset( $attributes['align'] ) ) ? esc_attr( $attributes['align'] ) : ''
106+
);
107+
}
108+
}

0 commit comments

Comments
 (0)