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:
parent
bb358f890b
commit
3158a94f0b
@ -206,19 +206,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Horizontal scroll fade hint for collection category pills (mobile only) */
|
/* Collection filter pills — flex-wrap base, JS-enhanced scroll on mobile */
|
||||||
.collection-filter-scroll {
|
.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);
|
mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
|
||||||
-webkit-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 {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .collection-filter-pills {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@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;
|
mask-image: none;
|
||||||
-webkit-mask-image: none;
|
-webkit-mask-image: none;
|
||||||
|
|
||||||
|
& .collection-filter-pills {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||||
const liveSocket = new LiveSocket("/live", Socket, {
|
const liveSocket = new LiveSocket("/live", Socket, {
|
||||||
params: {_csrf_token: csrfToken},
|
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
|
// Show progress bar on live navigation and form submits
|
||||||
|
|||||||
@ -137,57 +137,44 @@ defmodule SimpleshopThemeWeb.Shop.Collection do
|
|||||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-6">
|
<div class="flex flex-wrap items-center justify-between gap-3 mb-6">
|
||||||
<nav
|
<nav
|
||||||
aria-label="Collection filters"
|
aria-label="Collection filters"
|
||||||
class="collection-filter-scroll overflow-x-auto py-1 -mx-4 px-4 sm:overflow-visible sm:mx-0 sm:px-0 sm:py-0"
|
id="collection-filters"
|
||||||
|
phx-hook="CollectionFilters"
|
||||||
|
class="collection-filters"
|
||||||
>
|
>
|
||||||
<ul class="flex gap-1.5 sm:flex-wrap sm:gap-2">
|
<ul class="collection-filter-pills">
|
||||||
<li class="shrink-0">
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={collection_path("all", @current_sort)}
|
navigate={collection_path("all", @current_sort)}
|
||||||
|
aria-current={@current_slug == nil && "page"}
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 sm:px-4 sm:py-2 rounded-full text-xs sm:text-sm whitespace-nowrap transition-colors",
|
"collection-filter-pill",
|
||||||
if(@current_slug == nil, do: "font-medium", else: "hover:opacity-80")
|
if(@current_slug == nil, do: "active", else: "hover:opacity-80")
|
||||||
]}
|
]}
|
||||||
style={
|
|
||||||
if(@current_slug == nil,
|
|
||||||
do: "background-color: var(--t-accent); color: var(--t-text-on-accent);",
|
|
||||||
else: "background-color: var(--t-surface-raised); color: var(--t-text-primary);"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li class="shrink-0">
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={collection_path("sale", @current_sort)}
|
navigate={collection_path("sale", @current_sort)}
|
||||||
|
aria-current={@current_slug == "sale" && "page"}
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 sm:px-4 sm:py-2 rounded-full text-xs sm:text-sm whitespace-nowrap transition-colors",
|
"collection-filter-pill",
|
||||||
if(@current_slug == "sale", do: "font-medium", else: "hover:opacity-80")
|
if(@current_slug == "sale", do: "active", else: "hover:opacity-80")
|
||||||
]}
|
]}
|
||||||
style={
|
|
||||||
if(@current_slug == "sale",
|
|
||||||
do: "background-color: var(--t-accent); color: var(--t-text-on-accent);",
|
|
||||||
else: "background-color: var(--t-surface-raised); color: var(--t-text-primary);"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Sale
|
Sale
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<%= for category <- @categories do %>
|
<%= for category <- @categories do %>
|
||||||
<li class="shrink-0">
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={collection_path(category.slug, @current_sort)}
|
navigate={collection_path(category.slug, @current_sort)}
|
||||||
|
aria-current={@current_slug == category.slug && "page"}
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 sm:px-4 sm:py-2 rounded-full text-xs sm:text-sm whitespace-nowrap transition-colors",
|
"collection-filter-pill",
|
||||||
if(@current_slug == category.slug, do: "font-medium", else: "hover:opacity-80")
|
if(@current_slug == category.slug, do: "active", else: "hover:opacity-80")
|
||||||
]}
|
]}
|
||||||
style={
|
|
||||||
if(@current_slug == category.slug,
|
|
||||||
do: "background-color: var(--t-accent); color: var(--t-text-on-accent);",
|
|
||||||
else: "background-color: var(--t-surface-raised); color: var(--t-text-primary);"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{category.name}
|
{category.name}
|
||||||
</.link>
|
</.link>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user