diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7c96ce11..171dfa9c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ ## Draft +## 5.1.0 (01-26-2021) +- Updated Cornerstone theme and respected variants to meet the verticals/industries documented in BCTHEME-387 +- Fixed selecting certain option types which pushes window view to the bottom of the page. [#1959](https://github.com/bigcommerce/cornerstone/pull/1959) +- Fixed case when default option is out of stock add to cart button does not populate for in stock options. [#1955](https://github.com/bigcommerce/cornerstone/pull/1955) +- Dots on carousel should have similar colours as other slider controls. [#1958](https://github.com/bigcommerce/cornerstone/pull/1958) +- PDP - Fixed Empty "Description" Hiding All Tabs When in Tab View. [#1947](https://github.com/bigcommerce/cornerstone/pull/1947) +- Added custom event for product price change on PDP page. [#1948](https://github.com/bigcommerce/cornerstone/pull/1948) +- Fixed announcement of subscription message. [#1952](https://github.com/bigcommerce/cornerstone/pull/1952) +- Error message on PLPs not announced by screen reader. [#1956](https://github.com/bigcommerce/cornerstone/pull/1956) +- Add Play/Pause button to carousel. [#1944](https://github.com/bigcommerce/cornerstone/pull/1944) +- Alt text not provided for ratings. [#1949](https://github.com/bigcommerce/cornerstone/pull/1949) +- Fixed announcement for product on adding to cart. [#1950](https://github.com/bigcommerce/cornerstone/pull/1950) +- Fixed non-text contrast on add to cart modal according to WCAG AA standard. [#1946](https://github.com/bigcommerce/cornerstone/pull/1946) +- Fixed announcement of reCAPTCHA hidden content by screen reader. [#1943](https://github.com/bigcommerce/cornerstone/pull/1943) +- Carousel buttons do not receive focus. [#1937](https://github.com/bigcommerce/cornerstone/pull/1937) +- Empty cart message not read by screen reader. [#1935](https://github.com/bigcommerce/cornerstone/pull/1935) +- No tooltips provided for carousel buttons. [#1934](https://github.com/bigcommerce/cornerstone/pull/1934) +- Added announcement on shipping estimator errors. [#1932](https://github.com/bigcommerce/cornerstone/pull/1932) +- Add main tag on pages like Homepage, Category, Product etc to define the dominant content. [#1929](https://github.com/bigcommerce/cornerstone/pull/1929) +- Fixed unable to change product quantity several times on cart page using keyboard. [#1927](https://github.com/bigcommerce/cornerstone/pull/1927) +- Cornerstone - loading of thumbnail image delayed on cart page . [#1925](https://github.com/bigcommerce/cornerstone/pull/1925) +- Added narrow down pricing during option selections [#1924](https://github.com/bigcommerce/cornerstone/pull/1924) +- Cornerstone - Image Zoom Does Not Work on Internet Explorer. [#1923](https://github.com/bigcommerce/cornerstone/pull/1923) +- Fixed input placeholder color contrast according to AA standard. [#1933](https://github.com/bigcommerce/cornerstone/pull/1933) +- Bump stencil utils to 6.8.0. [#1945](https://github.com/bigcommerce/cornerstone/pull/1945) + ## 5.0.0 (12-14-2020) - Parse HTML entities in jsContext. [#1917](https://github.com/bigcommerce/cornerstone/pull/1917) - Product images squashed in Category view in AMP. [#1921](https://github.com/bigcommerce/cornerstone/pull/1921) @@ -27,6 +53,7 @@ - Added styling config for the PayPal SPB on checkout page [#1866](https://github.com/bigcommerce/cornerstone/pull/1866) - Moved zoomSize and productSize to the upper level, cause product.js is not availabe on the Quick View [#1884](https://github.com/bigcommerce/cornerstone/pull/1884) - Added new region on the cart page [#1901](https://github.com/bigcommerce/cornerstone/pull/1901) +- Add pagination for Wishlists.[#1906](https://github.com/bigcommerce/cornerstone/pull/1906) ## 4.12.1 (11-10-2020) - Write a Review modal cause TypeError. [#1899](https://github.com/bigcommerce/cornerstone/pull/1899) diff --git a/assets/js/test-unit/theme/common/payment-method.spec.js b/assets/js/test-unit/theme/common/payment-method.spec.js index 97d9351441..9114ac70e9 100644 --- a/assets/js/test-unit/theme/common/payment-method.spec.js +++ b/assets/js/test-unit/theme/common/payment-method.spec.js @@ -127,7 +127,7 @@ describe('PaymentMethod', () => { it('should have valid input expiration date', () => { const callback = jasmine.createSpy(); - const validator = { add: ({ validate }) => validate(callback, '12/20') }; + const validator = { add: ({ validate }) => validate(callback, '12/25') }; Validators.setExpirationValidation(validator, 'selector'); expect(callback).toHaveBeenCalledWith(true); diff --git a/assets/js/theme/auth.js b/assets/js/theme/auth.js index 23c11dfba2..9aaa227a84 100644 --- a/assets/js/theme/auth.js +++ b/assets/js/theme/auth.js @@ -11,6 +11,7 @@ export default class Auth extends PageManager { super(context); this.validationDictionary = createTranslationDictionary(context); this.formCreateSelector = 'form[data-create-account-form]'; + this.recaptcha = $('.g-recaptcha iframe[src]'); } registerLoginValidation($loginForm) { @@ -175,6 +176,10 @@ export default class Auth extends PageManager { * Request is made in this function to the remote endpoint and pulls back the states for country. */ onReady() { + if (!this.recaptcha.attr('title')) { + this.recaptcha.attr('title', this.context.recaptchaTitle); + } + const $createAccountForm = classifyForm(this.formCreateSelector); const $loginForm = classifyForm('.login-form'); const $forgotPasswordForm = classifyForm('.forgot-password-form'); diff --git a/assets/js/theme/cart.js b/assets/js/theme/cart.js index 6373a73032..ca438890f6 100644 --- a/assets/js/theme/cart.js +++ b/assets/js/theme/cart.js @@ -15,12 +15,17 @@ export default class Cart extends PageManager { this.$cartTotals = $('[data-cart-totals]'); this.$overlay = $('[data-cart] .loadingOverlay') .hide(); // TODO: temporary until roper pulls in his cart components + this.$activeCartItemId = null; + this.$activeCartItemBtnAction = null; this.bindEvents(); } cartUpdate($target) { const itemId = $target.data('cartItemid'); + this.$activeCartItemId = itemId; + this.$activeCartItemBtnAction = $target.data('action'); + const $el = $(`#qty-${itemId}`); const oldQty = parseInt($el.val(), 10); const maxQty = parseInt($el.data('quantityMax'), 10); @@ -220,6 +225,10 @@ export default class Cart extends PageManager { const quantity = $('[data-cart-quantity]', this.$cartContent).data('cartQuantity') || 0; $('body').trigger('cart-quantity-update', quantity); + + $(`[data-cart-itemid='${this.$activeCartItemId}']`, this.$cartContent) + .filter(`[data-action='${this.$activeCartItemBtnAction}']`) + .trigger('focus'); }); } diff --git a/assets/js/theme/cart/shipping-estimator.js b/assets/js/theme/cart/shipping-estimator.js index f3fc58b86a..6a30260f1d 100644 --- a/assets/js/theme/cart/shipping-estimator.js +++ b/assets/js/theme/cart/shipping-estimator.js @@ -17,12 +17,22 @@ export default class ShippingEstimator { } initFormValidation() { + const shippingEstimatorAlert = $('.shipping-quotes'); + this.shippingEstimator = 'form[data-shipping-estimator]'; this.shippingValidator = nod({ submit: `${this.shippingEstimator} .shipping-estimate-submit`, }); $('.shipping-estimate-submit', this.$element).on('click', event => { + // estimator error messages are being injected in html as a result + // of user submit; clearing and adding role on submit provides + // regular announcement of these error messages + if (shippingEstimatorAlert.attr('role')) { + shippingEstimatorAlert.removeAttr('role'); + } + + shippingEstimatorAlert.attr('role', 'alert'); // When switching between countries, the state/region is dynamic // Only perform a check for all fields when country has a value // Otherwise areAll('valid') will check country for validity diff --git a/assets/js/theme/category.js b/assets/js/theme/category.js index c1b1dea8c3..42a1df4a2b 100644 --- a/assets/js/theme/category.js +++ b/assets/js/theme/category.js @@ -35,6 +35,7 @@ export default class Category extends CatalogPage { }); }); + this.ariaNotifyNoProducts(); this.initCategoryButton(); } @@ -45,6 +46,13 @@ export default class Category extends CatalogPage { } } + ariaNotifyNoProducts() { + const $noProductsMessage = $('[data-no-products-notification]'); + if ($noProductsMessage.length) { + $noProductsMessage.focus(); + } + } + initFacetedSearch() { const { price_min_evaluation: onMinPriceError, diff --git a/assets/js/theme/common/carousel/index.js b/assets/js/theme/common/carousel/index.js index 867033e403..2a6abe3c05 100644 --- a/assets/js/theme/common/carousel/index.js +++ b/assets/js/theme/common/carousel/index.js @@ -2,6 +2,7 @@ import 'slick-carousel'; import { dotsSetup, + tooltipSetup, setTabindexes, arrowAriaLabling, heroCarouselSetup, @@ -33,6 +34,7 @@ const onCarouselChange = (event, carousel) => { dotsSetup($dots, actualSlide, actualSlideCount, $slider.data('dots-labels')); setTabindexes($slider.find('.slick-slide'), $prevArrow, $nextArrow, actualSlide, actualSlideCount); arrowAriaLabling($prevArrow, $nextArrow, actualSlide, actualSlideCount); + tooltipSetup($prevArrow, $nextArrow, $dots); }; export default function () { diff --git a/assets/js/theme/common/carousel/utils/heroCarouselSetup.js b/assets/js/theme/common/carousel/utils/heroCarouselSetup.js index 089f7720e1..e7aa880538 100644 --- a/assets/js/theme/common/carousel/utils/heroCarouselSetup.js +++ b/assets/js/theme/common/carousel/utils/heroCarouselSetup.js @@ -1,3 +1,5 @@ +import playPause from './playPause'; + const showCarouselIfSlidesAnalyzedSetup = ($carousel) => { const analyzedSlides = []; return ($slides) => ($slide) => { @@ -11,6 +13,8 @@ const showCarouselIfSlidesAnalyzedSetup = ($carousel) => { export default ($heroCarousel) => { if ($heroCarousel.length === 0) return; + playPause($heroCarousel); + const $slidesNodes = $heroCarousel.find('.heroCarousel-slide'); const showCarouselIfSlidesAnalyzed = showCarouselIfSlidesAnalyzedSetup($heroCarousel)($slidesNodes); diff --git a/assets/js/theme/common/carousel/utils/index.js b/assets/js/theme/common/carousel/utils/index.js index 97ab738300..3227c05446 100644 --- a/assets/js/theme/common/carousel/utils/index.js +++ b/assets/js/theme/common/carousel/utils/index.js @@ -3,3 +3,4 @@ export { default as arrowAriaLabling } from './arrowAriaLabling'; export { default as dotsSetup } from './dotsSetup'; export { default as getRealSlidesQuantityAndCurrentSlide } from './getRealSlidesQuantityAndCurrentSlide'; export { default as setTabindexes } from './setTabindexes'; +export { default as tooltipSetup } from './tooltipSetup'; diff --git a/assets/js/theme/common/carousel/utils/playPause.js b/assets/js/theme/common/carousel/utils/playPause.js new file mode 100644 index 0000000000..7fc67b55dd --- /dev/null +++ b/assets/js/theme/common/carousel/utils/playPause.js @@ -0,0 +1,38 @@ +import { throttle } from 'lodash'; + +const playAction = 'slickPlay'; +const pauseAction = 'slickPause'; + +export default ($heroCarousel) => { + const $playPauseButton = $('.js-hero-play-pause-button'); + + if ($playPauseButton.length === 0) return; + + const slickSettings = $heroCarousel[0].slick; + if (!slickSettings) return; + + const { slideCount, options: { speed } } = slickSettings; + if (slideCount < 2) { + $playPauseButton.css('display', 'none'); + return; + } + + const onPlayPauseClick = () => { + const isCarouselPlaying = $playPauseButton.data('play'); + const action = isCarouselPlaying ? pauseAction : playAction; + const { + play, + ariaPlay, + pause, + ariaPause, + } = $playPauseButton.data('labels'); + + $heroCarousel.slick(action); + $playPauseButton + .data('play', !isCarouselPlaying) + .text(action === playAction ? pause : play) + .attr('aria-label', action === playAction ? ariaPause : ariaPlay); + }; + + $playPauseButton.on('click', throttle(onPlayPauseClick, speed, { trailing: false })); +}; diff --git a/assets/js/theme/common/carousel/utils/setTabindexes.js b/assets/js/theme/common/carousel/utils/setTabindexes.js index 86335fc365..0fe2b1e103 100644 --- a/assets/js/theme/common/carousel/utils/setTabindexes.js +++ b/assets/js/theme/common/carousel/utils/setTabindexes.js @@ -4,7 +4,7 @@ export default ($slides, $prevArrow, $nextArrow, actualSlide, actualSlideCount) $slides.each((index, element) => { const $element = $(element); const tabIndex = $element.hasClass('slick-active') ? 0 : -1; - if (!$element.hasClass('js-product-slide')) { + if ($element.attr('href') !== undefined) { $element.attr('tabindex', tabIndex); } diff --git a/assets/js/theme/common/carousel/utils/tooltipSetup.js b/assets/js/theme/common/carousel/utils/tooltipSetup.js new file mode 100644 index 0000000000..8c14c8b35e --- /dev/null +++ b/assets/js/theme/common/carousel/utils/tooltipSetup.js @@ -0,0 +1,30 @@ +const carouselTooltipClass = 'carousel-tooltip'; +const carouselTooltip = ``; + +const setupTooltipAriaLabel = ($node) => { + const $existedTooltip = $node.find(`.${carouselTooltipClass}`); + + if ($existedTooltip.length) { + $existedTooltip.attr('aria-label', $node.attr('aria-label')); + } else { + const $tooltip = $(carouselTooltip).attr('aria-label', $node.attr('aria-label')); + $node.append($tooltip); + } +}; + +const setupArrowTooltips = (...arrowNodes) => { + arrowNodes.forEach($arrow => setupTooltipAriaLabel($arrow)); +}; + +const setupDotTooltips = ($dots) => { + $dots.children().each((idx, dot) => setupTooltipAriaLabel($(dot).find('button'))); +}; + +export default ($prevArrow, $nextArrow, $dots) => { + if ($prevArrow.length && $nextArrow.length) { + setupArrowTooltips($prevArrow, $nextArrow); + } + if ($dots) { + setupDotTooltips($dots); + } +}; diff --git a/assets/js/theme/common/product-details-base.js b/assets/js/theme/common/product-details-base.js index 9cbc5e2d96..71982946e7 100644 --- a/assets/js/theme/common/product-details-base.js +++ b/assets/js/theme/common/product-details-base.js @@ -264,6 +264,12 @@ export default class ProductDetailsBase { } else if (typeof (data.bulk_discount_rates) !== 'undefined') { viewModel.$bulkPricing.html(''); } + + const addToCartWrapper = $('#add-to-cart-wrapper'); + + if (addToCartWrapper.is(':hidden') && data.purchasable) { + addToCartWrapper.show(); + } } /** @@ -274,13 +280,19 @@ export default class ProductDetailsBase { this.clearPricingNotFound(viewModel); if (price.with_tax) { + const updatedPrice = price.price_range ? + `${price.price_range.min.with_tax.formatted} - ${price.price_range.max.with_tax.formatted}` + : price.with_tax.formatted; viewModel.priceLabel.$span.show(); - viewModel.$priceWithTax.html(price.with_tax.formatted); + viewModel.$priceWithTax.html(updatedPrice); } if (price.without_tax) { + const updatedPrice = price.price_range ? + `${price.price_range.min.without_tax.formatted} - ${price.price_range.max.without_tax.formatted}` + : price.without_tax.formatted; viewModel.priceLabel.$span.show(); - viewModel.$priceWithoutTax.html(price.without_tax.formatted); + viewModel.$priceWithoutTax.html(updatedPrice); } if (price.rrp_with_tax) { diff --git a/assets/js/theme/common/product-details.js b/assets/js/theme/common/product-details.js index cdea69b0aa..ecfcb92113 100644 --- a/assets/js/theme/common/product-details.js +++ b/assets/js/theme/common/product-details.js @@ -7,6 +7,7 @@ import modalFactory, { showAlertModal, modalTypes } from '../global/modal'; import { isEmpty, isPlainObject } from 'lodash'; import { normalizeFormData } from './utils/api'; import { isBrowserIE, convertIntoArray } from './utils/ie-helpers'; +import bannerUtils from './utils/banner-utils'; export default class ProductDetails extends ProductDetailsBase { constructor($scope, context, productAttributesData = {}) { @@ -46,6 +47,7 @@ export default class ProductDetails extends ProductDetailsBase { utils.api.productAttributes.optionChange($productId, $form.serialize(), 'products/bulk-discount-rates', optionChangeCallback); } else { this.updateProductAttributes(productAttributesData); + bannerUtils.dispatchProductBannerEvent(productAttributesData); } $productOptionsElement.show(); @@ -187,6 +189,7 @@ export default class ProductDetails extends ProductDetailsBase { const productAttributesContent = response.content || {}; this.updateProductAttributes(productAttributesData); this.updateView(productAttributesData, productAttributesContent); + bannerUtils.dispatchProductBannerEvent(productAttributesData); }); } @@ -340,6 +343,11 @@ export default class ProductDetails extends ProductDetailsBase { this.redirectTo(response.data.cart_item.cart_url || this.context.urls.cart); } }); + + $addToCartBtn.next().attr({ + role: 'status', + 'aria-live': 'polite', + }); } /** diff --git a/assets/js/theme/common/utils/banner-utils.js b/assets/js/theme/common/utils/banner-utils.js new file mode 100644 index 0000000000..cc93d85eec --- /dev/null +++ b/assets/js/theme/common/utils/banner-utils.js @@ -0,0 +1,29 @@ +import { isBrowserIE } from './ie-helpers'; + +const bannerUtils = { + dispatchProductBannerEvent: (productAttributes) => { + if (!productAttributes.price || isBrowserIE) return; + + let price = 0; + + if (!productAttributes.price.price_range) { + if (productAttributes.price.without_tax) { + price = productAttributes.price.without_tax.value; + } + + if (productAttributes.price.with_tax) { + price = productAttributes.price.with_tax.value; + } + } + + const evt = new CustomEvent('bigcommerce.productpricechange', { + detail: { + amount: price, + }, + }); + + window.dispatchEvent(evt); + }, +}; + +export default bannerUtils; diff --git a/assets/js/theme/common/utils/pagination-utils.js b/assets/js/theme/common/utils/pagination-utils.js new file mode 100644 index 0000000000..3e3627ac99 --- /dev/null +++ b/assets/js/theme/common/utils/pagination-utils.js @@ -0,0 +1,24 @@ +const changeWishlistPaginationLinks = (wishlistUrl, ...paginationItems) => $.each(paginationItems, (_, $item) => { + const paginationLink = $item.children('.pagination-link'); + + if ($item.length && !paginationLink.attr('href').includes('page=')) { + const pageNumber = paginationLink.attr('href'); + paginationLink.attr('href', `${wishlistUrl}page=${pageNumber}`); + } +}); + +/** + * helps to withdraw differences in structures around the stencil resource pagination + */ +export const wishlistPaginatorHelper = () => { + const $paginationList = $('.pagination-list'); + + if (!$paginationList.length) return; + + const $nextItem = $('.pagination-item--next', $paginationList); + const $prevItem = $('.pagination-item--previous', $paginationList); + const currentHref = $('[data-pagination-current-page-link]').attr('href'); + const partialPaginationUrl = currentHref.split('page=').shift(); + + changeWishlistPaginationLinks(partialPaginationUrl, $prevItem, $nextItem); +}; diff --git a/assets/js/theme/product/image-gallery.js b/assets/js/theme/product/image-gallery.js index 4f65e87585..299e3d620f 100644 --- a/assets/js/theme/product/image-gallery.js +++ b/assets/js/theme/product/image-gallery.js @@ -90,10 +90,14 @@ export default class ImageGallery { } checkImage() { - const containerHeight = $('.productView-image').height(); - const containerWidth = $('.productView-image').width(); - const height = this.easyzoom.data('easyZoom').$zoom.context.height; - const width = this.easyzoom.data('easyZoom').$zoom.context.width; + const $imageContainer = $('.productView-image'); + const containerHeight = $imageContainer.height(); + const containerWidth = $imageContainer.width(); + + const $image = this.easyzoom.data('easyZoom').$zoom; + const height = $image.height(); + const width = $image.width(); + if (height < containerHeight || width < containerWidth) { this.easyzoom.data('easyZoom').hide(); } diff --git a/assets/js/theme/wishlist.js b/assets/js/theme/wishlist.js index 1149255340..a32ab012e4 100644 --- a/assets/js/theme/wishlist.js +++ b/assets/js/theme/wishlist.js @@ -2,6 +2,7 @@ import 'foundation-sites/js/foundation/foundation'; import 'foundation-sites/js/foundation/foundation.reveal'; import nod from './common/nod'; import PageManager from './page-manager'; +import { wishlistPaginatorHelper } from './common/utils/pagination-utils'; export default class WishList extends PageManager { constructor(context) { @@ -60,6 +61,10 @@ export default class WishList extends PageManager { onReady() { const $addWishListForm = $('.wishlist-form'); + if ($('[data-pagination-wishlist]').length) { + wishlistPaginatorHelper(); + } + if ($addWishListForm.length) { this.registerAddWishListValidation($addWishListForm); } diff --git a/assets/scss/common/_focus-tooltip.scss b/assets/scss/common/_focus-tooltip.scss new file mode 100644 index 0000000000..9d1bd3ccbf --- /dev/null +++ b/assets/scss/common/_focus-tooltip.scss @@ -0,0 +1,37 @@ +@mixin addFocusTooltip ($attr: title) { + &:before { + content: " "; + position: absolute; + right: 0; + top: 50%; + border-width: remCalc(10px); + border-style: solid; + border-color: transparent transparent $adminBar-tooltip-bg-backgroundColor transparent; + } + + &:after { + content: attr($attr); + padding: remCalc(4px) remCalc(6px); + background-color: $adminBar-tooltip-bg-backgroundColor; + color: white; + position: absolute; + font-size: 1rem; + white-space: nowrap; + right: 0; + top: 100%; + cursor: default; + border-radius: remCalc(8px); + } + + &:before, + &:after { + display: none; + } + + &:focus { + &:before, + &:after { + display: block; + } + } +} diff --git a/assets/scss/common/index.scss b/assets/scss/common/index.scss index 5099662fb5..d4740fed51 100644 --- a/assets/scss/common/index.scss +++ b/assets/scss/common/index.scss @@ -1 +1,2 @@ @import 'aria'; +@import 'focus-tooltip'; diff --git a/assets/scss/components/citadel/forms/_forms.scss b/assets/scss/components/citadel/forms/_forms.scss index ca2898f8bc..2c247fa8c4 100644 --- a/assets/scss/components/citadel/forms/_forms.scss +++ b/assets/scss/components/citadel/forms/_forms.scss @@ -91,6 +91,11 @@ .form-option-wrapper { position: relative; display: inline-block; + + & .form-radio, & .form-checkbox { + bottom: 0.5rem; + left: 0.5rem; + } } // Citadel form-actions @@ -137,6 +142,11 @@ // appear before the button. // // ----------------------------------------------------------------------------- +@mixin placeholder { + &::-webkit-input-placeholder {@content} + &::-moz-placeholder {@content} + &:-ms-input-placeholder {@content} +} .form-label--alternate { font-family: fontFamily("headingSans"); @@ -209,6 +219,10 @@ @include breakpoint("large") { width: auto; } + + @include placeholder { + color: $formInput-placeholder-color; + } } .button { diff --git a/assets/scss/components/foundation/modal/_modal.scss b/assets/scss/components/foundation/modal/_modal.scss index bcc2f893ee..042c5fca44 100644 --- a/assets/scss/components/foundation/modal/_modal.scss +++ b/assets/scss/components/foundation/modal/_modal.scss @@ -4,34 +4,6 @@ // // 1. Fix for content shifted to top in modal window when bottom variant option selected // ============================================================================= -@mixin addFocusTooltip () { - &:focus { - &:before { - content: " "; - position: absolute; - right: 0; - top: 50%; - border-width: remCalc(10px); - border-style: solid; - border-color: transparent transparent $adminBar-tooltip-bg-backgroundColor transparent; - } - - &:after { - content: attr(title); - padding: remCalc(4px) remCalc(6px); - background-color: $adminBar-tooltip-bg-backgroundColor; - color: white; - position: absolute; - font-size: 1rem; - white-space: nowrap; - right: 0; - top: 100%; - cursor: default; - border-radius: remCalc(8px); - } - } -} - .modal { margin: 0; max-height: 90%; diff --git a/assets/scss/components/stencil/heroCarousel/_heroCarousel.scss b/assets/scss/components/stencil/heroCarousel/_heroCarousel.scss index d2f9cbca31..9c5dafd474 100644 --- a/assets/scss/components/stencil/heroCarousel/_heroCarousel.scss +++ b/assets/scss/components/stencil/heroCarousel/_heroCarousel.scss @@ -19,7 +19,6 @@ // margin-bottom: (spacing("double") + spacing("single")); margin-bottom: 0; margin-top: -(spacing("single")); // 3 - overflow: hidden; opacity: 0; &.is-visible { @@ -31,8 +30,6 @@ } &.slick-initialized { - max-height: 100vh; - @include breakpoint("small") { max-height: remCalc(400px); } @@ -72,7 +69,6 @@ left: 25px; } } - .slick-dots { bottom: spacing("base"); text-align: right; @@ -129,11 +125,11 @@ } .heroCarousel-image-wrapper { - height: remCalc(250px); display: flex; justify-content: center; align-items: flex-start; height: 56.25vw; + max-height: 100vh; @include breakpoint("small") { max-height: remCalc(400px); @@ -145,24 +141,30 @@ } &.is-square-image-type { - .heroCarousel-image-wrapper { height: 100vw; - - @include breakpoint("small") { - height: 56.25vw; - } } } &.is-vertical-image-type { - .heroCarousel-image-wrapper { height: 110vw; - + } + } + + &.is-square-image-type, + &.is-vertical-image-type { + .heroCarousel-image-wrapper { + max-height: 100vh; + @include breakpoint("small") { height: 56.25vw; - } + max-height: remCalc(400px); + } + + @include breakpoint("medium") { + max-height: remCalc(600px); + } } } } @@ -185,6 +187,8 @@ right: 0; top: 50%; transform: translateY(-50%); + max-height: 80%; + overflow: auto; &.heroCarousel-content--empty { background-color: transparent; @@ -239,3 +243,37 @@ margin-top: spacing("single"); } } + +.heroCarousel-play-pause-button { + position: absolute; + left: 15px; + bottom: spacing("third"); + height: 32px; + min-width: 80px; + max-width: 90px; + font-size: 14px; + line-height: 1.25; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: $slick-play-pause-button-color; + transition: color 100ms ease-out; + z-index: zIndex("lowest"); + border: 1px solid $slick-play-pause-button-borderColor; + @include carouselOpaqueBackgrounds($slick-play-pause-button-bgColor); + + @include breakpoint("small") { + max-width: 150px; + font-size: 18px; + } + + @include breakpoint("medium") { + left: 25px; + bottom: spacing("single"); + } + + &:hover { + color: $slick-play-pause-button-color-hover; + } +} diff --git a/assets/scss/components/stencil/productView/_productView.scss b/assets/scss/components/stencil/productView/_productView.scss index 55f02834c8..24b92a2801 100644 --- a/assets/scss/components/stencil/productView/_productView.scss +++ b/assets/scss/components/stencil/productView/_productView.scss @@ -153,6 +153,7 @@ margin-bottom: 0; padding-bottom: spacing("single") + spacing("third"); } + padding-bottom: spacing("single") + spacing("third"); &.product-options { overflow: hidden; // 1 @@ -469,6 +470,8 @@ .productView-image .easyzoom-flyout { overflow: hidden; position: absolute; + top: 0; + left: 0; width: 100%; height: 100%; diff --git a/assets/scss/components/vendor/slick/_slick.scss b/assets/scss/components/vendor/slick/_slick.scss index 925a004c24..8aeffa6bb5 100644 --- a/assets/scss/components/vendor/slick/_slick.scss +++ b/assets/scss/components/vendor/slick/_slick.scss @@ -45,7 +45,7 @@ } .slick-next { - right: -10px; + right: $slick-arrows-offset; @include breakpoint("large") { right: -(spacing("double") + spacing("quarter")); @@ -64,7 +64,7 @@ } .slick-prev { - left: -15px; + left: $slick-arrows-offset; @include breakpoint("large") { left: -(spacing("double") + spacing("quarter")); @@ -186,3 +186,67 @@ div.slick-slider { .slick-list { margin-bottom: 12px; } + +// +// Carousel tooltips for buttons and bullets +// ----------------------------------------------------------------------------- + +.carousel-tooltip { + height: 1px; + display: block; + position: relative; + margin-top: 10px; + @include addFocusTooltip($attr: aria-label); + + &:after { + padding: 15px 10px; + top: 10px; + } + + .slick-prev:focus &, + .slick-next:focus &, + .slick-dots button:focus & { + &:before, + &:after { + display: block; + } + } + + .slick-prev &, + .slick-next & { + &:before { + top: -7px; + } + } + + .slick-prev & { + &:before { + right: -2px; + } + + &:after { + right: auto; + left: -5px; + } + } + + .slick-next & { + &:after { + right: -5px; + } + } + + .slick-dots button & { + margin-top: 25px; + + &:before, + &:after { + right: 50%; + transform: translateX(50%); + } + + &:before { + top: -7px; + } + } +} diff --git a/assets/scss/settings/citadel/forms/_settings.scss b/assets/scss/settings/citadel/forms/_settings.scss index 172abb4ed5..49bb511b63 100644 --- a/assets/scss/settings/citadel/forms/_settings.scss +++ b/assets/scss/settings/citadel/forms/_settings.scss @@ -231,3 +231,5 @@ $form-checkRadio-labelBefore-top: remCalc(3px); $form-checkRadio-labelAfter-top: remCalc(4px); $form-minMaxRow-column-gutter: $column-gutter / 4; + +$formInput-placeholder-color: stencilColor("input-font-color"); diff --git a/assets/scss/settings/vendor/slick/_settings.scss b/assets/scss/settings/vendor/slick/_settings.scss index 04fef7707f..825c2ee846 100644 --- a/assets/scss/settings/vendor/slick/_settings.scss +++ b/assets/scss/settings/vendor/slick/_settings.scss @@ -5,23 +5,28 @@ // // ----------------------------------------------------------------------------- -$slick-font-path: "./fonts/"; -$slick-font-family: inherit; -$slick-loader-path: null; -$slick-arrow-color: stencilColor("carousel-arrow-color"); -$slick-arrow-color-hover: stencilColor("carousel-arrow-color--hover"); -$slick-arrow-bgColor: rgba(stencilColor("carousel-arrow-bgColor"), 0.9); -$slick-arrow-borderColor: stencilColor("carousel-arrow-borderColor"); -$slick-dot-color: stencilColor("carousel-dot-color"); -$slick-dot-color-active: stencilColor("carousel-dot-color-active"); -$slick-dot-bgColor: rgba(stencilColor("carousel-dot-bgColor"), 0.9); -$slick-prev-character: ""; -$slick-next-character: ""; -$slick-dot-character: ""; -$slick-dot-size: 60px; -$slick-opacity-default: 1; -$slick-opacity-on-hover: 0.8; -$slick-opacity-not-active: 0.6; +$slick-font-path: "./fonts/"; +$slick-font-family: inherit; +$slick-loader-path: null; +$slick-arrow-color: stencilColor("carousel-arrow-color"); +$slick-arrow-color-hover: stencilColor("carousel-arrow-color--hover"); +$slick-arrow-bgColor: rgba(stencilColor("carousel-arrow-bgColor"), 0.9); +$slick-arrow-borderColor: stencilColor("carousel-arrow-borderColor"); +$slick-play-pause-button-color: stencilColor("carousel-play-pause-button-textColor"); +$slick-play-pause-button-color-hover: stencilColor("carousel-play-pause-button-textColor--hover"); +$slick-play-pause-button-bgColor: rgba(stencilColor("carousel-play-pause-button-bgColor"), 0.9); +$slick-play-pause-button-borderColor: stencilColor("carousel-play-pause-button-borderColor"); +$slick-dot-color: stencilColor("carousel-dot-color"); +$slick-dot-color-active: stencilColor("carousel-dot-color-active"); +$slick-dot-bgColor: rgba(stencilColor("carousel-dot-bgColor"), 0.9); +$slick-prev-character: ""; +$slick-next-character: ""; +$slick-dot-character: ""; +$slick-dot-size: 60px; +$slick-opacity-default: 1; +$slick-opacity-on-hover: 0.8; +$slick-opacity-not-active: 0.6; +$slick-arrows-offset: -5px; // Stencil Additional Settings diff --git a/assets/scss/theme.scss b/assets/scss/theme.scss index 499c2f439d..5d6597a4ed 100644 --- a/assets/scss/theme.scss +++ b/assets/scss/theme.scss @@ -18,11 +18,12 @@ // // 1. Stencil global settings get imported first. // 2. Import all Citadel and Foundation settings. -// 3. Import Citadel's version of foundation. -// - This enables the ability to "null" variables in the Stencil settings. +// 3. Common aria helpers. // 4. Import Stencil's component settings overrides. // 5. Import tools which set/reset Citadel's global settings, to be consumed by // the rest of Stencil. +// 6. Import Citadel's version of foundation. +// - This enables the ability to "null" variables in the Stencil settings. // // ----------------------------------------------------------------------------- @@ -36,7 +37,7 @@ @import "../../node_modules/@bigcommerce/citadel/dist/settings/foundation/foundation"; // 2 @import "../../node_modules/@bigcommerce/citadel/dist/settings/bigcommerce/bigcommerce"; // 2 -@import "../../node_modules/@bigcommerce/citadel/dist/vendor/foundation/foundation"; // 3 +@import "../../node_modules/@bigcommerce/citadel/dist/vendor/foundation/foundation"; // 6 @import "settings/normalize/normalize"; // 4 @import "settings/vendor/vendor"; // 4 @@ -70,8 +71,8 @@ @import "../../node_modules/@bigcommerce/citadel/dist/vendor/normalize/normalize"; // 1 @import "../../node_modules/@bigcommerce/citadel/dist/components/components"; // 2 -@import "components/components"; // 3 -@import "common/index"; +@import "common/index"; // 3 +@import "components/components"; // 6 // Layouts diff --git a/assets/scss/tools/_theme_focus.scss b/assets/scss/tools/_theme_focus.scss index d52542c731..e42d1be1ce 100644 --- a/assets/scss/tools/_theme_focus.scss +++ b/assets/scss/tools/_theme_focus.scss @@ -4,7 +4,7 @@ $outline-width: 2px; $outline-style: solid; -$outline-color: #2E8FFF; +$outline-color: #0F7FFF; $outline-offset: 1px; input, diff --git a/config.json b/config.json index 05929ad039..0c67d27e46 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "name": "Midwest Nice", - "version": "5.0.0", + "version": "5.1.0", "template_engine": "handlebars_v4", "meta": { "price": 0, @@ -54,12 +54,11 @@ "homepage_featured_products_count": 8, "homepage_top_products_count": 4, "homepage_show_carousel": true, - "homepage_show_carousel_arrows": false, - "homepage_show_carousel_dots": true, - "homepage_carousel_infinite": false, + "homepage_show_carousel_arrows": true, + "homepage_show_carousel_play_pause_button": true, + "homepage_stretch_carousel_images": false, "homepage_show_mobile_carousel": true, "homepage_show_curation_module": true, - "homepage_stretch_carousel_images": false, "homepage_new_products_column_count": 4, "homepage_featured_products_column_count": 4, "homepage_top_products_column_count": 4, @@ -192,6 +191,10 @@ "carousel-arrow-color--hover": "#474747", "carousel-arrow-bgColor": "#ffffff", "carousel-arrow-borderColor": "#ffffff", + "carousel-play-pause-button-textColor": "8f8f8f", + "carousel-play-pause-button-textColor--hover": "#474747", + "carousel-play-pause-button-bgColor": "#ffffff", + "carousel-play-pause-button-borderColor": "#ffffff", "card-title-color": "#333333", "card-title-color-hover": "#757575", "card-figcaption-button-background": "#ffffff", @@ -419,11 +422,6 @@ "large_catalog" ], "industries": [ - "arts_crafts", - "animals_pets", - "fashion_jewelry", - "gifts_specialty", - "health_beauty", "home_garden" ] }, @@ -764,9 +762,7 @@ "large_catalog" ], "industries": [ - "arts_crafts", - "food_beverage", - "books_entertainment" + "fashion_jewelry" ] }, "settings": { @@ -841,6 +837,10 @@ "carousel-arrow-color--hover": "#474747", "carousel-arrow-bgColor": "#ffffff", "carousel-arrow-borderColor": "#ffffff", + "carousel-play-pause-button-textColor": "#8F8F8F", + "carousel-play-pause-button-textColor--hover": "#474747", + "carousel-play-pause-button-bgColor": "#ffffff", + "carousel-play-pause-button-borderColor": "#ffffff", "card-title-color": "#ff957f", "card-title-color-hover": "#fab9a3", "card-figcaption-button-background": "#85cdcf", @@ -969,11 +969,7 @@ "large_catalog" ], "industries": [ - "automotive_industrial", - "electronics_computers", - "fashion_jewelry", - "sports_recreation", - "toys_games" + "food_beverage" ] }, "settings": { @@ -1039,13 +1035,17 @@ "carousel-bgColor": "#f3b679", "carousel-title-color": "#4f3f2f", "carousel-description-color": "#4f3f2f", - "carousel-dot-color": "#e6a15c", - "carousel-dot-color-active": "#e6a15c", + "carousel-dot-color": "#D47A21", + "carousel-dot-color-active": "#D47A21", "carousel-dot-bgColor": "#4f3f2f", "carousel-arrow-color": "#D47A21", "carousel-arrow-color--hover": "#765B42", "carousel-arrow-bgColor": "#ffffff", "carousel-arrow-borderColor": "#ffffff", + "carousel-play-pause-button-textColor": "#D47A21", + "carousel-play-pause-button-textColor--hover": "#765B42", + "carousel-play-pause-button-bgColor": "#ffffff", + "carousel-play-pause-button-borderColor": "#ffffff", "card-title-color": "#bd5b00", "card-title-color-hover": "#7f5e3f", "card-figcaption-button-background": "#f3b679", diff --git a/lang/en.json b/lang/en.json index d6efbce8fb..10417fb063 100755 --- a/lang/en.json +++ b/lang/en.json @@ -138,7 +138,7 @@ "store_credit_overview": "{credit} Store Credit", "generic_error": "Oops! Something went wrong.", "currency": "Select Currency: {code}", - "currency_switch_promotion" : "Promotions and gift certificates that don't apply to the new currency will be removed from your cart. Are you sure you want to continue?", + "currency_switch_promotion": "Promotions and gift certificates that don't apply to the new currency will be removed from your cart. Are you sure you want to continue?", "newsletter_signup": "Register for our newsletter", "form_submit": "Submit", "no_preference": "No Preference", @@ -256,7 +256,8 @@ "heading": "Your account has been created", "intro": "Thank you for creating your account at {store_name}. Your account details have been emailed to {email}", "continue": "Continue Shopping" - } + }, + "recaptcha_title": "Google recaptcha" }, "login": { "heading": "Sign in", @@ -351,10 +352,10 @@ "download_items": "Download Items", "card_ending": "ending in {card}", "shipments": { - "date": "Date Shipped", - "method": "Shipping Method", - "link": "Tracking Link", - "header": "Shipping Details" + "date": "Date Shipped", + "method": "Shipping Method", + "link": "Tracking Link", + "header": "Shipping Details" }, "actions": "Actions", "reorder": "Reorder", @@ -396,14 +397,14 @@ }, "paypal": "PayPal", "billing_address_labels": { - "address_line_1": "Address Line 1", - "address_line_2": "Address Line 2", - "suburb_city": "Suburb/City", - "state_province": "State/Province", - "choose_state": "Choose a State", - "country": "Country", - "choose_country": "Choose a Country", - "zip_postcode": "Zip/Postcode" + "address_line_1": "Address Line 1", + "address_line_2": "Address Line 2", + "suburb_city": "Suburb/City", + "state_province": "State/Province", + "choose_state": "Choose a State", + "country": "Country", + "choose_country": "Choose a Country", + "zip_postcode": "Zip/Postcode" } }, "returns": { @@ -448,7 +449,7 @@ "update_details": "Update Details" }, "recent_items": { - "heading" : "Recently Viewed", + "heading": "Recently Viewed", "no_items": "The items you've recently looked at on our site will appear below." }, "wishlists": { @@ -543,7 +544,6 @@ }, "edit": { "heading": "Update Address" - }, "confirm_delete": "Are you sure you want to delete this address?", "submit_value": "Save Address" @@ -899,7 +899,11 @@ "carousel": { "arrowAriaLabel": "Go to slide [NUMBER] of", "dotAriaLabel": "Slide number", - "activeDotAriaLabel": "active" + "activeDotAriaLabel": "active", + "playPauseButtonPlay": "Play", + "playPauseButtonPause": "Pause", + "playPauseButtonAriaPlay": "Play carousel", + "playPauseButtonAriaPause": "Pause carousel" }, "validation_messages": { "valid_email": "You must enter a valid email.", @@ -943,4 +947,4 @@ "price_max_not_entered": "Max. price is required.", "price_invalid_value": "Input must be greater than 0." } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4d14adc286..bafdd2504a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "bigcommerce-cornerstone", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bigcommerce-cornerstone", - "version": "5.0.0", + "version": "5.1.0", "license": "MIT", "dependencies": { - "@bigcommerce/stencil-utils": "^6.6.0", + "@bigcommerce/stencil-utils": "^6.8.0", "core-js": "^3.6.5", "creditcards": "^3.0.1", "easyzoom": "^2.5.3", diff --git a/package.json b/package.json index 874a47b13f..cb51b6e9cb 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "bigcommerce-cornerstone", "description": "The BigCommerce reference theme for the Stencil platform", - "version": "5.0.0", + "version": "5.1.0", "private": true, "author": "BigCommerce", "license": "MIT", "dependencies": { - "@bigcommerce/stencil-utils": "^6.6.0", + "@bigcommerce/stencil-utils": "^6.8.0", "core-js": "^3.6.5", "creditcards": "^3.0.1", "easyzoom": "^2.5.3", @@ -67,4 +67,4 @@ "lighthouse": "npx lighthouse --config-path=lighthouse-config.js --quiet --output json --chrome-flags=\"--headless\" $URL | jq '.audits | map_values(.rawValue)'", "test": "npx jest" } -} +} \ No newline at end of file diff --git a/schema.json b/schema.json index e50600866a..19b8629331 100644 --- a/schema.json +++ b/schema.json @@ -1014,6 +1014,12 @@ "force_reload": true, "id": "homepage_show_carousel_arrows" }, + { + "type": "checkbox", + "label": "i18n.ShowCarouselPlayPauseButton", + "force_reload": true, + "id": "homepage_show_carousel_play_pause_button" + }, { "type": "checkbox", "label": "i18n.AllowImageToStretchOn", @@ -1065,6 +1071,21 @@ "label": "i18n.ArrowBorder", "id": "carousel-arrow-borderColor" }, + { + "type": "color", + "label": "i18n.PlayPauseButtonText", + "id": "carousel-play-pause-button-textColor" + }, + { + "type": "color", + "label": "i18n.PlayPauseButtonBackground", + "id": "carousel-play-pause-button-bgColor" + }, + { + "type": "color", + "label": "i18n.PlayPauseButtonBorder", + "id": "carousel-play-pause-button-borderColor" + }, { "type": "heading", "content": "i18n.Products" diff --git a/schemaTranslations.json b/schemaTranslations.json index d26ee2b577..b995c6da9a 100644 --- a/schemaTranslations.json +++ b/schemaTranslations.json @@ -936,6 +936,13 @@ "uk": "Показати стрілки каруселі", "zh": "显示轮播箭头" }, + "i18n.ShowCarouselPlayPauseButton": { + "default": "Show carousel Play/Pause button", + "fr": "Show carousel Play/Pause button", + "it": "Show carousel Play/Pause button", + "uk": "Show carousel Play/Pause button", + "zh": "Show carousel Play/Pause button" + }, "i18n.AllowImageToStretchOn": { "default": "Allow image to stretch on large screens", "fr": "Permettre à l’image de s’étirer sur de grands écrans", @@ -1006,6 +1013,27 @@ "uk": "Кордон стрілки", "zh": "箭头边框" }, + "i18n.PlayPauseButtonText": { + "default": "Play/Pause button text", + "fr": "Play/Pause button text", + "it": "Play/Pause button text", + "uk": "Play/Pause button text", + "zh": "Play/Pause button text" + }, + "i18n.PlayPauseButtonBackground": { + "default": "Play/Pause button background", + "fr": "Play/Pause button background", + "it": "Play/Pause button background", + "uk": "Play/Pause button background", + "zh": "Play/Pause button background" + }, + "i18n.PlayPauseButtonBorder": { + "default": "Play/Pause button border", + "fr": "Play/Pause button border", + "it": "Play/Pause button border", + "uk": "Play/Pause button border", + "zh": "Play/Pause button border" + }, "i18n.NumberOfFeaturedProducts": { "default": "Number of featured products", "fr": "Nombre de produits en vedette", diff --git a/templates/components/account/wishlist-item-list.html b/templates/components/account/wishlist-item-list.html index f8fbdb7e23..7db81e8416 100644 --- a/templates/components/account/wishlist-item-list.html +++ b/templates/components/account/wishlist-item-list.html @@ -11,3 +11,4 @@ {{/each}} +{{> components/common/paginator wishlist.pagination}} diff --git a/templates/components/amp/products/product-options.html b/templates/components/amp/products/product-options.html index 372911a367..13c41a7300 100644 --- a/templates/components/amp/products/product-options.html +++ b/templates/components/amp/products/product-options.html @@ -8,9 +8,7 @@ {{{dynamicComponent 'components/products/options'}}} {{/each}} - {{#if product.can_purchase}} - {{> components/products/add-to-cart}} - {{/if}} + {{> components/products/add-to-cart}}
diff --git a/templates/components/amp/products/ratings.html b/templates/components/amp/products/ratings.html index 7401a4585e..8fee5edc36 100644 --- a/templates/components/amp/products/ratings.html +++ b/templates/components/amp/products/ratings.html @@ -1,11 +1,16 @@ -{{#for 1 5}} - {{#if ../this.rating '>=' $index}} - - {{else}} - - {{/if}} -{{/for}} + + {{#for 1 5}} + {{#if ../this.rating '>=' $index}} + diff --git a/templates/components/carousel-content.html b/templates/components/carousel-content.html index 90b4771bb7..877ff49886 100644 --- a/templates/components/carousel-content.html +++ b/templates/components/carousel-content.html @@ -2,6 +2,6 @@ + {{else}} + + {{/if}} + {{/for}} +{{{text}}}
{{{heading}}} {{#if button_text}} - + {{button_text}} {{/if}} diff --git a/templates/components/carousel.html b/templates/components/carousel.html index 6c7b531846..56756fd34b 100644 --- a/templates/components/carousel.html +++ b/templates/components/carousel.html @@ -15,10 +15,14 @@ "activeDotAriaLabel": "{{lang 'carousel.activeDotAriaLabel'}}" }'> {{#and arrows (if carousel.slides.length '>' 1)}} - + {{/and}} {{#each carousel.slides}} + {{#if button_text}} + diff --git a/templates/components/category/product-listing.html b/templates/components/category/product-listing.html index 79ba37609d..4abca77826 100644 --- a/templates/components/category/product-listing.html +++ b/templates/components/category/product-listing.html @@ -19,5 +19,11 @@ {{> components/common/paginator pagination.category}} {{else}} - {{lang 'categories.no_products'}} ++ {{lang 'categories.no_products'}} +
{{/if}} diff --git a/templates/components/common/body.html b/templates/components/common/body.html index 0c3d75a9fd..37249490b2 100644 --- a/templates/components/common/body.html +++ b/templates/components/common/body.html @@ -1,8 +1,8 @@ -