Add Shopify variant options selector with circular radio buttons
All checks were successful
Deploy / Deploy to Production (push) Successful in 7s
All checks were successful
Deploy / Deploy to Production (push) Successful in 7s
- Implement dynamic option rendering from Shopify variant data - Generate circular radio buttons for product variants (sizes, colors, etc.) - Disable add-to-cart button until option is selected - Display "Choisissez une option" text when option required - Update button text and enable on option selection - Add is-selected class to chosen option - Handle disabled state for out-of-stock variants - Restore btn__default button style with icon and text structure - Add chooseOption translation key in FR/EN Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0c8cc5000c
commit
4987c4830f
5 changed files with 109 additions and 69 deletions
|
|
@ -55,9 +55,8 @@
|
|||
renderPrice(product);
|
||||
renderDetails(product, isEnglish);
|
||||
renderImages(product, isEnglish);
|
||||
renderVariants(product);
|
||||
renderOptions(product);
|
||||
setupAddToCart(product);
|
||||
renderStock(product);
|
||||
}
|
||||
|
||||
function renderTitle(product, isEnglish) {
|
||||
|
|
@ -113,42 +112,78 @@
|
|||
}
|
||||
}
|
||||
|
||||
function renderVariants(product) {
|
||||
function renderOptions(product) {
|
||||
if (product.variants.edges.length <= 1) return;
|
||||
|
||||
const variantsContainer = document.querySelector("[data-product-variants]");
|
||||
const variantSelector = document.querySelector("[data-variant-selector]");
|
||||
const firstVariant = product.variants.edges[0].node;
|
||||
if (!firstVariant.selectedOptions || firstVariant.selectedOptions.length === 0) return;
|
||||
|
||||
if (!variantsContainer || !variantSelector) return;
|
||||
const mainOption = firstVariant.selectedOptions[0];
|
||||
const optionValues = new Set();
|
||||
|
||||
variantsContainer.style.display = "block";
|
||||
|
||||
variantSelector.innerHTML = product.variants.edges
|
||||
.map((edge) => {
|
||||
const variant = edge.node;
|
||||
const variantId = variant.id.replace(
|
||||
"gid://shopify/ProductVariant/",
|
||||
""
|
||||
);
|
||||
const price = parseFloat(variant.price.amount).toFixed(2) + "€";
|
||||
const availability = variant.availableForSale
|
||||
? ""
|
||||
: " (Rupture de stock)";
|
||||
|
||||
return `<option value="${variantId}" ${
|
||||
!variant.availableForSale ? "disabled" : ""
|
||||
}>
|
||||
${variant.title} - ${price}${availability}
|
||||
</option>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
variantSelector.addEventListener("change", (e) => {
|
||||
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||
if (addToCartBtn) {
|
||||
addToCartBtn.dataset.variantId = e.target.value;
|
||||
product.variants.edges.forEach(edge => {
|
||||
const variant = edge.node;
|
||||
if (variant.selectedOptions && variant.selectedOptions[0]) {
|
||||
optionValues.add(variant.selectedOptions[0].value);
|
||||
}
|
||||
});
|
||||
|
||||
if (optionValues.size <= 1) return;
|
||||
|
||||
const optionsContainer = document.querySelector("[data-product-options]");
|
||||
const optionsList = document.querySelector("[data-product-options-list]");
|
||||
|
||||
if (!optionsContainer || !optionsList) return;
|
||||
|
||||
const optionName = mainOption.name;
|
||||
const optionSlug = optionName.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
optionsList.innerHTML = Array.from(optionValues).map((value) => {
|
||||
const uniqueId = `${optionSlug}-${value.toLowerCase().replace(/\s+/g, '-')}`;
|
||||
const variant = product.variants.edges.find(e =>
|
||||
e.node.selectedOptions && e.node.selectedOptions[0]?.value === value
|
||||
)?.node;
|
||||
const isAvailable = variant?.availableForSale || false;
|
||||
|
||||
return `
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
id="${uniqueId}"
|
||||
name="${optionSlug}"
|
||||
value="${value}"
|
||||
data-variant-id="${variant ? variant.id.replace('gid://shopify/ProductVariant/', '') : ''}"
|
||||
${!isAvailable ? 'disabled' : ''}
|
||||
/>
|
||||
<label for="${uniqueId}">${value}</label>
|
||||
</li>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
optionsContainer.style.display = 'block';
|
||||
|
||||
const radios = optionsList.querySelectorAll('input[type="radio"]');
|
||||
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||
const buttonText = addToCartBtn?.querySelector('[data-button-text]');
|
||||
|
||||
radios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
const variantId = this.dataset.variantId;
|
||||
|
||||
if (addToCartBtn) {
|
||||
addToCartBtn.dataset.variantId = variantId;
|
||||
addToCartBtn.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
if (buttonText) {
|
||||
buttonText.textContent = addToCartBtn.dataset.defaultText || 'Ajouter au panier';
|
||||
}
|
||||
|
||||
const allLi = optionsList.querySelectorAll('li');
|
||||
allLi.forEach(li => li.classList.remove('is-selected'));
|
||||
this.closest('li').classList.add('is-selected');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupAddToCart(product) {
|
||||
|
|
@ -158,31 +193,35 @@
|
|||
const productId = product.id.replace("gid://shopify/Product/", "");
|
||||
addToCartBtn.dataset.productId = productId;
|
||||
|
||||
const firstAvailableVariant = product.variants.edges.find(
|
||||
(e) => e.node.availableForSale
|
||||
);
|
||||
if (firstAvailableVariant) {
|
||||
const variantId = firstAvailableVariant.node.id.replace(
|
||||
"gid://shopify/ProductVariant/",
|
||||
""
|
||||
);
|
||||
addToCartBtn.dataset.variantId = variantId;
|
||||
}
|
||||
}
|
||||
const hasMultipleVariants = product.variants.edges.length > 1;
|
||||
const firstVariant = product.variants.edges[0]?.node;
|
||||
const hasOptions = firstVariant?.selectedOptions && firstVariant.selectedOptions.length > 0;
|
||||
|
||||
function renderStock(product) {
|
||||
const stockEl = document.querySelector("[data-product-stock]");
|
||||
if (!stockEl) return;
|
||||
const uniqueOptions = new Set();
|
||||
product.variants.edges.forEach(edge => {
|
||||
if (edge.node.selectedOptions && edge.node.selectedOptions[0]) {
|
||||
uniqueOptions.add(edge.node.selectedOptions[0].value);
|
||||
}
|
||||
});
|
||||
const hasMultipleOptions = uniqueOptions.size > 1;
|
||||
|
||||
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||
|
||||
if (product.availableForSale) {
|
||||
stockEl.textContent = addToCartBtn?.dataset.textInStock || "En stock";
|
||||
stockEl.classList.add("in-stock");
|
||||
if (hasMultipleVariants && hasOptions && hasMultipleOptions) {
|
||||
addToCartBtn.setAttribute('disabled', 'disabled');
|
||||
const buttonText = addToCartBtn.querySelector('[data-button-text]');
|
||||
if (buttonText) {
|
||||
buttonText.textContent = addToCartBtn.dataset.textChooseOption || 'Choisissez une option';
|
||||
}
|
||||
} else {
|
||||
stockEl.textContent =
|
||||
addToCartBtn?.dataset.textOutOfStock || "Rupture de stock";
|
||||
stockEl.classList.add("out-of-stock");
|
||||
const firstAvailableVariant = product.variants.edges.find(
|
||||
(e) => e.node.availableForSale
|
||||
);
|
||||
if (firstAvailableVariant) {
|
||||
const variantId = firstAvailableVariant.node.id.replace(
|
||||
"gid://shopify/ProductVariant/",
|
||||
""
|
||||
);
|
||||
addToCartBtn.dataset.variantId = variantId;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue