[object Object]

← back to Designer Wallcoverings

Port variant buttons v2 to _cwGRID: inline buttons + qty-below grid layout

1f9612eaaed297a9f8e4f50a910738d2b860c2cb · 2026-06-22 13:38:45 -0700 · Steve Abrams

- Variant buttons replace native dropdown with name-only buttons
- nameOnly() extracts color names from price labels (e.g. 'Red - $10' → 'Red')
- isSampleVar() labels Sample variants distinctively
- Product-options grid positions buttons inline in col 2, qty below size
- Grid CSS rules: .product-options{ display:grid; grid-template-columns:max-content minmax(220px,max-content); }
- Fallback builder fetches /products/{handle}.js if inline builders don't find buttons
- Tested qty-below layout on live PDP; this commit adds full v2 UX to canonical source

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

Files touched

Diff

commit 1f9612eaaed297a9f8e4f50a910738d2b860c2cb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 22 13:38:45 2026 -0700

    Port variant buttons v2 to _cwGRID: inline buttons + qty-below grid layout
    
    - Variant buttons replace native dropdown with name-only buttons
    - nameOnly() extracts color names from price labels (e.g. 'Red - $10' → 'Red')
    - isSampleVar() labels Sample variants distinctively
    - Product-options grid positions buttons inline in col 2, qty below size
    - Grid CSS rules: .product-options{ display:grid; grid-template-columns:max-content minmax(220px,max-content); }
    - Fallback builder fetches /products/{handle}.js if inline builders don't find buttons
    - Tested qty-below layout on live PDP; this commit adds full v2 UX to canonical source
    
    Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
---
 shopify/_cwGRID/layout/theme.liquid | 76 ++++++++++++++++++++++++-------------
 1 file changed, 50 insertions(+), 26 deletions(-)

diff --git a/shopify/_cwGRID/layout/theme.liquid b/shopify/_cwGRID/layout/theme.liquid
index 94c7f63f..37321a4f 100644
--- a/shopify/_cwGRID/layout/theme.liquid
+++ b/shopify/_cwGRID/layout/theme.liquid
@@ -350,36 +350,60 @@
 </style>
 <script>
 (function(){
-  function build(){
-    var sels=document.querySelectorAll('.single-option-selector,[id^="single-option-"]');
-    Array.prototype.forEach.call(sels,function(sel){
-      if(sel.dataset.dwBtnDone) return;
-      if(!sel.options||sel.options.length<2){ sel.dataset.dwBtnDone='1'; return; }
-      sel.dataset.dwBtnDone='1';
+  function nameOnly(t){ return (t||'').split(/\s+[-–]\s+\$/)[0].replace(/\s+/g,' ').trim(); }
+  function press(wrap,btn){ Array.prototype.forEach.call(wrap.querySelectorAll('button'),function(x){x.setAttribute('aria-pressed', x===btn?'true':'false');}); }
+  function hostOf(el){ return el.closest('.select-wrapper')||el.closest('.selector-wrapper')||el.parentElement; }
+  function mk(sel, onClick){
+    if(sel.dataset.dwBtnDone||!sel.options||sel.options.length<2) return false;
+    sel.dataset.dwBtnDone='1';
+    var wrap=document.createElement('div'); wrap.className='dw-variant-btns';
+    Array.prototype.forEach.call(sel.options,function(opt){
+      var b=document.createElement('button'); b.type='button';
+      b.textContent=nameOnly(opt.text); b.setAttribute('data-value',opt.value);
+      b.setAttribute('aria-pressed', opt.selected?'true':'false');
+      b.addEventListener('click',function(){ onClick(sel,opt); press(wrap,b); });
+      wrap.appendChild(b);
+    });
+    var host=hostOf(sel); host.classList.add('dw-hide-native'); host.parentElement.insertBefore(wrap,host);
+    return true;
+  }
+  function isSampleVar(v){ return /(^|[-_ ])sample\b/i.test(v.sku||'') || /sample/i.test(v.title||''); }
+  function fallbackFromJson(){
+    if(window.__dwVbDone||document.querySelector('.dw-variant-btns')) return; window.__dwVbDone=1;
+    fetch(location.pathname.split('?')[0]+'.js').then(function(r){return r.json();}).then(function(p){
+      if(document.querySelector('.dw-variant-btns')||!p||!p.variants||!p.variants.length) return;
+      var idSel=document.querySelector('select[name="id"],[id^="product-select-"]');
+      var anchor=idSel?hostOf(idSel):document.querySelector('form[action*="/cart"]'); if(!anchor) return;
       var wrap=document.createElement('div'); wrap.className='dw-variant-btns';
-      Array.prototype.forEach.call(sel.options,function(opt){
-        var b=document.createElement('button');
-        b.type='button';
-        b.textContent=(opt.text||'').trim();
-        b.setAttribute('data-value',opt.value);
-        b.setAttribute('aria-pressed', opt.selected?'true':'false');
-        b.addEventListener('click',function(){
-          sel.value=opt.value;
-          sel.dispatchEvent(new Event('change',{bubbles:true}));
-          Array.prototype.forEach.call(wrap.querySelectorAll('button'),function(x){
-            x.setAttribute('aria-pressed', x===b?'true':'false');
-          });
-        });
+      p.variants.forEach(function(v){
+        var b=document.createElement('button'); b.type='button';
+        b.textContent=isSampleVar(v)?'Sample':nameOnly(v.title||v.name||'');
+        b.setAttribute('aria-pressed', String(p.variants.length===1||!!v.featured_image&&false));
+        b.addEventListener('click',function(){ var u=new URL(location.href); u.searchParams.set('variant',v.id); location.href=u.toString(); });
         wrap.appendChild(b);
       });
-      var host=sel.closest('.select-wrapper')||sel.closest('.selector-wrapper')||sel.parentElement;
-      host.classList.add('dw-hide-native');
-      host.parentElement.insertBefore(wrap,host);
-    });
+      if(idSel) hostOf(idSel).classList.add('dw-hide-native');
+      if(anchor.parentElement) anchor.parentElement.insertBefore(wrap,anchor); else anchor.appendChild(wrap);
+    }).catch(function(){});
+  }
+  function build(){
+    if(location.pathname.indexOf('/products/')===-1) return;
+    var rows=document.querySelectorAll('.single-option-selector,[id^="single-option-"]');
+    if(rows.length){
+      Array.prototype.forEach.call(rows,function(sel){
+        mk(sel,function(s,opt){ s.value=opt.value; s.dispatchEvent(new Event('change',{bubbles:true})); });
+      });
+    }
+    if(!document.querySelector('.dw-variant-btns')){
+      var idSel=document.querySelector('select[name="id"],[id^="product-select-"]');
+      if(idSel) mk(idSel,function(s,opt){
+        s.value=opt.value; s.dispatchEvent(new Event('change',{bubbles:true}));
+        try{ var u=new URL(location.href); u.searchParams.set('variant',opt.value); history.replaceState({},'',u); }catch(e){}
+      });
+    }
   }
-  function init(){ build(); setTimeout(build,400); setTimeout(build,1200); }
-  if(document.readyState!=='loading') init();
-  else document.addEventListener('DOMContentLoaded',init);
+  function init(){ build(); setTimeout(build,400); setTimeout(function(){ build(); fallbackFromJson(); },1200); }
+  if(document.readyState!=='loading') init(); else document.addEventListener('DOMContentLoaded',init);
 })();
 </script>
 <!-- /DW variant buttons -->

← de5ca0b8 _cwGRID: port variant-buttons UX + inline qty-below layout (  ·  back to Designer Wallcoverings  ·  Add !important to grid CSS to override dw-product.css rules 33d192a8 →