Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storefront): BCTHEME-122 Add labels to swatches #1761

Merged
merged 2 commits into from
Aug 26, 2020
Merged
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
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"arrow-parens": 0,
"prefer-destructuring": 0,
"import/no-named-as-default": 0,
"import/no-named-as-default-member": 0
"import/no-named-as-default-member": 0,
"import/prefer-default-export": "off"
},
"globals": {
"$": true,
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Draft
- Add labels to swatches. [#1761](https://github.com/bigcommerce/cornerstone/pull/1761)
- ARIA attributes on Write Review modal need valid values. [#1790](https://github.com/bigcommerce/cornerstone/pull/1790)
- Fixed improper heading hierarchy on PLPs. [#1779](https://github.com/bigcommerce/cornerstone/pull/1779)
- Cornerstone - Cart link not visible on mobile Chrome depending on swatch image size. [#1793](https://github.com/bigcommerce/cornerstone/pull/1793)
Expand Down
4 changes: 2 additions & 2 deletions assets/js/test-unit/theme/common/faceted-search.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import FacetedSearch from '../../../theme/common/faceted-search';
import { Validators } from '../../../theme/common/form-utils';
import { Validators } from '../../../theme/common/utils/form-utils';
import $ from 'jquery';
import { hooks, api } from '@bigcommerce/stencil-utils';
import urlUtils from '../../../theme/common/url-utils';
import urlUtils from '../../../theme/common/utils/url-utils';

describe('FacetedSearch', () => {
let facetedSearch;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/test-unit/theme/common/form-utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Validators } from '../../../theme/common/form-utils';
import { Validators } from '../../../theme/common/utils/form-utils';

describe('Validators', () => {
let validator;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/test-unit/theme/common/url-utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import urlUtil from '../../../theme/common/url-utils';
import urlUtil from '../../../theme/common/utils/url-utils';

describe('Url Utilities', () => {
describe('urlUtils', () => {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import nod from './common/nod';
import Wishlist from './wishlist';
import validation from './common/form-validation';
import stateCountry from './common/state-country';
import { classifyForm, Validators, insertStateHiddenField } from './common/form-utils';
import { classifyForm, Validators, insertStateHiddenField } from './common/utils/form-utils';
import { creditCardType, storeInstrument, Validators as CCValidators, Formatters as CCFormatters } from './common/payment-method';
import swal from './global/sweet-alert';

Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import stateCountry from './common/state-country';
import nod from './common/nod';
import validation from './common/form-validation';
import forms from './common/models/forms';
import { classifyForm, Validators } from './common/form-utils';
import { classifyForm, Validators } from './common/utils/form-utils';

export default class Auth extends PageManager {
constructor(context) {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/cart/shipping-estimator.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import stateCountry from '../common/state-country';
import nod from '../common/nod';
import utils from '@bigcommerce/stencil-utils';
import { Validators } from '../common/form-utils';
import { Validators } from '../common/utils/form-utils';
import swal from '../global/sweet-alert';

export default class ShippingEstimator {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/catalog.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PageManager from './page-manager';
import urlUtils from './common/url-utils';
import urlUtils from './common/utils/url-utils';
import Url from 'url';

export default class CatalogPage extends PageManager {
Expand Down
8 changes: 8 additions & 0 deletions assets/js/theme/common/aria/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const ariaKeyCodes = {
RETURN: 13,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
};
1 change: 1 addition & 0 deletions assets/js/theme/common/aria/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as initRadioOptions } from './radioOptions';
60 changes: 60 additions & 0 deletions assets/js/theme/common/aria/radioOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ariaKeyCodes } from './constants';

const setCheckedRadioItem = (itemCollection, itemIdx) => {
itemCollection.each((idx, item) => {
const $item = $(item);
if (idx !== itemIdx) {
$item.attr('aria-checked', false).prop('checked', false);
return;
}

$item.attr('aria-checked', true).prop('checked', true).focus();
});
};

const calculateTargetItemPosition = (lastItemIdx, currentIdx) => {
switch (true) {
case currentIdx > lastItemIdx: return 0;
case currentIdx < 0: return lastItemIdx;
default: return currentIdx;
}
};

const handleItemKeyDown = itemCollection => e => {
const { keyCode } = e;
const itemIdx = itemCollection.index(e.currentTarget);
const lastCollectionItemIdx = itemCollection.length - 1;

if (Object.values(ariaKeyCodes).includes(keyCode)) {
e.preventDefault();
e.stopPropagation();
}

switch (keyCode) {
case ariaKeyCodes.RETURN:
case ariaKeyCodes.SPACE: {
setCheckedRadioItem(itemCollection, itemIdx);
break;
}
case ariaKeyCodes.LEFT:
case ariaKeyCodes.UP: {
const prevItemIdx = calculateTargetItemPosition(lastCollectionItemIdx, itemIdx - 1);
itemCollection.get(prevItemIdx).focus();
break;
}
case ariaKeyCodes.RIGHT:
case ariaKeyCodes.DOWN: {
const nextItemIdx = calculateTargetItemPosition(lastCollectionItemIdx, itemIdx + 1);
itemCollection.get(nextItemIdx).focus();
break;
}

default: break;
}
};

export default ($container, itemSelector) => {
const $itemCollection = $container.find(itemSelector);

$container.on('keydown', itemSelector, handleItemKeyDown($itemCollection));
};
4 changes: 2 additions & 2 deletions assets/js/theme/common/faceted-search.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { hooks, api } from '@bigcommerce/stencil-utils';
import _ from 'lodash';
import Url from 'url';
import urlUtils from './url-utils';
import urlUtils from './utils/url-utils';
import modalFactory from '../global/modal';
import collapsibleFactory from './collapsible';
import { Validators } from './form-utils';
import { Validators } from './utils/form-utils';
import nod from './nod';

/**
Expand Down
33 changes: 33 additions & 0 deletions assets/js/theme/common/product-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,23 @@ import modalFactory, { showAlertModal, modalTypes } from '../global/modal';
import _ from 'lodash';
import Wishlist from '../wishlist';
import { normalizeFormData } from './utils/api';
import { initRadioOptions } from './aria';
import { isBrowserIE, convertIntoArray } from './utils/ie-helpers';

const optionsTypesMap = {
INPUT_FILE: 'input-file',
INPUT_TEXT: 'input-text',
INPUT_NUMBER: 'input-number',
INPUT_CHECKBOX: 'input-checkbox',
TEXTAREA: 'textarea',
DATE: 'date',
SET_SELECT: 'set-select',
SET_RECTANGLE: 'set-rectangle',
SET_RADIO: 'set-radio',
SWATCH: 'swatch',
PRODUCT_LIST: 'product-list',
};

export default class ProductDetails {
constructor($scope, context, productAttributesData = {}) {
this.$overlay = $('[data-cart-item-add] .loadingOverlay');
Expand All @@ -25,6 +40,12 @@ export default class ProductDetails {
const hasOptions = $productOptionsElement.html().trim().length;
const hasDefaultOptions = $productOptionsElement.find('[data-default]').length;

$('[data-product-attribute]').each((__, value) => {
const type = value.getAttribute('data-product-attribute');

this._makeProductVariantAccessible(value, type);
});

$productOptionsElement.on('change', event => {
this.productOptionsChanged(event);
this.setProductVariant();
Expand Down Expand Up @@ -58,6 +79,18 @@ export default class ProductDetails {
this.previewModal = modalFactory('#previewModal')[0];
}

_makeProductVariantAccessible(variantDomNode, variantType) {
switch (variantType) {
case optionsTypesMap.SET_RADIO:
case optionsTypesMap.SWATCH: {
initRadioOptions($(variantDomNode), '[type=radio]');
break;
}

default: break;
}
}

setProductVariant() {
const unsatisfiedRequiredFields = [];
const options = [];
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/common/state-country.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import utils from '@bigcommerce/stencil-utils';
import _ from 'lodash';
import { insertStateHiddenField } from './form-utils';
import { insertStateHiddenField } from './utils/form-utils';
import { showAlertModal } from '../global/modal';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'lodash';
import nod from './nod';
import forms from './models/forms';
import nod from '../nod';
import forms from '../models/forms';

const inputTagNames = [
'input',
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Review from './product/reviews';
import collapsibleFactory from './common/collapsible';
import ProductDetails from './common/product-details';
import videoGallery from './product/video-gallery';
import { classifyForm } from './common/form-utils';
import { classifyForm } from './common/utils/form-utils';

export default class Product extends PageManager {
constructor(context) {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { hooks } from '@bigcommerce/stencil-utils';
import CatalogPage from './catalog';
import FacetedSearch from './common/faceted-search';
import compareProducts from './global/compare-products';
import urlUtils from './common/url-utils';
import urlUtils from './common/utils/url-utils';
import Url from 'url';
import collapsibleFactory from './common/collapsible';
import 'jstree';
Expand Down
15 changes: 12 additions & 3 deletions templates/components/products/options/swatch.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="form-field" data-product-attribute="swatch">
<label class="form-label form-label--alternate form-label--inlineSmall">
<div class="form-field" data-product-attribute="swatch" role="radiogroup" aria-labelledby="swatchGroup">
<label class="form-label form-label--alternate form-label--inlineSmall" id="swatchGroup">
{{this.display_name}}:
<span data-option-value></span>

Expand All @@ -14,14 +14,23 @@
value=""
id="attribute_swatch_{{../id}}_none"
checked="{{#if defaultValue '==' ''}}checked{{/if}}"
aria-label="{{lang 'products.none'}}"
>
<label class="form-option form-option-swatch" for="attribute_swatch_{{../id}}_none">
<span class='form-option-variant form-option-variant--none' title="{{lang 'products.none'}}">{{lang 'products.none'}}</span>
</label>
{{/unless}}

{{#each this.values}}
<input class="form-radio" type="radio" name="attribute[{{../id}}]" value="{{id}}" id="attribute_swatch_{{../id}}_{{id}}" {{#if selected}}checked data-default{{/if}} {{#if ../required}}required{{/if}}>
<input class="form-radio"
type="radio"
name="attribute[{{../id}}]"
value="{{id}}"
id="attribute_swatch_{{../id}}_{{id}}"
{{#if selected}}checked data-default{{/if}}
{{#if ../required}}required{{/if}}
aria-label="{{this.label}}"
>
<label class="form-option form-option-swatch" for="attribute_swatch_{{../id}}_{{id}}" data-product-attribute-value="{{id}}">
{{#if image}}
<span class='form-option-variant form-option-variant--pattern' title="{{this.label}}" style="background-image: url('{{getImage image "swatch_option_size"}}');"></span>
Expand Down