Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/components/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import formatMoney from '../utils/money';
import normalizeConfig from '../utils/normalize-config';
import browserFeatures from '../utils/detect-features';
import getUnitPriceBaseUnit from '../utils/unit-price';
import parseTemplateString from '../utils/template-string';
import ProductView from '../views/product';
import ProductUpdater from '../updaters/product';

Expand Down Expand Up @@ -262,6 +263,7 @@ export default class Product extends Component {
formattedUnitPriceBaseUnit: this.formattedUnitPriceBaseUnit,
carouselIndex: 0,
carouselImages: this.carouselImages,
imageIndexString: this.imageIndexString,
});
}

Expand All @@ -272,7 +274,7 @@ export default class Product extends Component {
src: image.src,
carouselSrc: this.props.client.image.helpers.imageForSize(image, {maxWidth: 100, maxHeight: 100}),
isSelected: image.id === this.currentImage.id,
altText: this.imageAltText(image.altText),
ariaLabel: this.carouselImageLabel(image.altText),
};
});
}
Expand Down Expand Up @@ -361,7 +363,7 @@ export default class Product extends Component {
[`click ${this.selectors.product.quantityIncrement}`]: this.onQuantityIncrement.bind(this, 1),
[`click ${this.selectors.product.quantityDecrement}`]: this.onQuantityIncrement.bind(this, -1),
[`blur ${this.selectors.product.quantityInput}`]: this.onQuantityBlur.bind(this),
[`click ${this.selectors.product.carouselItem}`]: this.onCarouselItemClick.bind(this),
[`click ${this.selectors.product.carouselItemLink}`]: this.onCarouselItemClick.bind(this),
[`click ${this.selectors.product.carouselNext}`]: this.onCarouselChange.bind(this, 1),
[`click ${this.selectors.product.carouselPrevious}`]: this.onCarouselChange.bind(this, -1),
}, this.options.DOMEvents);
Expand Down Expand Up @@ -847,6 +849,11 @@ export default class Product extends Component {
return altText || this.model.title;
}

carouselImageLabel(altText) {
const imageAlt = this.imageAltText(altText);
return `${this.options.text.carouselImageLinkAccessibilityLabel} ${imageAlt}`;
}

get priceAccessibilityLabel() {
return this.hasCompareAtPrice ? this.options.text.salePriceAccessibilityLabel : this.options.text.regularPriceAccessibilityLabel;
}
Expand All @@ -858,4 +865,15 @@ export default class Product extends Component {
get hasCompareAtPrice() {
return Boolean(this.selectedVariant && this.selectedVariant.compareAtPriceV2);
}

get imageIndexString() {
const imageList = this.model.images;
const total = imageList.length;

const currentImage = imageList.find((image) => {
return image.id === this.currentImage.id;
});
const index = imageList.indexOf(currentImage) + 1;
return parseTemplateString(this.options.text.carouselImageIndexLabel, {index, total});
}
}
15 changes: 13 additions & 2 deletions src/defaults/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,15 @@ const defaults = {
img: 'shopify-buy__product__variant-img',
imgWrapper: 'shopify-buy__product-img-wrapper',
carousel: 'shopify-buy__carousel',
carouselNext: 'carousel-button--next',
carouselPrevious: 'carousel-button--previous',
carouselImgWrapper: 'shopify-buy__carousel-img-wrapper',
carouselButtons: 'shopify-buy__carousel__buttons',
carouselButton: 'shopify-buy__carousel__button',
carouselNext: 'shopify-buy__carousel__button--next',
carouselPrevious: 'shopify-buy__carousel__button--previous',
carouselButtonIcon: 'shopify-buy__carousel__button__icon',
carouselItem: 'shopify-buy__carousel-item',
carouselItemSelected: 'shopify-buy__carousel-item--selected',
carouselItemLink: 'shopify-buy__carousel-item__link',
blockButton: 'shopify-buy__btn--parent',
button: 'shopify-buy__btn',
buttonWrapper: 'shopify-buy__btn-wrapper',
Expand Down Expand Up @@ -84,6 +89,12 @@ const defaults = {
unitPriceAccessibilitySeparator: 'per',
regularPriceAccessibilityLabel: 'Regular price',
salePriceAccessibilityLabel: 'Sale price',
carouselImageLinkAccessibilityLabel: 'Load image into gallery viewer.',
carouselAriaRoleDescription: 'Carousel',
carouselAriaLabel: 'Image gallery',
carouselPreviousImage: 'Previous image',
carouselNextImage: 'Next image',
carouselImageIndexLabel: 'Image ${index} of ${total}',
},
},
modalProduct: {
Expand Down
4 changes: 0 additions & 4 deletions src/styles/embeds/sass/components/modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@
padding-right: calc(var(--gutter) * 2);
}

.shopify-buy__product__variant-img {
margin: 0 auto;
}

.shopify-buy__btn--close {
top: -60px;
color: color-mod(var(--color-white) lightness(+10%));
Expand Down
46 changes: 24 additions & 22 deletions src/styles/embeds/sass/components/product.css
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,20 @@
width: calc(16.666% - var(--gutter));
margin-left: var(--gutter);
display: inline-block;
vertical-align: middle;
cursor: pointer;
position: relative;
background-size: cover;
background-position: center;
padding: 0;
border: none;

&:nth-child(n+7) {
margin-top: var(--gutter);
}
}

.shopify-buy__carousel-item__link {
display: block;
background-size: cover;
background-position: center;
cursor: pointer;
position: relative;
padding: 0;
border: none;

&:before {
content: "";
Expand All @@ -317,15 +320,16 @@
}
}

.main-image-wrapper {
.shopify-buy__carousel-img-wrapper {
position: relative;
}

.carousel-button {
position: absolute;
.shopify-buy__carousel__buttons {
text-align: center;
}

.shopify-buy__carousel__button {
width: 75px;
top: 0;
height: 100%;
border: none;
font-size: 0;
background-color: transparent;
Expand All @@ -335,23 +339,21 @@
&:hover,
&:focus {
opacity: 0.9;
outline: none;
}
}

.carousel-button-arrow {
width: 20px;
display: inline-block;
margin-left: 25px;
.shopify-buy__carousel__button--next {
margin-left: 10px;
}

.carousel-button--previous {
left: 0;
transform: rotate(180deg);
.shopify-buy__carousel__button--previous {
margin-right: 10px;
}

.carousel-button--next {
right: 0;
.shopify-buy__carousel__button__icon {
width: 20px;
display: inline-block;
fill: currentColor;
}

.shopify-buy__carousel-item--selected {
Expand Down
31 changes: 18 additions & 13 deletions src/templates/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,27 @@ const buttonTemplate = '<div class="{{data.classes.product.buttonWrapper}}" data
const productTemplate = {
img: '{{#data.currentImage.srcLarge}}<div class="{{data.classes.product.imgWrapper}}" data-element="product.imgWrapper"><img alt="{{data.currentImage.altText}}" data-element="product.img" class="{{data.classes.product.img}}" src="{{data.currentImage.srcLarge}}" /></div>{{/data.currentImage.srcLarge}}',
imgWithCarousel: `<div class="{{data.classes.product.imgWrapper}}" data-element="product.imageWrapper">
<div class="main-image-wrapper">
<button type="button" class="carousel-button carousel-button--previous">
Left
<img class="carousel-button-arrow" src="//sdks.shopifycdn.com/buy-button/latest/arrow.svg" alt="Carousel Arrow"/>
</button>
<img class="{{data.classes.product.img}}" alt="{{data.currentImage.altText}}" src="{{data.currentImage.src}}" data-element="product.img" />
<button type="button" class="carousel-button carousel-button--next">
Right
<img class="carousel-button-arrow" src="//sdks.shopifycdn.com/buy-button/latest/arrow.svg" alt="Carousel Arrow"/>
</button>
<div class="{{data.classes.product.carouselImgWrapper}}" aria-roledescription="{{data.text.carouselAriaRoleDescription}}" aria-label="{{data.text.carouselAriaLabel}}" >
<div aria-live="polite" aria-atomic="true">
<span class="visuallyhidden">{{data.imageIndexString}}</span>
<img class="{{data.classes.product.img}}" alt="{{data.currentImage.altText}}" src="{{data.currentImage.src}}" data-element="product.img" />
</div>
<div class="{{data.classes.product.carouselButtons}}">
<button type="button" class="{{data.classes.product.carouselButton}} {{data.classes.product.carouselPrevious}}" aria-label="{{data.text.carouselPreviousImage}}">
<svg class="{{data.classes.product.carouselButtonIcon}}" viewBox="0 0 19 34"><path d="M15.2 4l.8.7L3.4 17.2 16 29.8l-.8.7L2 17.2 15.2 4z" /></svg>
</button>
<button type="button" class="{{data.classes.product.carouselButton}} {{data.classes.product.carouselNext}}" aria-label="{{data.text.carouselNextImage}}">
<svg class="{{data.classes.product.carouselButtonIcon}}" viewBox="0 0 19 34"><path d="M2.9 30.7l-.8-.7 12.6-12.5L2.1 4.9l.8-.7 13.2 13.3L2.9 30.7z" /></svg>
</button>
</div>
</div>
<div class="{{data.classes.product.carousel}}">
<ul class="{{data.classes.product.carousel}}" role="list">
{{#data.carouselImages}}
<a data-element="product.carouselitem" aria-label="{{altText}}" href="{{src}}" class="{{data.classes.product.carouselItem}} {{#isSelected}} {{data.classes.product.carouselItemSelected}} {{/isSelected}}" data-image-id="{{id}}" style="background-image: url({{carouselSrc}})"></a>
<li class="{{data.classes.product.carouselItem}} {{#isSelected}} {{data.classes.product.carouselItemSelected}} {{/isSelected}}" {{#isSelected}} aria-current="true" {{/isSelected}}>
<a class="{{data.classes.product.carouselItemLink}}" data-element="product.carouselitem" aria-label="{{ariaLabel}}" href="{{src}}" data-image-id="{{id}}" style="background-image: url({{carouselSrc}})"></a>
</li>
{{/data.carouselImages}}
</div>
</ul>
</div>`,
title: '<h1 class="{{data.classes.product.title}}" data-element="product.title">{{data.title}}</h1>',
variantTitle: '{{#data.hasVariants}}<h2 class="{{data.classes.product.variantTitle}}" data-element="product.variantTitle">{{data.selectedVariant.title}}</h2>{{/data.hasVariants}}',
Expand Down
9 changes: 9 additions & 0 deletions src/utils/template-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

function parseTemplateString(string, replacements) {
return string.replace(/\${.+?}/g, (match) => {
const key = match.substr(2, match.length - 3).trim();
return replacements[key];
});
}

export default parseTemplateString;
4 changes: 4 additions & 0 deletions test/fixtures/product-fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ const testProduct = {
{
id: '1',
src: 'https://cdn.shopify.com/s/files/1/0014/8583/2214/products/image-one.jpg',
altText: 'image one alt text',
},
{
id: '2',
src: 'https://cdn.shopify.com/s/files/1/0014/8583/2214/products/image-two.jpeg',
altText: 'image two alt text',
},
{
id: '3',
src: 'https://cdn.shopify.com/s/files/1/0014/8583/2214/products/image-three.jpg',
altText: 'image three alt text',
},
{
id: '4',
src: 'https://cdn.shopify.com/s/files/1/0014/8583/2214/products/image-four.jpeg',
altText: 'image four alt text',
},
],
options: [
Expand Down
1 change: 1 addition & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import './unit/normalize-config';
import './unit/detect-features';
import './unit/unit-price';
import './unit/focus';
import './unit/template-string';

window.chai = chai;
window.sinon = sinon;
Expand Down
Loading