[object Object]

← back to Designer Wallcoverings

5.1 dev: horizontal collapsible Boost filter bar (dw-filter-horizontal.{css,js} + boost-overrides neutralize legacy collapse) + theme.liquid includes

8e45fc2129bcbffc1ae0d2889ecb8f57be36c7d3 · 2026-06-22 17:11:40 -0700 · Steve Abrams

Files touched

Diff

commit 8e45fc2129bcbffc1ae0d2889ecb8f57be36c7d3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 22 17:11:40 2026 -0700

    5.1 dev: horizontal collapsible Boost filter bar (dw-filter-horizontal.{css,js} + boost-overrides neutralize legacy collapse) + theme.liquid includes
---
 .../assets/dw-filter-horizontal.css                | 188 +++++++++++++++++++++
 .../assets/dw-filter-horizontal.js                 | 169 ++++++++++++++++++
 shopify/theme-5.0-duplicate/layout/theme.liquid    |   3 +
 .../snippets/dw-boost-overrides.liquid             |  11 +-
 4 files changed, 367 insertions(+), 4 deletions(-)

diff --git a/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.css b/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.css
new file mode 100644
index 00000000..10c97b6f
--- /dev/null
+++ b/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.css
@@ -0,0 +1,188 @@
+/* ============================================================================
+   DW Horizontal Filter Bar — restyles Boost AI Search & Discovery filter groups
+   from a vertical LEFT SIDEBAR into a HORIZONTAL, collapsible bar across the
+   top of the product grid.
+
+   Scope: only takes effect when <body> carries the `dw-filters-horizontal`
+   class (added by dw-filter-horizontal.js once the Boost filter tree exists).
+   Replaces the old `dw-filters-collapsed` hide-entirely behavior.
+
+   Boost DOM (React-rendered):
+     .boost-sd__filter-tree-wrapper  > .boost-sd__filter-tree (the sidebar)
+       .boost-sd__filter             (one per group: Color, Style, Brand, …)
+         .boost-sd__filter-title     (group label / toggle)
+         .boost-sd__filter-options   (the option list / swatches)
+     .boost-sd__product-list         (the grid)
+   ============================================================================ */
+
+/* ---- 1. Make the filter tree a horizontal row pinned above the grid ------- */
+body.dw-filters-horizontal .boost-sd__filter-tree-wrapper {
+  display: block !important;
+  width: 100% !important;
+  max-width: 100% !important;
+  flex: 0 0 100% !important;
+  padding: 0 !important;
+  margin: 0 0 14px 0 !important;
+  border: none !important;
+  background: transparent !important;
+}
+
+/* The product+filter wrap is normally a side-by-side flex row; stack it so the
+   bar sits ABOVE the grid and the grid gets full width. */
+body.dw-filters-horizontal .boost-sd__filter-products-wrap,
+body.dw-filters-horizontal .boost-sd__filter-products-wrap > div {
+  display: block !important;
+  width: 100% !important;
+  max-width: 100% !important;
+}
+body.dw-filters-horizontal .boost-sd__product-list-wrapper,
+body.dw-filters-horizontal .boost-sd__product-list {
+  width: 100% !important;
+  max-width: 100% !important;
+  flex: 1 1 100% !important;
+  padding-left: 0 !important;
+  padding-right: 0 !important;
+}
+
+/* The filter tree itself: a flex row of group chips, wrap on small screens. */
+body.dw-filters-horizontal .boost-sd__filter-tree {
+  display: flex !important;
+  flex-direction: row !important;
+  flex-wrap: wrap !important;
+  align-items: stretch !important;
+  gap: 8px !important;
+  width: 100% !important;
+  max-width: 100% !important;
+  padding: 10px 12px !important;
+  margin: 0 !important;
+  background: #fff !important;
+  border: 1.5px solid #000 !important;
+  border-radius: 12px !important;
+  box-sizing: border-box !important;
+}
+
+/* ---- 2. Each filter group → a self-contained dropdown chip ----------------- */
+body.dw-filters-horizontal .boost-sd__filter {
+  position: relative !important;
+  flex: 0 0 auto !important;
+  width: auto !important;
+  margin: 0 !important;
+  padding: 0 !important;
+  border: none !important;
+  border-bottom: none !important;
+}
+
+/* Group title becomes the chip face */
+body.dw-filters-horizontal .boost-sd__filter-title {
+  display: inline-flex !important;
+  align-items: center !important;
+  gap: 6px !important;
+  margin: 0 !important;
+  padding: 7px 14px !important;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
+  font-size: 11px !important;
+  font-weight: 700 !important;
+  text-transform: uppercase !important;
+  letter-spacing: 1px !important;
+  color: #222 !important;
+  background: #f7f5f2 !important;
+  border: 1px solid #cfc8bd !important;
+  border-radius: 999px !important;
+  cursor: pointer !important;
+  white-space: nowrap !important;
+  user-select: none !important;
+}
+body.dw-filters-horizontal .boost-sd__filter-title:hover {
+  background: #efe9e0 !important;
+}
+
+/* The option list becomes an absolutely-positioned dropdown under the chip.
+   We DO NOT hide it with display:none on the Boost element (that would fight
+   Boost's own collapse state); instead we float it and toggle visibility via
+   the `dw-open` class our JS puts on the parent .boost-sd__filter. */
+body.dw-filters-horizontal .boost-sd__filter-options,
+body.dw-filters-horizontal .boost-sd__filter-collapsible,
+body.dw-filters-horizontal .boost-sd__filter-content {
+  position: absolute !important;
+  top: calc(100% + 6px) !important;
+  left: 0 !important;
+  z-index: 60 !important;
+  min-width: 220px !important;
+  max-width: 340px !important;
+  max-height: 60vh !important;
+  overflow-y: auto !important;
+  padding: 12px 14px !important;
+  background: #fff !important;
+  border: 1.5px solid #000 !important;
+  border-radius: 10px !important;
+  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.16) !important;
+  /* hidden until the group is opened */
+  display: none !important;
+}
+/* Open state — our JS toggles .dw-open on the .boost-sd__filter */
+body.dw-filters-horizontal .boost-sd__filter.dw-open > .boost-sd__filter-options,
+body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-collapsible,
+body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-content {
+  display: block !important;
+}
+
+/* Keep swatch / option layout sane inside the dropdown */
+body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-option {
+  display: flex !important;
+  align-items: center !important;
+}
+
+/* Price ($) group can render a slider — give it room */
+body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-price,
+body.dw-filters-horizontal .boost-sd__filter.dw-open [class*="price-slider"] {
+  min-width: 240px !important;
+}
+
+/* ---- 3. The collapse/expand toggle for the WHOLE bar ----------------------- */
+.dw-hbar-toggle {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 12px;
+  margin: 4px 0 4px 8px;
+  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+  font-size: 11px;
+  font-weight: 700;
+  text-transform: uppercase;
+  letter-spacing: 1.5px;
+  user-select: none;
+  background: #fff;
+  border: 1.5px solid #000;
+  border-radius: 999px;
+  cursor: pointer;
+  vertical-align: middle;
+}
+.dw-hbar-toggle:hover { background: #f5f5f5; }
+.dw-hbar-toggle svg { width: 14px; height: 14px; }
+
+/* When the bar is collapsed, hide the tree wrapper and free the grid. */
+body.dw-filters-horizontal.dw-hbar-collapsed .boost-sd__filter-tree-wrapper {
+  display: none !important;
+}
+
+/* ---- 4. Responsive: horizontal scroll instead of wrap on phones ----------- */
+@media (max-width: 768px) {
+  body.dw-filters-horizontal .boost-sd__filter-tree {
+    flex-wrap: nowrap !important;
+    overflow-x: auto !important;
+    -webkit-overflow-scrolling: touch !important;
+    padding: 8px 10px !important;
+  }
+  body.dw-filters-horizontal .boost-sd__filter-options,
+  body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-collapsible,
+  body.dw-filters-horizontal .boost-sd__filter.dw-open .boost-sd__filter-content {
+    position: fixed !important;
+    top: auto !important;
+    left: 8px !important;
+    right: 8px !important;
+    bottom: 8px !important;
+    min-width: 0 !important;
+    max-width: none !important;
+    max-height: 55vh !important;
+  }
+}
diff --git a/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.js b/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.js
new file mode 100644
index 00000000..5a22dbe8
--- /dev/null
+++ b/shopify/theme-5.0-duplicate/assets/dw-filter-horizontal.js
@@ -0,0 +1,169 @@
+/* ============================================================================
+   DW Horizontal Filter Bar — JS
+   Turns the Boost AI Search & Discovery vertical filter sidebar into a
+   horizontal, collapsible filter bar across the top of the product grid.
+
+   What it does (theme-side only, no Boost-dashboard change):
+     1. Waits for Boost's React-rendered .boost-sd__filter-tree to exist.
+     2. Adds body.dw-filters-horizontal so dw-filter-horizontal.css applies the
+        horizontal layout (each group becomes a chip with a dropdown panel).
+     3. Each group title (.boost-sd__filter-title) toggles a `.dw-open` class on
+        its .boost-sd__filter so only one dropdown shows at a time. Clicks on the
+        actual option items still bubble to Boost, so filtering still works.
+     4. Injects a single "Filters" pill into the Boost toolbar that collapses /
+        expands the whole bar; collapsed state persists in localStorage.
+
+   Replaces the old `dw-filters-collapsed` (hide-entirely) behavior on
+   collection/search pages. Only runs where a Boost filter tree is present.
+   ============================================================================ */
+(function () {
+  'use strict';
+
+  var BODY = document.body;
+  var LS_KEY = 'dw-hbar-collapsed';
+  var wired = false;
+
+  function isGridPage() {
+    return /\/(collections|search)/.test(window.location.pathname);
+  }
+
+  function qs(sel, root) { return (root || document).querySelector(sel); }
+
+  /* Toggle a single filter group open/closed (accordion: one at a time). */
+  function bindGroup(filterEl) {
+    if (filterEl.getAttribute('data-dw-hbar')) return;
+    filterEl.setAttribute('data-dw-hbar', '1');
+
+    var title = qs('.boost-sd__filter-title', filterEl);
+    if (!title) return;
+
+    title.addEventListener('click', function (e) {
+      // Only handle clicks on the title chip itself, not on option rows that
+      // Boost may have nested — let those reach Boost.
+      if (e.target.closest('.boost-sd__filter-option') ||
+          e.target.closest('.boost-sd__filter-options')) {
+        return;
+      }
+      var alreadyOpen = filterEl.classList.contains('dw-open');
+      // Close all siblings first
+      var tree = filterEl.closest('.boost-sd__filter-tree');
+      if (tree) {
+        tree.querySelectorAll('.boost-sd__filter.dw-open').forEach(function (f) {
+          if (f !== filterEl) f.classList.remove('dw-open');
+        });
+      }
+      filterEl.classList.toggle('dw-open', !alreadyOpen);
+    });
+  }
+
+  function bindAllGroups() {
+    var groups = document.querySelectorAll('.boost-sd__filter-tree .boost-sd__filter');
+    for (var i = 0; i < groups.length; i++) bindGroup(groups[i]);
+  }
+
+  /* Close any open dropdown when clicking outside the bar. */
+  function bindOutsideClose() {
+    document.addEventListener('click', function (e) {
+      if (e.target.closest('.boost-sd__filter-tree')) return;
+      document.querySelectorAll('.boost-sd__filter.dw-open').forEach(function (f) {
+        f.classList.remove('dw-open');
+      });
+    });
+  }
+
+  /* Build the collapse/expand toggle pill and drop it in the Boost toolbar. */
+  function buildToggle() {
+    if (qs('#dw-hbar-toggle')) return;
+    var toolbar = qs('.boost-sd__toolbar-item--product-count, .boost-sd__product-count');
+    if (!toolbar || !toolbar.parentElement) return;
+
+    var btn = document.createElement('button');
+    btn.id = 'dw-hbar-toggle';
+    btn.className = 'dw-hbar-toggle';
+    btn.type = 'button';
+    btn.setAttribute('aria-label', 'Toggle filter bar');
+
+    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+    svg.setAttribute('viewBox', '0 0 24 24');
+    svg.setAttribute('fill', 'none');
+    svg.setAttribute('stroke', 'currentColor');
+    svg.setAttribute('stroke-width', '2');
+    var ys = ['6', '12', '18'];
+    for (var i = 0; i < ys.length; i++) {
+      var l = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+      l.setAttribute('x1', '4'); l.setAttribute('y1', ys[i]);
+      l.setAttribute('x2', '20'); l.setAttribute('y2', ys[i]);
+      svg.appendChild(l);
+    }
+    btn.appendChild(svg);
+
+    var label = document.createElement('span');
+    label.id = 'dw-hbar-label';
+    btn.appendChild(label);
+
+    function syncLabel() {
+      var collapsed = BODY.classList.contains('dw-hbar-collapsed');
+      label.textContent = collapsed ? 'Filters' : 'Hide Filters';
+    }
+
+    btn.addEventListener('click', function () {
+      var collapsed = BODY.classList.toggle('dw-hbar-collapsed');
+      try { localStorage.setItem(LS_KEY, collapsed ? '1' : '0'); } catch (e) {}
+      syncLabel();
+    });
+
+    toolbar.parentElement.insertBefore(btn, toolbar);
+    syncLabel();
+  }
+
+  /* Apply the persisted collapsed state. Default: bar EXPANDED (visible). */
+  function applyPersistedState() {
+    var collapsed = false;
+    try { collapsed = localStorage.getItem(LS_KEY) === '1'; } catch (e) {}
+    BODY.classList.toggle('dw-hbar-collapsed', collapsed);
+  }
+
+  function wire() {
+    if (wired) return true;
+    var tree = qs('.boost-sd__filter-tree');
+    if (!tree) return false;
+    wired = true;
+
+    // Remove the legacy hide-entirely behavior so the two don't fight.
+    BODY.classList.remove('dw-filters-collapsed');
+    BODY.classList.add('dw-filters-horizontal');
+
+    applyPersistedState();
+    bindAllGroups();
+    bindOutsideClose();
+    buildToggle();
+
+    // Boost re-renders the tree on filter apply / page change — re-bind groups
+    // and keep our toggle present.
+    var obs = new MutationObserver(function () {
+      clearTimeout(obs._t);
+      obs._t = setTimeout(function () {
+        bindAllGroups();
+        buildToggle();
+      }, 150);
+    });
+    var wrap = qs('.boost-sd__filter-tree-wrapper') || tree.parentElement || document.body;
+    obs.observe(wrap, { childList: true, subtree: true });
+
+    return true;
+  }
+
+  function start() {
+    if (!isGridPage()) return;
+    var tries = 0;
+    var poll = setInterval(function () {
+      if (wire() || tries++ > 80) clearInterval(poll);
+    }, 250);
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', start);
+  } else {
+    start();
+  }
+})();
diff --git a/shopify/theme-5.0-duplicate/layout/theme.liquid b/shopify/theme-5.0-duplicate/layout/theme.liquid
index 7a0347ad..272543e4 100644
--- a/shopify/theme-5.0-duplicate/layout/theme.liquid
+++ b/shopify/theme-5.0-duplicate/layout/theme.liquid
@@ -1023,5 +1023,8 @@ alt="web statistics"></a></div></noscript>
 {%- comment -%} DW PDP restyle: size <select> -> pills + uniform CTA pill layout (2026-06-22) {%- endcomment -%}
 {{ 'dw-pdp-restyle.css' | asset_url | stylesheet_tag }}
 <script src="{{ 'dw-pdp-size-pills.js' | asset_url }}" defer></script>
+{%- comment -%} DW horizontal collapsible filter bar over Boost (2026-06-22) {%- endcomment -%}
+{{ 'dw-filter-horizontal.css' | asset_url | stylesheet_tag }}
+<script src="{{ 'dw-filter-horizontal.js' | asset_url }}" defer></script>
 </body>
 </html>
diff --git a/shopify/theme-5.0-duplicate/snippets/dw-boost-overrides.liquid b/shopify/theme-5.0-duplicate/snippets/dw-boost-overrides.liquid
index 322bbf9c..e8d5e98e 100644
--- a/shopify/theme-5.0-duplicate/snippets/dw-boost-overrides.liquid
+++ b/shopify/theme-5.0-duplicate/snippets/dw-boost-overrides.liquid
@@ -31,8 +31,10 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
 .boost-sd__product-item img{max-width:100% !important;height:auto !important;width:100% !important}
 .boost-sd__product-list{overflow:hidden !important;display:grid !important}
 
-/* Filter toggle button */
-.dw-filter-toggle{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;margin:4px 0 4px 8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;user-select:none;background:#fff;border:1.5px solid #000;border-radius:999px;cursor:pointer;vertical-align:middle}
+/* Legacy filter toggle button — superseded by the horizontal-bar toggle
+   (#dw-hbar-toggle in dw-filter-horizontal.js). Hidden to avoid two pills. */
+.dw-filter-toggle{display:none !important}
+.dw-filter-toggle--legacy{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;margin:4px 0 4px 8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;user-select:none;background:#fff;border:1.5px solid #000;border-radius:999px;cursor:pointer;vertical-align:middle}
 .dw-filter-toggle:hover{background:#f5f5f5}
 .dw-filter-toggle svg{width:14px;height:14px}
 
@@ -82,8 +84,9 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
 
 <script>
 (function(){
-  /* Collapse filters immediately */
-  document.body.classList.add('dw-filters-collapsed');
+  /* Horizontal filter bar (dw-filter-horizontal.js) now owns filter visibility.
+     Do NOT add the legacy 'dw-filters-collapsed' hide-entirely class — it would
+     hide the sidebar that the horizontal bar restyles in place. */
   /* Set CSS var for default 4 cols */
   document.documentElement.style.setProperty('--dw-grid-cols', 4);
 

← 6b98ef31 DW PDP restyle: size-select -> pills + uniform CTA pill layo  ·  back to Designer Wallcoverings  ·  Port MOQ enforcement to Steves Version 5.1 (Archetype): meta d8930599 →