diff --git a/assets/css/theme-semantic.css b/assets/css/theme-semantic.css index 54aac28..80a1fda 100644 --- a/assets/css/theme-semantic.css +++ b/assets/css/theme-semantic.css @@ -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; + } } } diff --git a/assets/js/app.js b/assets/js/app.js index 5c8f4ab..0c21b48 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -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 diff --git a/lib/simpleshop_theme_web/live/shop/collection.ex b/lib/simpleshop_theme_web/live/shop/collection.ex index 0f55a46..0d6a5cc 100644 --- a/lib/simpleshop_theme_web/live/shop/collection.ex +++ b/lib/simpleshop_theme_web/live/shop/collection.ex @@ -137,57 +137,44 @@ defmodule SimpleshopThemeWeb.Shop.Collection do