[object Object]

← back to Commercialrealestate

sort options as a chip bar in the LEFT PANEL (Steve): shared sort-bar.js mirrors each page's sort <select> into a .rail 'Sort' chip section (inline fallback where no rail), proxying clicks to reuse existing sort logic; on 7 grids; reverted condos to a plain select for uniformity

3bd92017f9b1b60a74216a05358e5d392798d1b6 · 2026-08-03 13:05:06 -0700 · Steve Abrams

Files touched

Diff

commit 3bd92017f9b1b60a74216a05358e5d392798d1b6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 3 13:05:06 2026 -0700

    sort options as a chip bar in the LEFT PANEL (Steve): shared sort-bar.js mirrors each page's sort <select> into a .rail 'Sort' chip section (inline fallback where no rail), proxying clicks to reuse existing sort logic; on 7 grids; reverted condos to a plain select for uniformity
---
 public/broker-grid.html |  1 +
 public/condos.html      | 34 ++++++++---------------
 public/just-listed.html |  1 +
 public/licensed.html    |  1 +
 public/mls.html         |  1 +
 public/rent-rolls.html  |  1 +
 public/sales.html       |  1 +
 public/sort-bar.js      | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 92 insertions(+), 22 deletions(-)

diff --git a/public/broker-grid.html b/public/broker-grid.html
index 4726faf..60d34e1 100644
--- a/public/broker-grid.html
+++ b/public/broker-grid.html
@@ -449,5 +449,6 @@ fetch('/api/brokers/all').then(r=>r.json()).then(d=>{DATA=d.brokers||[];autoCols
 </script>
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body>
 </html>
diff --git a/public/condos.html b/public/condos.html
index df6e954..ff85e28 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -35,12 +35,6 @@
   .topbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:14px 24px;border-bottom:1px solid var(--line);position:sticky;top:53px;background:rgba(14,17,22,.95);backdrop-filter:blur(6px);z-index:20;}
   .topbar input[type=text]{min-width:260px;}
   .topbar .dens{font-size:12px;color:var(--mut);display:flex;align-items:center;gap:7px;}
-  /* all sort options as a visible bar of chips (Steve 2026-08-03) */
-  .sortbar{display:inline-flex;align-items:center;gap:4px;flex-wrap:wrap;}
-  .sortbar .sortlabel{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--mut);margin-right:2px;}
-  .sortchip{background:var(--card,#141925);color:var(--mut,#8b949e);border:1px solid var(--line,#232a38);border-radius:7px;padding:4px 9px;font-size:12px;cursor:pointer;}
-  .sortchip:hover{color:var(--ink);border-color:var(--mut);}
-  .sortchip.on{border-color:var(--blue,#58a6ff);color:var(--blue,#58a6ff);background:rgba(88,166,255,.12);font-weight:600;}
   .viewseg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;}
   .viewseg button{background:var(--card);color:var(--mut);border:0;border-right:1px solid var(--line);padding:7px 12px;font-size:12px;cursor:pointer;}
   .viewseg button:last-child{border-right:0;} .viewseg button.active{background:#1f6feb;color:#fff;}
@@ -141,17 +135,16 @@
     <input type="text" id="q" placeholder="search all fields — project, city, zip, address, broker, firm, status… (space = AND)">
     <span class="viewseg" id="viewseg"><button data-view="grid" class="active">▦ Grid</button><button data-view="list">☰ List</button></span>
     <button id="shortbtn" title="Show only your shortlisted (starred) listings — shared with the Explorer & MLS">⭐ Shortlist (0)</button>
-    <div class="sortbar" id="csort" title="Sort condos" role="group" aria-label="Sort">
-      <span class="sortlabel">Sort</span>
-      <button class="sortchip" data-sort="">Newest</button>
-      <button class="sortchip" data-sort="price_desc">Price ↓</button>
-      <button class="sortchip" data-sort="price_asc">Price ↑</button>
-      <button class="sortchip" data-sort="pps_desc">$/SF ↓</button>
-      <button class="sortchip" data-sort="year_desc">Year ↓</button>
-      <button class="sortchip" data-sort="hoa_asc">HOA ↑</button>
-      <button class="sortchip" data-sort="proj_az">Project A→Z</button>
-      <button class="sortchip" data-sort="city_az">City A→Z</button>
-    </div>
+    <select id="csort" title="Sort condos" style="background:var(--card,#141925);color:var(--ink,#E6E8EC);border:1px solid var(--line,#232a38);border-radius:7px;padding:5px 8px;font-size:12px">
+      <option value="">Newest</option>
+      <option value="price_desc">Price ↓</option>
+      <option value="price_asc">Price ↑</option>
+      <option value="pps_desc">$/SF ↓</option>
+      <option value="year_desc">Year ↓</option>
+      <option value="hoa_asc">HOA ↑</option>
+      <option value="proj_az">Project A→Z</option>
+      <option value="city_az">City A→Z</option>
+    </select>
     <label class="dens">Density <input type="range" id="cols" min="1" max="20" step="1" value="3"></label>
     <span class="count" id="count">…</span>
   </div>
@@ -488,11 +481,7 @@ function sortRows(rows){
   }[SORTKEY];
   return cmp?rows.slice().sort(cmp):rows;
 }
-(function(){ const bar=$('#csort'); if(!bar) return;
-  const mark=()=>bar.querySelectorAll('.sortchip').forEach(c=>c.classList.toggle('on',(c.dataset.sort||'')===(SORTKEY||'')));
-  bar.addEventListener('click',e=>{ const c=e.target.closest('.sortchip'); if(!c) return; SORTKEY=c.dataset.sort||''; localStorage.setItem('condoSort',SORTKEY); mark(); apply(); });
-  mark();
-})();
+(function(){ const s=$('#csort'); if(s){ s.value=SORTKEY; s.addEventListener('change',()=>{ SORTKEY=s.value; localStorage.setItem('condoSort',SORTKEY); apply(); }); } })();
 $('#cols').oninput=e=>{ document.documentElement.style.setProperty('--cols',e.target.value); localStorage.setItem('condoCols',e.target.value); };
 const savedCols=localStorage.getItem('condoCols'); if(savedCols){ $('#cols').value=savedCols; document.documentElement.style.setProperty('--cols',savedCols); }
 $$('#viewseg button').forEach(b=>b.classList.toggle('active',b.dataset.view===VIEW));
@@ -592,5 +581,6 @@ boot();
 </script>
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body>
 </html>
diff --git a/public/just-listed.html b/public/just-listed.html
index 32a5112..7dbe501 100644
--- a/public/just-listed.html
+++ b/public/just-listed.html
@@ -180,4 +180,5 @@ fetch('/data/listings.json').then(r=>r.json()).then(d=>{
   buildRail();render();
 }).catch(()=>{$('#count').textContent='failed to load /data/listings.json';});
 </script>  <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body></html>
diff --git a/public/licensed.html b/public/licensed.html
index 78d2455..42c6284 100644
--- a/public/licensed.html
+++ b/public/licensed.html
@@ -208,4 +208,5 @@ document.addEventListener('click',async e=>{
 });
 boot().catch(e=>{$('#count').textContent='Failed to load: '+e.message;});
 </script>  <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body></html>
diff --git a/public/mls.html b/public/mls.html
index 41327b1..b78493e 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -575,5 +575,6 @@ fetch('/data/ranked.json').then(r=>r.json()).then(async d=>{ DATA=(d.ranked||d)|
   <script src="/column-manager.js" defer></script>
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body>
 </html>
diff --git a/public/rent-rolls.html b/public/rent-rolls.html
index d857223..454d2d6 100644
--- a/public/rent-rolls.html
+++ b/public/rent-rolls.html
@@ -433,5 +433,6 @@ fetch('/api/rent-rolls').then(r=>r.json()).then(d=>{
 </script>
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body>
 </html>
diff --git a/public/sales.html b/public/sales.html
index 5f94b0a..a899e20 100644
--- a/public/sales.html
+++ b/public/sales.html
@@ -384,5 +384,6 @@ fetch('/api/closed-sales?limit=5000').then(r=>r.json()).then(d=>{
   <script src="/column-manager.js" defer></script>
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
+  <script src="/sort-bar.js" defer></script>
 </body>
 </html>
diff --git a/public/sort-bar.js b/public/sort-bar.js
new file mode 100644
index 0000000..fc2e86a
--- /dev/null
+++ b/public/sort-bar.js
@@ -0,0 +1,74 @@
+/* sort-bar.js — surface ALL sort options as a chip bar in the LEFT PANEL (Steve 2026-08-03:
+ * "provide all sort options in a bar … put on left panel"). Mirrors the page's existing sort
+ * <select> (#csort / #sortsel / #sort) into a .rail section of chips; clicking a chip drives the
+ * same select (so every page's existing sort logic is reused, including dynamic column lists).
+ * Falls back to an inline bar where a page has no left rail. Self-contained. */
+(function () {
+  'use strict';
+  if (window.__sortBarInit) return; window.__sortBarInit = true;
+
+  var css = document.createElement('style');
+  css.textContent = [
+    '.sb-sec .sb-h{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--mut,#8A93A2);margin:0 0 9px;font-weight:600;}',
+    '.sb-chips{display:flex;flex-wrap:wrap;gap:5px;}',
+    '.sbchip{background:var(--card,#141925);color:var(--mut,#8b949e);border:1px solid var(--line,#232a38);border-radius:7px;padding:4px 9px;font-size:12px;cursor:pointer;line-height:1.2;}',
+    '.sbchip:hover{color:var(--ink,#e6edf3);border-color:var(--mut,#8A93A2);}',
+    '.sbchip.on{border-color:var(--blue,#58a6ff);color:var(--blue,#58a6ff);background:rgba(88,166,255,.14);font-weight:600;}',
+    'span.sb-sec{display:inline-flex;align-items:center;gap:5px;flex-wrap:wrap}'
+  ].join('');
+  document.head.appendChild(css);
+
+  function sel() { return document.querySelector('#csort, #sortsel, #sort'); }
+
+  function ensureChips(s) {
+    var host = document.getElementById('sb-host');
+    if (host) return host.querySelector('.sb-chips');
+    var rail = document.querySelector('.rail');
+    var chips = document.createElement('div'); chips.className = 'sb-chips';
+    if (rail) {
+      var sec = document.createElement('div'); sec.className = 'rsec sb-sec'; sec.id = 'sb-host';
+      sec.innerHTML = '<div class="sb-h">Sort</div>'; sec.appendChild(chips);
+      rail.insertBefore(sec, rail.firstChild);       // top of the left panel
+    } else {
+      var span = document.createElement('span'); span.className = 'sb-sec'; span.id = 'sb-host';
+      var lbl = document.createElement('span'); lbl.className = 'sb-h'; lbl.textContent = 'Sort'; lbl.style.margin = '0 4px 0 0';
+      span.appendChild(lbl); span.appendChild(chips);
+      s.parentNode.insertBefore(span, s);            // inline bar where there's no rail
+    }
+    s.style.display = 'none';                          // the bar replaces the dropdown
+    return chips;
+  }
+
+  function build() {
+    var s = sel(); if (!s || s.tagName !== 'SELECT' || !s.options.length) return;
+    var chips = ensureChips(s);
+    chips.innerHTML = Array.prototype.map.call(s.options, function (o, i) {
+      var label = (o.textContent || '').replace(/^Sort:\s*/i, '').trim() || ('Option ' + (i + 1));
+      return '<button class="sbchip' + (i === s.selectedIndex ? ' on' : '') + '" data-i="' + i + '">' + label.replace(/</g, '&lt;') + '</button>';
+    }).join('');
+  }
+
+  document.addEventListener('click', function (e) {
+    var c = e.target.closest && e.target.closest('#sb-host .sbchip'); if (!c) return;
+    var s = sel(); if (!s) return;
+    s.selectedIndex = +c.dataset.i;
+    s.dispatchEvent(new Event('change', { bubbles: true }));
+    c.parentNode.querySelectorAll('.sbchip').forEach(function (x) { x.classList.toggle('on', x === c); });
+  });
+
+  function syncActive() {
+    var s = sel(), chips = document.querySelector('#sb-host .sb-chips'); if (!s || !chips) return;
+    Array.prototype.forEach.call(chips.children, function (x) { x.classList.toggle('on', +x.dataset.i === s.selectedIndex); });
+  }
+
+  function boot() {
+    build();
+    var s = sel();
+    if (s) {
+      new MutationObserver(build).observe(s, { childList: true }); // dynamic/column-driven option lists
+      s.addEventListener('change', syncActive);                    // keep active chip in sync (e.g. header-click sort)
+    }
+  }
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function () { setTimeout(boot, 400); });
+  else setTimeout(boot, 400);
+})();

← 044dfa4 condos: sort control is now a visible BAR of all 8 sort-opti  ·  back to Commercialrealestate  ·  afternoon CRE update 2026-08-03 75aff2f →