Skip to content
This repository was archived by the owner on Oct 4, 2022. It is now read-only.

Commit 2d242c7

Browse files
authored
Merge pull request #1118 from Yoast/P1-345-Woo-shopping-data-in-google-preview
P1 345 woo shopping data in google preview
2 parents 1f7a14e + 0d8b89d commit 2d242c7

File tree

14 files changed

+269
-27710
lines changed

14 files changed

+269
-27710
lines changed

apps/components/ComponentsExample.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { __ } from "@wordpress/i18n";
22

3-
import { Alert, CourseDetails, FullHeightCard, Warning } from "@yoast/components";
3+
import { Alert, CourseDetails, FullHeightCard, StarRating, Warning } from "@yoast/components";
44
import { getCourseFeed, getDirectionalStyle, makeOutboundLink } from "@yoast/helpers";
55
import React from "react";
66
import styled from "styled-components";
@@ -138,6 +138,12 @@ export default class ComponentsExample extends React.Component {
138138
</Alert>;
139139
}
140140

141+
updateStars( event ) {
142+
this.setState({
143+
input: event.target.value
144+
} );
145+
}
146+
141147
/**
142148
* Renders all the Component examples.
143149
*
@@ -171,6 +177,9 @@ export default class ComponentsExample extends React.Component {
171177
<p key="2">This spans to multiple lines.</p>,
172178
] }
173179
/>
180+
<h2>Star rating</h2>
181+
<i>Accepts a rating from 0-5 and colors the stars yellow accordingly</i>
182+
<StarRating rating={ 3.5 } />
174183
<h2>Outbound links</h2>
175184
<p>
176185
<NonYoastLink href="http://www.example.org">example.org</NonYoastLink>

packages/components/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from "./inputs";
1313
export * from "./insights-card";
1414
export * from "./radiobutton";
1515
export * from "./select";
16+
export * from "./star-rating";
1617
export * from "./help-icon";
1718
export * from "./tables";
1819

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
4+
/**
5+
* Renders StarRating component.
6+
*
7+
* @param {Object} props The props.
8+
*
9+
* @returns {React.Component} The StarRating Component displays a number between 0 and 5 as (partly) colored stars.
10+
*/
11+
function StarRating( props ) {
12+
let rating = props.rating;
13+
14+
// As we have 5 stars, the rating should be between 0 and 5.
15+
if ( rating < 0 ) {
16+
rating = 0;
17+
}
18+
19+
if ( rating > 5 ) {
20+
rating = 5;
21+
}
22+
23+
const ratingPercentage = rating * 20;
24+
25+
return (
26+
<div
27+
aria-hidden="true"
28+
className="yoast-star-rating"
29+
>
30+
<span className="yoast-star-rating__placeholder" role="img">
31+
<span className="yoast-star-rating__fill" style={ { width: ratingPercentage + "%" } } />
32+
</span>
33+
</div>
34+
);
35+
}
36+
37+
export default StarRating;
38+
39+
StarRating.propTypes = {
40+
rating: PropTypes.number.isRequired,
41+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import "./star-rating.css";
2+
3+
export { default as StarRating } from "./StarRating.js";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.yoast-star-rating {
2+
width: 65px;
3+
height: 12px;
4+
display: inline-block;
5+
}
6+
7+
.yoast-star-rating span {
8+
height: 100%;
9+
width: 100%;
10+
background-size: 13px 12px;
11+
background-repeat: repeat-x;
12+
}
13+
14+
.yoast-star-rating__placeholder {
15+
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAmCAQAAAAYCMGrAAAA+klEQVR4AcWV4cbtMBBFF0MIVUopoVSrhDDv/3gf/RFRpzdNOty1HiBO99mzeYWgCMZMKCPGrCgrxiSUhCkDeukxJKCXAUMiehkxw6FZhxEzmp0x4kCzByYISqlYdal0supS6WrVpdLEK0YSamJiJOPY0c/uOG4s6CcXfuKJaJcRzyNCQJsNiF1sRTR1hP11NNJ8RCrONOPRf+r7J+TZgQ5CNfMOYvW/2YxDqzqA/57+gVY9eiakrnyZEGXDsaE3p/4JScwPX3rtnZATDxnPWT7X16XAHaH8HWNrlxJD9TyGti5tCM84zpZe+RxNjeX9tZqLaGoMxN/P/wHP5Vw+8ZxnEQAAAABJRU5ErkJggg==);
16+
display: inline-block;
17+
overflow: hidden;
18+
position: relative;
19+
}
20+
21+
.yoast-star-rating__fill {
22+
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAmBAMAAABALxQTAAAAFVBMVEVMaXH4twP4twP4twP4twP4twP4twP7w8S/AAAAB3RSTlMAFv5uPpvQloUsTQAAAMFJREFUeAGE0TEOgzAMQFEXoDNiYC6/wFxxAsTADDkB5f6HqNRENXUi8TYiRfnY8lNXkjBOkuBWSeAhsYJOYiW9xO4MEqshkTbCSyIH7GLdgFasHHgmwkikZQD6OROZRG4Hxju8o/TNhbNhCqkOxaZDVKdxNnq/EjUS/A2o0PuXpyVeb9bjDWY9QSWXDQfBbtbjtWY9bM4sqfx+5yYt8wNcAFEzrGGkk5668KsFrKewPtQ3aFqh8WOnYZ+lIBQkgykAWk8rlAqcHfQAAAAASUVORK5CYII=);
23+
display: block;
24+
}

packages/search-metadata-previews/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Snippet preview exports.
22
export { default as FixedWidthContainer } from "./snippet-preview/FixedWidthContainer";
33
export { default as HelpTextWrapper } from "./snippet-preview/HelpTextWrapper";
4-
export { default as SnippetPreview } from "./snippet-preview/SnippetPreview";
4+
export * from "./snippet-preview/SnippetPreview";
55

66
// Snippet editor exports.
77
export {

packages/search-metadata-previews/src/snippet-editor/SnippetEditor.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ class SnippetEditor extends React.Component {
518518
faviconSrc,
519519
mobileImageSrc,
520520
idSuffix,
521+
shoppingData,
521522
} = this.props;
522523

523524
const {
@@ -556,6 +557,7 @@ class SnippetEditor extends React.Component {
556557
locale={ locale }
557558
faviconSrc={ faviconSrc }
558559
mobileImageSrc={ mobileImageSrc }
560+
shoppingData={ shoppingData }
559561
{ ...mappedData }
560562
/>
561563

@@ -601,6 +603,7 @@ SnippetEditor.propTypes = {
601603
faviconSrc: PropTypes.string,
602604
mobileImageSrc: PropTypes.string,
603605
idSuffix: PropTypes.string,
606+
shoppingData: PropTypes.object,
604607
};
605608

606609
SnippetEditor.defaultProps = {
@@ -629,6 +632,7 @@ SnippetEditor.defaultProps = {
629632
faviconSrc: "",
630633
mobileImageSrc: "",
631634
idSuffix: "",
635+
shoppingData: {},
632636
};
633637

634638
export default SnippetEditor;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { Fragment } from "react";
2+
import PropTypes from "prop-types";
3+
import styled from "styled-components";
4+
import { __, sprintf } from "@wordpress/i18n";
5+
import { round } from "lodash";
6+
import { StarRating } from "@yoast/components";
7+
8+
const ProductData = styled.span`
9+
color: #70757a;
10+
`;
11+
12+
/**
13+
* Renders ProductData component.
14+
*
15+
* @param {Object} props The props.
16+
*
17+
* @returns {React.Component} The StarRating Component.
18+
*/
19+
function ProductDataDesktop( props ) {
20+
const { shoppingData } = props;
21+
22+
/* Translators: %s expands to the actual rating, e.g. 8/10. */
23+
const ratingPart = sprintf( __( "Rating: %s", "yoast-components" ), round( ( shoppingData.rating * 2 ), 1 ) + "/10" );
24+
25+
/* Translators: %s expands to the review count. */
26+
const reviewPart = sprintf( __( "%s reviews", "yoast-components" ), shoppingData.reviewCount );
27+
28+
return (
29+
<ProductData>
30+
{ ( shoppingData.reviewCount > 0 ) &&
31+
<Fragment>
32+
<StarRating rating={ shoppingData.rating } />
33+
<span> { ratingPart } · </span>
34+
<span>{ reviewPart } · </span>
35+
</Fragment>
36+
}
37+
{ shoppingData.price &&
38+
<Fragment>
39+
<span dangerouslySetInnerHTML={ { __html: shoppingData.price } } />
40+
</Fragment>
41+
}
42+
{ shoppingData.availability &&
43+
<span> · { shoppingData.availability }</span> }
44+
</ProductData>
45+
);
46+
}
47+
48+
export default ProductDataDesktop;
49+
50+
ProductDataDesktop.propTypes = {
51+
shoppingData: PropTypes.shape( {
52+
rating: PropTypes.number,
53+
reviewCount: PropTypes.number,
54+
availability: PropTypes.string,
55+
price: PropTypes.string,
56+
} ).isRequired,
57+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import styled from "styled-components";
4+
import { __ } from "@wordpress/i18n";
5+
import { round } from "lodash";
6+
7+
import { StarRating } from "@yoast/components";
8+
9+
const ProductData = styled.div`
10+
display: flex;
11+
`;
12+
13+
const ProductDataCell50 = styled.div`
14+
flex: 1;
15+
max-width: 50%;
16+
`;
17+
18+
const ProductDataCell25 = styled.div`
19+
flex: 1;
20+
max-width: 25%;
21+
`;
22+
23+
const ProductDataInnerLower = styled.div`
24+
color: #70757a;
25+
`;
26+
27+
/**
28+
* Renders ProductData component.
29+
*
30+
* @param {Object} props The props.
31+
*
32+
* @returns {React.Component} The StarRating Component.
33+
*/
34+
function ProductDataMobile( props ) {
35+
const { shoppingData } = props;
36+
37+
return (
38+
<ProductData>
39+
{ ( shoppingData.rating > 0 ) &&
40+
<ProductDataCell50 className="yoast-shopping-data-preview__column">
41+
<div className="yoast-shopping-data-preview__upper">{ __( "Rating", "yoast-components" ) }</div>
42+
<ProductDataInnerLower className="yoast-shopping-data-preview__lower">
43+
<span>{ round( ( shoppingData.rating * 2 ), 1 ) }/10 </span>
44+
<StarRating rating={ shoppingData.rating } />
45+
<span> ({ shoppingData.reviewCount })</span>
46+
</ProductDataInnerLower>
47+
</ProductDataCell50>
48+
}
49+
{ ( shoppingData.price ) &&
50+
<ProductDataCell25 className="yoast-shopping-data-preview__column">
51+
<div className="yoast-shopping-data-preview__upper">{ __( "Price", "yoast-components" ) }</div>
52+
<ProductDataInnerLower
53+
className="yoast-shopping-data-preview__lower"
54+
dangerouslySetInnerHTML={ { __html: shoppingData.price } }
55+
/>
56+
</ProductDataCell25>
57+
}
58+
{ ( shoppingData.availability ) &&
59+
<ProductDataCell25 className="yoast-shopping-data-preview__column">
60+
<div className="yoast-shopping-data-preview__upper">{ __( "Availability", "yoast-components" ) }</div>
61+
<ProductDataInnerLower className="yoast-shopping-data-preview__lower">
62+
{ shoppingData.availability }
63+
</ProductDataInnerLower>
64+
</ProductDataCell25>
65+
}
66+
</ProductData>
67+
);
68+
}
69+
70+
export default ProductDataMobile;
71+
72+
ProductDataMobile.propTypes = {
73+
shoppingData: PropTypes.shape( {
74+
rating: PropTypes.number,
75+
reviewCount: PropTypes.number,
76+
availability: PropTypes.string,
77+
price: PropTypes.string,
78+
} ).isRequired,
79+
};

packages/search-metadata-previews/src/snippet-preview/SnippetPreview.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const {
2121

2222
// Internal dependencies.
2323
import FixedWidthContainer from "./FixedWidthContainer";
24+
import ProductDataDesktop from "./ProductDataDesktop";
25+
import ProductDataMobile from "./ProductDataMobile";
2426
import { DEFAULT_MODE, MODE_DESKTOP, MODE_MOBILE, MODES } from "./constants";
2527

2628
/*
@@ -722,6 +724,37 @@ export default class SnippetPreview extends PureComponent {
722724
return null;
723725
}
724726

727+
/**
728+
* Renders the product / shopping data, in mobile or desktop view, based on the mode.
729+
*
730+
* @returns {ReactElement} The rendered description.
731+
*/
732+
renderProductData() {
733+
const { mode, shoppingData } = this.props;
734+
735+
if ( Object.values( shoppingData ).length === 0 ) {
736+
return null;
737+
}
738+
739+
if ( mode === MODE_DESKTOP ) {
740+
return (
741+
<ProductDataDesktop
742+
shoppingData={ shoppingData }
743+
/>
744+
);
745+
}
746+
747+
if ( mode === MODE_MOBILE ) {
748+
return (
749+
<ProductDataMobile
750+
shoppingData={ shoppingData }
751+
/>
752+
);
753+
}
754+
755+
return null;
756+
}
757+
725758
/**
726759
* Renders the snippet preview.
727760
*
@@ -783,12 +816,24 @@ export default class SnippetPreview extends PureComponent {
783816
</SnippetTitle>
784817
{ amp }
785818
</PartContainer>
819+
<PartContainer className="yoast-shopping-data-preview--desktop">
820+
<ScreenReaderText>
821+
{ __( "Shopping data preview:", "yoast-components" ) }
822+
</ScreenReaderText>
823+
{ isDesktopMode && this.renderProductData() }
824+
</PartContainer>
786825
<PartContainer>
787826
<ScreenReaderText>
788-
{ __( "Meta description preview", "yoast-components" ) + ":" }
827+
{ __( "Meta description preview:", "yoast-components" ) }
789828
</ScreenReaderText>
790829
{ this.renderDescription() }
791830
</PartContainer>
831+
<PartContainer className="yoast-shopping-data-preview--mobile">
832+
<ScreenReaderText>
833+
{ __( "Shopping data preview:", "yoast-components" ) }
834+
</ScreenReaderText>
835+
{ ! isDesktopMode && this.renderProductData() }
836+
</PartContainer>
792837
</Container>
793838
</section>
794839
);
@@ -837,6 +882,7 @@ SnippetPreview.propTypes = {
837882
isAmp: PropTypes.bool,
838883
faviconSrc: PropTypes.string,
839884
mobileImageSrc: PropTypes.string,
885+
shoppingData: PropTypes.object,
840886

841887
onMouseUp: PropTypes.func.isRequired,
842888
onHover: PropTypes.func,
@@ -856,6 +902,7 @@ SnippetPreview.defaultProps = {
856902
isAmp: false,
857903
faviconSrc: "",
858904
mobileImageSrc: "",
905+
shoppingData: {},
859906

860907
onHover: () => {},
861908
onMouseEnter: () => {},

0 commit comments

Comments
 (0)