Skip to content

Commit

Permalink
feat(storefront): BCTHEME-122 Add labels to swatches (#1761)
Browse files Browse the repository at this point in the history
  • Loading branch information
BC-tymurbiedukhin authored Aug 26, 2020
1 parent 3c76310 commit ad952be
Show file tree
Hide file tree
Showing 20 changed files with 132 additions and 19 deletions.
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
File renamed without changes.
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

0 comments on commit ad952be

Please sign in to comment.