initial commit
This commit is contained in:
@ -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';
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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;
|
@ -0,0 +1,4 @@
|
||||
// Duplicate class for specificity in the editor.
|
||||
.wc-block-components-review-list.wc-block-components-review-list {
|
||||
margin: 0;
|
||||
}
|
@ -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;
|
@ -0,0 +1,3 @@
|
||||
.wc-block-components-review-sort-select {
|
||||
text-align: right;
|
||||
}
|
Reference in New Issue
Block a user