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

[Variant Picker] Add swatch display type #3180

Merged
merged 11 commits into from
Jan 9, 2024
157 changes: 157 additions & 0 deletions assets/component-product-variant-picker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
variant-selects {
display: block;
}

.product--no-media .product-form__input--pill,
.product--no-media .product-form__input--swatch,
.product--no-media .product-form__input--dropdown {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}

.product--no-media .product-form__input.product-form__input--pill,
.product--no-media .product-form__input.product-form__input--swatch {
flex-wrap: wrap;
margin: 0 auto 1.2rem auto;
}

.product--no-media .product-form__input--dropdown {
flex-direction: column;
max-width: 100%;
}

:is(.product-form__input--pill, .product-form__input--swatch) .form__label {
margin-bottom: 0.2rem;
}

.product-form__input input[type='radio'] {
clip: rect(0, 0, 0, 0);
overflow: hidden;
position: absolute;
height: 1px;
width: 1px;
}

.product-form__input input[type='radio']:not(.disabled) + label > .label-unavailable {
display: none;
}

.product-form__input--dropdown {
--swatch-input--size: 2rem;
margin-bottom: 1.6rem;
}


.product-form__input--dropdown .dropdown-swatch + select {
padding-left: calc(2.4rem + var(--swatch-input--size));
}

.product-form__input--dropdown .dropdown-swatch {
position: absolute;
left: 1.6rem;
top: calc(50% - var(--swatch-input--size) / 2);
width: var(--swatch-input--size);
height: var(--swatch-input--size);
z-index: 1;
}


/* Custom styles for Pill display type */
.product-form__input--pill input[type='radio'] + label {
border: var(--variant-pills-border-width) solid rgba(var(--color-foreground), var(--variant-pills-border-opacity));
background-color: rgb(var(--color-background));
color: rgba(var(--color-foreground));
border-radius: var(--variant-pills-radius);
color: rgb(var(--color-foreground));
display: inline-block;
margin: 0.7rem 0.5rem 0.2rem 0;
padding: 1rem 2rem;
font-size: 1.4rem;
letter-spacing: 0.1rem;
line-height: 1;
text-align: center;
transition: border var(--duration-short) ease;
cursor: pointer;
position: relative;
}

.product-form__input--pill input[type='radio'] + label:before {
content: '';
position: absolute;
top: calc(var(--variant-pills-border-width) * -1);
right: calc(var(--variant-pills-border-width) * -1);
bottom: calc(var(--variant-pills-border-width) * -1);
left: calc(var(--variant-pills-border-width) * -1);
z-index: -1;
border-radius: var(--variant-pills-radius);
box-shadow: var(--variant-pills-shadow-horizontal-offset) var(--variant-pills-shadow-vertical-offset)
var(--variant-pills-shadow-blur-radius) rgba(var(--color-shadow), var(--variant-pills-shadow-opacity));
}

.product-form__input--pill input[type='radio'] + label:hover {
border-color: rgb(var(--color-foreground));
}

.product-form__input--pill input[type='radio']:checked + label {
background-color: rgb(var(--color-foreground));
color: rgb(var(--color-background));
}

@media screen and (forced-colors: active) {
.product-form__input--pill input[type='radio']:checked + label {
text-decoration: underline;
}

.product-form__input--pill input[type='radio']:focus-visible + label {
outline: transparent solid 1px;
outline-offset: 2px;
}
}

.product-form__input--pill input[type='radio']:checked + label::selection {
background-color: rgba(var(--color-background), 0.3);
}

.product-form__input--pill input[type='radio']:disabled + label,
.product-form__input--pill input[type='radio'].disabled + label {
border-color: rgba(var(--color-foreground), 0.1);
color: rgba(var(--color-foreground), 0.6);
text-decoration: line-through;
}

.product-form__input--pill input[type='radio'].disabled:checked + label,
.product-form__input--pill input[type='radio']:disabled:checked + label {
color: rgba(var(--color-background), 0.6);
}
.product-form__input--pill input[type='radio']:focus-visible + label {
box-shadow: 0 0 0 0.3rem rgb(var(--color-background)), 0 0 0 0.5rem rgba(var(--color-foreground), 0.55);
}

/* Fallback */
.product-form__input--pill input[type='radio'].focused + label {
box-shadow: 0 0 0 0.3rem rgb(var(--color-background)), 0 0 0 0.5rem rgba(var(--color-foreground), 0.55);
}

/* No outline when focus-visible is available in the browser */
.no-js .product-form__input--pill input[type='radio']:focus:not(:focus-visible) + label {
box-shadow: none;
}
/* End custom styles for Pill display type */

/* Custom styles for Swatch display type */
.product-form__input--swatch {
display: flex;
}

.product-form__input--swatch .swatch-input__input + .swatch-input__label {
margin: 0.7rem 1.2rem 0.2rem 0;
}

@media screen and (min-width: 750px) {
.product-form__input--swatch .swatch-input__input + .swatch-input__label {
--swatch-input--size: 2.8rem;
}
}
/* End custom styles for Swatch display type */
69 changes: 69 additions & 0 deletions assets/component-swatch-input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* swatch-input lives in its own file for reusability of the swatch in other areas than the product form context */
.swatch-input__input + .swatch-input__label {
--swatch-input--size: 4.4rem;
--swatch-input--border-radius: 50%;

display: inline-block;
border-radius: var(--swatch-input--border-radius);
cursor: pointer;
outline-offset: 0.2rem;
forced-color-adjust: none;
}

.swatch-input__input + .swatch-input__label.swatch-input__label--square {
--swatch-input--border-radius: 0.2rem;
}

.swatch-input__input + .swatch-input__label:hover {
outline: 0.2rem solid rgba(var(--color-foreground), 0.4);
}

.swatch-input__input:active + .swatch-input__label,
.swatch-input__input:checked + .swatch-input__label {
outline: 0.1rem solid rgb(var(--color-foreground));
}

/* Visually disabled */
.swatch-input__input.disabled:not(:active):not(:checked) + .swatch-input__label:hover {
outline: none;
}

/* Focus visible */
.swatch-input__input:focus-visible + .swatch-input__label {
box-shadow: 0 0 0 0.5rem rgb(var(--color-background)), 0 0 0 0.7rem rgba(var(--color-foreground), 0.55);
}

/* Overrides for swatch snippet when used inside disabled swatch-input */
.swatch-input__input:disabled + .swatch-input__label > .swatch,
.swatch-input__input.disabled + .swatch-input__label > .swatch {
position: relative;
overflow: hidden;
}

/* Display white semi-transparent overlay over swatch when input is disabled */
.swatch-input__input:disabled + .swatch-input__label > .swatch::before,
.swatch-input__input.disabled + .swatch-input__label > .swatch::before {
content: '';
position: absolute;
inset: 0;
background: rgba(250, 250, 250, 0.5);
}

/* Display crossed out line over swatch when input is disabled */
.swatch-input__input:disabled + .swatch-input__label > .swatch::after,
.swatch-input__input.disabled + .swatch-input__label > .swatch::after {
/* Diagonal of a square = length of the side * sqrt(2) */
--diagonal--size: calc(var(--swatch-input--size) * 1.414);
--crossed-line--size: 0.1rem;
--crossed-line--color: rgb(0, 0, 0);

content: '';
position: absolute;
bottom: calc(var(--crossed-line--size) * -0.5);
left: 0;
width: var(--diagonal--size);
height: var(--crossed-line--size);
background-color: var(--crossed-line--color);
transform: rotate(-45deg);
transform-origin: left;
}
22 changes: 22 additions & 0 deletions assets/component-swatch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* swatch lives in its own file for reusability of the swatch in swatch-input and dropdown */
.swatch {
--swatch--size: var(--swatch-input--size, 4.4rem);
--swatch--border-radius: var(--swatch-input--border-radius, 50%);

display: block;
width: var(--swatch--size);
aspect-ratio: 1 / 1;
background: var(--swatch--background);
background-size: cover;
border: 0.1rem solid rgba(var(--color-background-contrast), 0.5);
border-radius: var(--swatch--border-radius);
}

.swatch--square {
--swatch--border-radius: var(--swatch-input--border-radius, 0.2rem);
}

.swatch--unavailable {
border-style: dashed;
border-color: rgba(var(--color-foreground), 0.5);
}
74 changes: 41 additions & 33 deletions assets/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -959,9 +959,10 @@ class VariantSelects extends HTMLElement {
this.addEventListener('change', this.onVariantChange);
}

onVariantChange() {
onVariantChange(event) {
this.updateOptions();
this.updateMasterId();
this.updateSelectedSwatchValue(event);
this.toggleAddButton(true, '', false);
this.updatePickupAvailability();
this.removeErrorMessage();
Expand All @@ -980,7 +981,14 @@ class VariantSelects extends HTMLElement {
}

updateOptions() {
this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
this.options = Array.from(this.querySelectorAll('select, fieldset'), (element) => {
if (element.tagName === 'SELECT') {
return element.value;
}
if (element.tagName === 'FIELDSET') {
return Array.from(element.querySelectorAll('input')).find((radio) => radio.checked)?.value;
}
});
}

updateMasterId() {
Expand All @@ -993,6 +1001,26 @@ class VariantSelects extends HTMLElement {
});
}

updateSelectedSwatchValue({ target }) {
const { name, value, tagName } = target;

if (tagName === 'SELECT' && target.selectedOptions.length) {
const swatchValue = target.selectedOptions[0].dataset.optionSwatchValue;
const selectedDropdownSwatchValue = this.querySelector(`[data-selected-dropdown-swatch="${name}"] > .swatch`);
if (!selectedDropdownSwatchValue) return;
if (swatchValue) {
selectedDropdownSwatchValue.style.setProperty('--swatch--background', swatchValue);
selectedDropdownSwatchValue.classList.remove('swatch--unavailable');
} else {
selectedDropdownSwatchValue.style.setProperty('--swatch--background', 'unset');
selectedDropdownSwatchValue.classList.add('swatch--unavailable');
}
} else if (tagName === 'INPUT' && target.type === 'radio') {
const selectedSwatchValue = this.querySelector(`[data-selected-swatch-value="${name}"]`);
if (selectedSwatchValue) selectedSwatchValue.innerHTML = value;
}
}

updateMedia() {
if (!this.currentVariant) return;
if (!this.currentVariant.featured_media) return;
Expand Down Expand Up @@ -1046,12 +1074,17 @@ class VariantSelects extends HTMLElement {
});
}

setInputAvailability(listOfOptions, listOfAvailableOptions) {
listOfOptions.forEach((input) => {
if (listOfAvailableOptions.includes(input.getAttribute('value'))) {
input.innerText = input.getAttribute('value');
} else {
input.innerText = window.variantStrings.unavailable_with_option.replace('[value]', input.getAttribute('value'));
setInputAvailability(elementList, availableValuesList) {
elementList.forEach((element) => {
const value = element.getAttribute('value');
const availableElement = availableValuesList.includes(value);

if (element.tagName === 'INPUT') {
element.classList.toggle('disabled', !availableElement);
} else if (element.tagName === 'OPTION') {
element.innerText = availableElement
? value
: window.variantStrings.unavailable_with_option.replace('[value]', value);
}
});
}
Expand Down Expand Up @@ -1206,31 +1239,6 @@ class VariantSelects extends HTMLElement {

customElements.define('variant-selects', VariantSelects);

class VariantRadios extends VariantSelects {
constructor() {
super();
}

setInputAvailability(listOfOptions, listOfAvailableOptions) {
listOfOptions.forEach((input) => {
if (listOfAvailableOptions.includes(input.getAttribute('value'))) {
input.classList.remove('disabled');
} else {
input.classList.add('disabled');
}
});
}

updateOptions() {
const fieldsets = Array.from(this.querySelectorAll('fieldset'));
this.options = fieldsets.map((fieldset) => {
return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
});
}
}

customElements.define('variant-radios', VariantRadios);

class ProductRecommendations extends HTMLElement {
constructor() {
super();
Expand Down
1 change: 0 additions & 1 deletion assets/product-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ if (!customElements.get('product-info')) {
super();
this.input = this.querySelector('.quantity__input');
this.currentVariant = this.querySelector('.product-variant-id');
this.variantSelects = this.querySelector('variant-radios');
this.submitButton = this.querySelector('[type="submit"]');
}

Expand Down
4 changes: 2 additions & 2 deletions assets/quick-add.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ if (!customElements.get('quick-add-modal')) {
}

preventVariantURLSwitching() {
const variantPicker = this.modalContent.querySelector('variant-radios,variant-selects');
const variantPicker = this.modalContent.querySelector('variant-selects');
if (!variantPicker) return;

variantPicker.setAttribute('data-update-url', 'false');
Expand All @@ -87,7 +87,7 @@ if (!customElements.get('quick-add-modal')) {
preventDuplicatedIDs() {
const sectionId = this.productElement.dataset.section;
this.productElement.innerHTML = this.productElement.innerHTML.replaceAll(sectionId, `quickadd-${sectionId}`);
this.productElement.querySelectorAll('variant-selects, variant-radios, product-info').forEach((element) => {
this.productElement.querySelectorAll('variant-selects, product-info').forEach((element) => {
element.dataset.originalSection = sectionId;
});
}
Expand Down
Loading