From fbebb0add34a3baa017c664cff8833c05f541404 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Thu, 11 Jun 2026 01:07:56 +0530 Subject: [PATCH] feat: mobile mini-cart dropdown tap + per-unit max for responsive range --- .../ResponsiveRangeComponent.js | 20 +++++++- assets/js/src/frontend/navigation.js | 50 +++++++++++++++++++ .../compat/woocommerce/_nav-cart.scss | 12 +++++ package.json | 2 +- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/assets/apps/customizer-controls/src/responsive-range/ResponsiveRangeComponent.js b/assets/apps/customizer-controls/src/responsive-range/ResponsiveRangeComponent.js index e7af2046f7..22c1872ae3 100644 --- a/assets/apps/customizer-controls/src/responsive-range/ResponsiveRangeComponent.js +++ b/assets/apps/customizer-controls/src/responsive-range/ResponsiveRangeComponent.js @@ -36,7 +36,7 @@ const ResponsiveRangeComponent = ({ control }) => { }, []); const { label } = control.params; - const { hideResponsive, units, defaultVal, min, max } = + const { hideResponsive, units, defaultVal, min, max, unitsMax } = control.params.input_attrs; const suffixValue = () => { @@ -58,6 +58,16 @@ const ResponsiveRangeComponent = ({ control }) => { const isRelativeUnit = () => ['em', 'rem'].includes(suffixValue()); + // Allow a different max per unit (e.g. 1200 for px but 100 for %). + const maxForUnit = (unit) => { + if (unitsMax && unit && unitsMax[unit] !== undefined) { + return unitsMax[unit]; + } + return max || 100; + }; + + const currentMax = () => maxForUnit(suffixValue()); + const unitButtons = () => { if (!units) { return null; @@ -90,6 +100,12 @@ const ResponsiveRangeComponent = ({ control }) => { nextValue[currentDevice] ); } + // Clamp the value to the new unit's max (e.g. when + // switching from px to %, 600 becomes 100). + const unitMax = maxForUnit(unit); + if (nextValue[currentDevice] > unitMax) { + nextValue[currentDevice] = unitMax; + } setValue(nextValue); control.setting.set(JSON.stringify(nextValue)); }} @@ -134,7 +150,7 @@ const ResponsiveRangeComponent = ({ control }) => { { diff --git a/assets/js/src/frontend/navigation.js b/assets/js/src/frontend/navigation.js index cb0c634ad4..c9e0b82853 100644 --- a/assets/js/src/frontend/navigation.js +++ b/assets/js/src/frontend/navigation.js @@ -19,6 +19,7 @@ export const initNavigation = () => { handleMobileDropdowns(); handleSearch(); handleMiniCartPosition(); + handleMiniCartMobileToggle(); window.HFG.initSearch = function () { handleSearch(); handleMobileDropdowns(); @@ -238,6 +239,55 @@ function handleMiniCartPosition() { window.addEventListener('resize', handleMiniCartPosition); +/** + * Toggle the dropdown mini cart on tap for mobile. + * + * On desktop the dropdown mini cart opens on hover. Touch devices have no + * hover, so without this the cart icon would just follow its link to the cart + * page. Below the laptop breakpoint we toggle a `cart-dropdown-open` class on + * tap instead; the dropdown's appearance is reused from the desktop styles + * (see the woocommerce nav-cart styles), so customers can preview the cart and + * keep shopping. It closes on a second tap or when tapping outside of it. + */ +function handleMiniCartMobileToggle() { + const carts = document.querySelectorAll('.responsive-nav-cart.dropdown'); + if (carts.length === 0) { + return; + } + + // Mirrors the $laptop (960px) breakpoint where the hover dropdown applies. + const isMobile = () => window.matchMedia('(max-width: 959px)').matches; + + carts.forEach((cart) => { + const openButton = cart.querySelector('.cart-icon-wrapper'); + if (openButton === null) { + return; + } + openButton.addEventListener('click', function (e) { + if (!isMobile() || cart.classList.contains('cart-is-empty')) { + return; + } + e.preventDefault(); + cart.classList.toggle('cart-dropdown-open'); + }); + }); + + // Close an open dropdown when tapping outside of it. + document.addEventListener('click', function (e) { + if (!isMobile()) { + return; + } + carts.forEach((cart) => { + if ( + cart.classList.contains('cart-dropdown-open') && + !cart.contains(e.target) + ) { + cart.classList.remove('cart-dropdown-open'); + } + }); + }); +} + /** * Create an overlay to allow closing. * diff --git a/assets/scss/components/compat/woocommerce/_nav-cart.scss b/assets/scss/components/compat/woocommerce/_nav-cart.scss index a0aa46e3d9..c684851496 100644 --- a/assets/scss/components/compat/woocommerce/_nav-cart.scss +++ b/assets/scss/components/compat/woocommerce/_nav-cart.scss @@ -165,6 +165,18 @@ $cart-width: 360px; } } +// Mobile: tapping the cart icon toggles `cart-dropdown-open` (see +// navigation.js). The dropdown appearance is already defined above, so we only +// need to reveal it here — reusing the same element and styles as the desktop. +@media (max-width: 959px) { + + .responsive-nav-cart.dropdown.cart-dropdown-open .nv-nav-cart { + display: block; + opacity: 1; + visibility: visible; + } +} + @mixin nav-cart--laptop() { .nv-nav-cart { diff --git a/package.json b/package.json index 2eb20167d5..9abc59de61 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ { "gzip": false, "running": false, - "limit": "7 KB", + "limit": "8 KB", "path": "assets/js/build/modern/frontend.js" }, {