initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,3 @@
export { default as ReviewList } from './review-list';
export { default as ReviewListItem } from './review-list-item';
export { default as ReviewSortSelect } from './review-sort-select';

View File

@ -0,0 +1,207 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import ReadMore from '@woocommerce/base-components/read-more';
/**
* Internal dependencies
*/
import './style.scss';
function getReviewImage( review, imageType, isLoading ) {
if ( isLoading || ! review ) {
return (
<div className="wc-block-review-list-item__image wc-block-components-review-list-item__image" />
);
}
return (
<div className="wc-block-review-list-item__image wc-block-components-review-list-item__image">
{ imageType === 'product' ? (
<img
aria-hidden="true"
alt={ review.product_image?.alt || '' }
src={ review.product_image?.thumbnail || '' }
/>
) : (
<img
aria-hidden="true"
alt=""
src={ review.reviewer_avatar_urls[ '96' ] || '' }
/>
) }
{ review.verified && (
<div
className="wc-block-review-list-item__verified wc-block-components-review-list-item__verified"
title={ __(
'Verified buyer',
'woocommerce'
) }
>
{ __( 'Verified buyer', 'woocommerce' ) }
</div>
) }
</div>
);
}
function getReviewContent( review ) {
return (
<ReadMore
maxLines={ 10 }
moreText={ __(
'Read full review',
'woocommerce'
) }
lessText={ __(
'Hide full review',
'woocommerce'
) }
className="wc-block-review-list-item__text wc-block-components-review-list-item__text"
>
<div
dangerouslySetInnerHTML={ {
// `content` is the `review` parameter returned by the `reviews` endpoint.
// It's filtered with `wp_filter_post_kses()`, which removes dangerous HTML tags,
// so using it inside `dangerouslySetInnerHTML` is safe.
__html: review.review || '',
} }
/>
</ReadMore>
);
}
function getReviewProductName( review ) {
return (
<div className="wc-block-review-list-item__product wc-block-components-review-list-item__product">
<a
href={ review.product_permalink }
dangerouslySetInnerHTML={ {
// `product_name` might have html entities for things like
// emdash. So to display properly we need to allow the
// browser to render.
__html: review.product_name,
} }
/>
</div>
);
}
function getReviewerName( review ) {
const { reviewer = '' } = review;
return (
<div className="wc-block-review-list-item__author wc-block-components-review-list-item__author">
{ reviewer }
</div>
);
}
function getReviewDate( review ) {
const {
date_created: dateCreated,
formatted_date_created: formattedDateCreated,
} = review;
return (
<time
className="wc-block-review-list-item__published-date wc-block-components-review-list-item__published-date"
dateTime={ dateCreated }
>
{ formattedDateCreated }
</time>
);
}
function getReviewRating( review ) {
const { rating } = review;
const starStyle = {
width: ( rating / 5 ) * 100 + '%' /* stylelint-disable-line */,
};
const ratingText = sprintf(
/* translators: %f is referring to the average rating value */
__( 'Rated %f out of 5', 'woocommerce' ),
rating
);
return (
<div className="wc-block-review-list-item__rating wc-block-components-review-list-item__rating">
<div
className="wc-block-review-list-item__rating__stars wc-block-components-review-list-item__rating__stars"
role="img"
aria-label={ ratingText }
>
<span style={ starStyle }>{ ratingText }</span>
</div>
</div>
);
}
const ReviewListItem = ( { attributes, review = {} } ) => {
const {
imageType,
showReviewDate,
showReviewerName,
showReviewImage,
showReviewRating: showReviewRatingAttr,
showReviewContent,
showProductName,
} = attributes;
const { rating } = review;
const isLoading = ! Object.keys( review ).length > 0;
const showReviewRating = Number.isFinite( rating ) && showReviewRatingAttr;
return (
<li
className={ classNames(
'wc-block-review-list-item__item',
'wc-block-components-review-list-item__item',
{
'is-loading': isLoading,
'wc-block-components-review-list-item__item--has-image': showReviewImage,
}
) }
aria-hidden={ isLoading }
>
{ ( showProductName ||
showReviewDate ||
showReviewerName ||
showReviewImage ||
showReviewRating ) && (
<div className="wc-block-review-list-item__info wc-block-components-review-list-item__info">
{ showReviewImage &&
getReviewImage( review, imageType, isLoading ) }
{ ( showProductName ||
showReviewerName ||
showReviewRating ||
showReviewDate ) && (
<div className="wc-block-review-list-item__meta wc-block-components-review-list-item__meta">
{ showReviewRating && getReviewRating( review ) }
{ showProductName &&
getReviewProductName( review ) }
{ showReviewerName && getReviewerName( review ) }
{ showReviewDate && getReviewDate( review ) }
</div>
) }
</div>
) }
{ showReviewContent && getReviewContent( review ) }
</li>
);
};
ReviewListItem.propTypes = {
attributes: PropTypes.object.isRequired,
review: PropTypes.object,
};
/**
* BE AWARE. ReviewListItem expects product data that is equivalent to what is
* made available for output in a public view. Thus content that may contain
* html data is not sanitized further.
*
* Currently the following data is trusted (assumed to already be sanitized):
* - `review.review` (review content)
* - `review.product_name` (the product title)
*/
export default ReviewListItem;

View File

@ -0,0 +1,212 @@
.is-loading {
.wc-block-components-review-list-item__text {
@include placeholder();
@include force-content();
display: block;
width: 60%;
}
.wc-block-components-review-list-item__info {
.wc-block-components-review-list-item__image {
@include placeholder();
@include force-content();
}
.wc-block-components-review-list-item__meta {
.wc-block-components-review-list-item__author {
@include placeholder();
@include font-size(regular);
@include force-content();
width: 80px;
}
.wc-block-components-review-list-item__product {
display: none;
}
.wc-block-components-review-list-item__rating {
.wc-block-components-review-list-item__rating__stars > span {
display: none;
}
}
}
.wc-block-components-review-list-item__published-date {
@include placeholder();
@include force-content();
height: 1em;
width: 120px;
}
}
}
.editor-styles-wrapper .wc-block-components-review-list-item__item,
.wc-block-components-review-list-item__item {
margin: 0 0 $gap-large * 2;
list-style: none;
}
.wc-block-components-review-list-item__info {
display: grid;
grid-template-columns: 1fr;
margin-bottom: $gap-large;
}
.wc-block-components-review-list-item__meta {
grid-column: 1;
grid-row: 1;
}
.wc-block-components-review-list-item__item--has-image {
.wc-block-components-review-list-item__info {
grid-template-columns: calc(3em + #{$gap}) 1fr;
}
.wc-block-components-review-list-item__meta {
grid-column: 2;
}
}
.wc-block-components-review-list-item__image {
align-items: center;
display: flex;
height: 3em;
grid-column: 1;
grid-row: 1 / 3;
justify-content: center;
position: relative;
width: 3em;
> img {
display: block;
max-height: 100%;
object-fit: contain;
}
}
.wc-block-components-review-list-item__verified {
width: 21px;
height: 21px;
text-indent: 21px;
margin: 0;
line-height: 21px;
overflow: hidden;
position: absolute;
right: -7px;
bottom: -7px;
&::before {
width: 21px;
height: 21px;
background: transparent url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="21" height="21" fill="none"%3E%3Ccircle cx="10.5" cy="10.5" r="10.5" fill="%23fff"/%3E%3Cpath fill="%23008A21" fill-rule="evenodd" d="M2.1667 10.5003c0-4.6 3.7333-8.3333 8.3333-8.3333s8.3334 3.7333 8.3334 8.3333S15.1 18.8337 10.5 18.8337s-8.3333-3.7334-8.3333-8.3334zm2.5 0l4.1666 4.1667 7.5001-7.5-1.175-1.1833-6.325 6.325-2.9917-2.9834-1.175 1.175z" clip-rule="evenodd"/%3E%3Cmask id="a" width="17" height="17" x="2" y="2" maskUnits="userSpaceOnUse"%3E%3Cpath fill="%23fff" fill-rule="evenodd" d="M2.1667 10.5003c0-4.6 3.7333-8.3333 8.3333-8.3333s8.3334 3.7333 8.3334 8.3333S15.1 18.8337 10.5 18.8337s-8.3333-3.7334-8.3333-8.3334zm2.5 0l4.1666 4.1667 7.5001-7.5-1.175-1.1833-6.325 6.325-2.9917-2.9834-1.175 1.175z" clip-rule="evenodd"/%3E%3C/mask%3E%3Cg mask="url(%23a)"%3E%3Cpath fill="%23008A21" d="M.5.5h20v20H.5z"/%3E%3C/g%3E%3C/svg%3E') center center no-repeat; /* stylelint-disable-line */
display: block;
content: "";
}
}
.wc-block-components-review-list-item__meta {
display: flex;
align-items: center;
flex-flow: row wrap;
&::after {
// Force wrap after star rating.
order: 3;
content: "";
flex-basis: 100%;
}
}
.wc-block-components-review-list-item__product {
display: block;
font-weight: bold;
order: 1;
margin-right: $gap*0.5;
}
.wc-block-components-review-list-item__author {
display: block;
font-weight: bold;
order: 1;
margin-right: $gap*0.5;
}
.wc-block-components-review-list-item__product + .wc-block-components-review-list-item__author {
font-weight: normal;
order: 4;
}
.wc-block-components-review-list-item__published-date {
order: 5;
}
.wc-block-components-review-list-item__product + .wc-block-components-review-list-item__author + .wc-block-components-review-list-item__published-date {
padding-left: $gap/2;
position: relative;
&::before {
content: "";
display: inline-block;
margin-left: -$gap*0.5;
border-right: 1px solid currentColor;
opacity: 0.5;
height: 1em;
vertical-align: middle;
position: absolute;
top: calc(50% + 0.1em);
transform: translateY(-50%);
}
}
.wc-block-components-review-list-item__author:first-child + .wc-block-components-review-list-item__published-date,
.wc-block-components-review-list-item__rating + .wc-block-components-review-list-item__author + .wc-block-components-review-list-item__published-date {
&::before {
display: none;
}
}
.wc-block-components-review-list-item__rating {
order: 2;
> .wc-block-components-review-list-item__rating__stars {
@include font-size(regular);
display: block;
top: 0;
overflow: hidden;
position: relative;
height: 1em;
line-height: 1;
width: 5.3em;
font-family: star; /* stylelint-disable-line */
font-weight: 400;
}
> .wc-block-components-review-list-item__rating__stars::before {
content: "\53\53\53\53\53";
opacity: 0.25;
float: left;
top: 0;
left: 0;
position: absolute;
}
> .wc-block-components-review-list-item__rating__stars span {
overflow: hidden;
float: left;
top: 0;
left: 0;
position: absolute;
padding-top: 1.5em;
}
> .wc-block-components-review-list-item__rating__stars span::before {
content: "\53\53\53\53\53";
top: 0;
position: absolute;
left: 0;
color: #e6a237;
}
}
.wc-block-components-review-list-item__text p {
font-size: inherit;
}

View File

@ -0,0 +1,49 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import ReviewListItem from '../review-list-item';
import './style.scss';
const ReviewList = ( { attributes, reviews } ) => {
const showAvatars = getSetting( 'showAvatars', true );
const reviewRatingsEnabled = getSetting( 'reviewRatingsEnabled', true );
const showReviewImage =
( showAvatars || attributes.imageType === 'product' ) &&
attributes.showReviewImage;
const showReviewRating =
reviewRatingsEnabled && attributes.showReviewRating;
const attrs = {
...attributes,
showReviewImage,
showReviewRating,
};
return (
<ul className="wc-block-review-list wc-block-components-review-list">
{ reviews.length === 0 ? (
<ReviewListItem attributes={ attrs } />
) : (
reviews.map( ( review, i ) => (
<ReviewListItem
key={ review.id || i }
attributes={ attrs }
review={ review }
/>
) )
) }
</ul>
);
};
ReviewList.propTypes = {
attributes: PropTypes.object.isRequired,
reviews: PropTypes.array.isRequired,
};
export default ReviewList;

View File

@ -0,0 +1,4 @@
// Duplicate class for specificity in the editor.
.wc-block-components-review-list.wc-block-components-review-list {
margin: 0;
}

View File

@ -0,0 +1,59 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import SortSelect from '@woocommerce/base-components/sort-select';
/**
* Internal dependencies
*/
import './style.scss';
const ReviewSortSelect = ( { onChange, readOnly, value } ) => {
return (
<SortSelect
className="wc-block-review-sort-select wc-block-components-review-sort-select"
label={ __( 'Order by', 'woocommerce' ) }
onChange={ onChange }
options={ [
{
key: 'most-recent',
label: __( 'Most recent', 'woocommerce' ),
},
{
key: 'highest-rating',
label: __(
'Highest rating',
'woocommerce'
),
},
{
key: 'lowest-rating',
label: __(
'Lowest rating',
'woocommerce'
),
},
] }
readOnly={ readOnly }
screenReaderLabel={ __(
'Order reviews by',
'woocommerce'
) }
value={ value }
/>
);
};
ReviewSortSelect.propTypes = {
onChange: PropTypes.func,
readOnly: PropTypes.bool,
value: PropTypes.oneOf( [
'most-recent',
'highest-rating',
'lowest-rating',
] ),
};
export default ReviewSortSelect;

View File

@ -0,0 +1,3 @@
.wc-block-components-review-sort-select {
text-align: right;
}