Add Shopify variant options selector with circular radio buttons
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:
isUnknown 2026-01-16 12:15:28 +01:00
parent 0c8cc5000c
commit 4987c4830f
5 changed files with 109 additions and 69 deletions

View file

@ -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;
}
}
}
})();