progressive enhancement for collection filter pills

Flex-wrap base (no JS needed, active pill always visible). JS hook
switches to horizontal scroll with scroll-into-view when pills exceed
2.5 rows on mobile. Desktop always wraps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-16 17:47:51 +00:00
parent bb358f890b
commit 3158a94f0b
3 changed files with 97 additions and 34 deletions

View File

@@ -206,19 +206,66 @@
}
}
/* Horizontal scroll fade hint for collection category pills (mobile only) */
.collection-filter-scroll {
/* Collection filter pills — flex-wrap base, JS-enhanced scroll on mobile */
.collection-filter-pills {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
}
.collection-filter-pill {
display: inline-block;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
white-space: nowrap;
transition: opacity 0.15s;
background-color: var(--t-surface-raised);
color: var(--t-text-primary);
&.active {
background-color: var(--t-accent);
color: var(--t-text-on-accent);
font-weight: 500;
}
}
/* When JS detects overflow, it adds this class to switch to horizontal scroll */
.collection-filters.is-scrollable {
overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
-webkit-mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
@media (min-width: 640px) {
& .collection-filter-pills {
flex-wrap: nowrap;
}
}
@media (min-width: 640px) {
.collection-filter-pills {
gap: 0.5rem;
}
.collection-filter-pill {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
/* Desktop always wraps — no scroll needed */
.collection-filters.is-scrollable {
overflow-x: visible;
mask-image: none;
-webkit-mask-image: none;
& .collection-filter-pills {
flex-wrap: wrap;
}
}
}

View File

@@ -458,10 +458,39 @@ const SearchModal = {
}
}
// Flex-wrap base → horizontal scroll enhancement for collection category pills.
// If the pills wrap past 2 rows on mobile, switches to single-row scroll
// and scrolls the active pill into view.
const CollectionFilters = {
mounted() { this._enhance() },
updated() { this._enhance() },
_enhance() {
const nav = this.el
const ul = nav.querySelector("ul")
if (!ul) return
// Reset to measure natural wrap height
nav.classList.remove("is-scrollable")
const firstItem = ul.querySelector("li")
if (!firstItem) return
const rowHeight = firstItem.offsetHeight
const wrapsToMany = ul.scrollHeight > rowHeight * 2.5
if (wrapsToMany && window.innerWidth < 640) {
nav.classList.add("is-scrollable")
const active = nav.querySelector("[aria-current]")
if (active) {
active.scrollIntoView({ inline: "center", block: "nearest", behavior: "instant" })
}
}
}
}
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal, CollectionFilters},
})
// Show progress bar on live navigation and form submits