Navegación de categorías mobile

Dependiendo de tu diseño puede que necesites tener las categorías más visibles en mobile. En este tutorial vamos a ver cómo agregar una navegación solo para categorías quedando de la siguiente forma:

HTML

Lo primero que vamos a hacer es crear las carpeta navigation dentro de la carpeta snipplets

1. Dentro de navigation, agregamos los dos siguientes snipplets con sus respectivos códigos:

navigation-categories.tpl

En este archivo listamos todas las categorías y subcategorías de la tienda

{# All store categories and subcategories with links #}

{% for category in categories %}
    {% if category.subcategories %}
        <a href="#" class="js-modal-open modal-list-item" data-toggle="#category-{{ category.id }}">
            {{ category.name }}
              {% include "snipplets/svg/chevron-right.tpl" with {svg_custom_class: "icon-inline icon-lg modal-list-icon"} %}
        </a>
    {% else %}
        <a href="{{ category.url }}" class="modal-list-item">
            {{ category.name }}
        </a>
    {% endif %}
{% endfor %}

navigation-categories-panel.tpl

Acá llamamos al componente del modal o popup a través de un embed. Este snipplet se llama a sí mismo iterando por cada subcategoría.

{# Modals for each subcategory #}


{% embed "snipplets/modal.tpl" with{modal_id: 'category-' ~ category.id, modal_class: 'categories', modal_position: 'right', modal_transition: 'slide', modal_width: 'full', modal_head: true, modal_overlay: false, modal_close_left: true, modal_helper: 'pb-5'} %}
    {% block modal_head %}
        {{ category.name }}
    {% endblock %}
    {% block modal_body %}
        <a href="{{ category.url }}" class="modal-list-item font-weight-bold">
            {{ "Ver toda esta categoría" | translate }}
        </a>
        {% snipplet "navigation/navigation-categories.tpl" with categories = category.subcategories %}
    {% endblock %}
{% endembed %}


{% for subcategory in category.subcategories %}
    {% snipplet "navigation/navigation-categories-panel.tpl" with category = subcategory %}
{% endfor %}

2. Luego tenemos que llamar al link que abre el popup que engloba a todas las cateogrías. En el theme Base lo ubicamos dentro del snipplet header.tpl dentro de la carpeta snipplets/header, pero en tu caso puede que sea otro archivo. Lo importante es que este dentro de la navegación principal del sitio

   {# Main tab link #}


    <a href="#" id="toggle-mobile-categories" class="js-modal-open btn btn-block nav-tab" data-toggle="#main-categories">
        {{ 'Categorías' | translate }}
        {% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "js-toggle-icon icon-inline icon-lg"} %}
        {% include "snipplets/svg/chevron-up.tpl" with {svg_custom_class: "js-toggle-icon icon-inline icon-lg hidden"} %}
    </a>

3. Luego agregamos la clase js-head-main al div que contiene todo el head del sitio.

4. Dentro del mismo tpl que editamos en el paso 2 pero por fuera de la navegación del sitio vamos a llamar a los snipplets creados en el paso 1, de la siguiente manera:

{# Main categories modal #}

<div id="nav-mobile-categories" style="display: none;">
    {% embed "snipplets/modal.tpl" with{modal_id: 'main-categories', modal_class: 'categories', modal_position: 'top', modal_transition: 'slide', modal_width: 'full', modal_overlay: false} %}
        {% block modal_head %}
            
        {% endblock %}
        {% block modal_body %}
            <a href="{{ store.products_url }}" class="modal-list-item font-weight-bold">
                {{ 'Ver todos los productos' | translate }}
            </a>
            {% snipplet "navigation/navigation-categories.tpl" %}
        {% endblock %}
    {% endembed %}

    {# Subcategories modals #}

    {% for category in categories %}
        {% snipplet "navigation/navigation-categories-panel.tpl" %}
    {% endfor %}
</div>

Podemos notar que nuevamente usamos un embed para el modal de las categorías principales, y luego agregamos al resto de los modals para las demás subcategorías, usando un for.

4.  Ahora necesitamos crear el snipplet para el componente modal o popup dentro de la carpeta snipplets. Este tpl se llama modal.tpl y el código es:

{# /*============================================================================
  #Modal
==============================================================================*/

#Properties
    // ID
    // Position - Top, Right, Bottom, Left
    // Transition - Slide and Fade
    // Width - Full and Box
    // modal_form_action - For modals that has a form
    // modal_overlay - used for modals with an overlay behind
    // modal_head - to show a header on the modal
    // modal_close_left - If true shows arrow left on modal header, else shows an X to close


#Head
    // Block - modal_head
#Body
    // Block - modal_body
#Footer
    // Block - modal_footer


#}

<div id="{{ modal_id }}" class="js-modal modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft {% if not modal_overlay %}js-no-overlay modal-no-overlay{% endif %}" style="display: none;">
    {% if modal_form_action %}
    <form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}">
    {% endif %}
    {% if modal_head %}
        <div class="js-modal-close modal-header">
            <span class="modal-close">
                {% if modal_close_left %}
                    {% include "snipplets/svg/chevron-left.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
                {% else %}
                    {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
                {% endif %}
            </span>
            {% block modal_head %}{% endblock %}
        </div>
    {% endif %}
    <div class="modal-body">
        {% block modal_body %}{% endblock %}
    </div>
    {% if modal_footer %}
        <div class="modal-footer d-md-block">
            {% block modal_foot %}{% endblock %}
        </div>
    {% endif %}
    {% if modal_form_action %}
    </form>
    {% endif %}
</div>

Si ya estabas usando este snipplet desde antes, es probable que tengas que actualizar los demás lugares donde lo usabas, agregando los parámetros modal_head: true y modal_overlay: true en el embed.

5. Por último para la parte de HTML, necesitamos agregar una carpeta SVG dentro de la carpeta snipplets, donde vamos a sumar los sniplpets para los iconos usados. En este caso serán los siguientes:

times.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>

chevron-down.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M441.9 167.3l-19.8-19.8c-4.7-4.7-12.3-4.7-17 0L224 328.2 42.9 147.5c-4.7-4.7-12.3-4.7-17 0L6.1 167.3c-4.7 4.7-4.7 12.3 0 17l209.4 209.4c4.7 4.7 12.3 4.7 17 0l209.4-209.4c4.7-4.7 4.7-12.3 0-17z"/></svg>

chevron-up.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M6.101 359.293L25.9 379.092c4.686 4.686 12.284 4.686 16.971 0L224 198.393l181.13 180.698c4.686 4.686 12.284 4.686 16.971 0l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L232.485 132.908c-4.686-4.686-12.284-4.686-16.971 0L6.101 342.322c-4.687 4.687-4.687 12.285 0 16.971z"/></svg>

chevron-right.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M24.707 38.101L4.908 57.899c-4.686 4.686-4.686 12.284 0 16.971L185.607 256 4.908 437.13c-4.686 4.686-4.686 12.284 0 16.971L24.707 473.9c4.686 4.686 12.284 4.686 16.971 0l209.414-209.414c4.686-4.686 4.686-12.284 0-16.971L41.678 38.101c-4.687-4.687-12.285-4.687-16.971 0z"/></svg>

chevron-left.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M231.293 473.899l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L70.393 256 251.092 74.87c4.686-4.686 4.686-12.284 0-16.971L231.293 38.1c-4.686-4.686-12.284-4.686-16.971 0L4.908 247.515c-4.686 4.686-4.686 12.284 0 16.971L214.322 473.9c4.687 4.686 12.285 4.686 16.971-.001z"/></svg>

CSS

Requisito:

Tener agregados en tu diseño las clases helpers. Podés seguir este este pequeño tutorial para hacerlo (simplemente es copiar y pegar algunas clases, no toma más de 1 minuto).

1. Agregamos el siguiente SASS de colores en style-colors.scss.tpl (o la hoja de tu diseño que tenga los colores y tipografías de la tienda). Recordá que las variables de colores y tipografías pueden variar respecto a tu diseño:

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


{# /* // Modals */ #}


.modal{
  color: $main-foreground;
  background-color:$main-background;
  &-list-item{
    border-bottom: 1px solid rgba($main-foreground, .1);
  }
}


{# /* // Buttons */ #}


.btn{
  text-decoration: none;
  text-align: center;
  border: 0;
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  text-transform: uppercase;
  background: none;
  @include prefix(transition, all 0.4s ease, webkit ms moz o);
  &:hover,
  &:focus{
    outline: 0;
    opacity: 0.8;
  }
  &[disabled],
  &[disabled]:hover{
    opacity: 0.5;
    cursor: not-allowed;
    outline: 0;
  }
}

  .nav-tab{
    border-top: 1px solid $main-foreground;
  }

2. Agregar los estilos dentro del archivo static/style-critical.tpl 

Si en tu diseño usas una hoja de estilos para el CSS crítico, vamos a necesitar el siguiente código dentro de la misma, pero si no es el caso entonces podés unificar el CSS de los pasos 2 y 3 en un solo archivo.

.nav-tab{
  padding: 10px;
  font-weight: bold;
}

3. Agregar los estilos dentro del archivo static/style-async.tpl 

{# /* // Modals */ #}


.modal {
  position: fixed;
  top: 0;
  display: block;
  width: 80%;
  height: 100%;
  padding: 10px;
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
  transition: all .2s cubic-bezier(.16,.68,.43,.99);
  z-index: 20000;
  &-header{
    width: calc(100% + 20px);
    margin: -10px 0 10px -10px;
    padding: 10px 15px;
    font-size: 20px;
  }
  &-footer{
    padding: 10px;
    clear: both;
  }
  &-full {
    width: 100%;
  }
  &-docked-sm{
    width: 100%;
  }
  &-docked-small{
    width: 80%;
  }
  &-top{
    top: -100%;
    left: 0;
  }
  &-bottom{
    top: 100%;
    left: 0;
  }
  &-left{
    left: -100%;
  }
  &-right{
    right: -100%;
    &.modal-categories{
      top: auto;
    }
  }
  &-centered{
    height: 100%;
    width: 100%;
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
    &.modal-categories{
      top: auto;
      box-shadow: -4px 0 17px 0 rgba(0, 0, 0, 0.23);
    }
  }
  &-left.modal-show {
    left: 0;
  }
  &-right.modal-show {
    right: 0;
  }
  &-close { 
    display: inline-block;
    padding: 1px 5px 5px 0;
    margin-right: 5px;
    vertical-align: middle;
    cursor: pointer;
  }
  .tab-group{
    margin:  0 -10px 20px -10px;
  }
  &-list-item{
    position: relative;
    display: block;
    width: 100%;
    padding: 15px 5px;
    text-align: left;
    font-size: 14px;
  }
  &-list-icon{
    position: absolute;
    right: 10px;
  }
  &-categories .modal-header{
    margin-bottom: 0;
  }
}


.modal-overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #00000047;
  z-index: 10000;
}

JS

⚠️ A partir del día 30 de enero de 2023, la librería jQuery será removida del código de nuestras tiendas, por lo tanto la función "$" no podrá ser utilizada.

1. El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS). El código que necesitamos es el siguiente:

{# Full screen mobile modals back events #}

if (window.innerWidth < 768) {

    {# Clean url hash function #}

    cleanURLHash = function(){
        const uri = window.location.toString();
        const clean_uri = uri.substring(0, uri.indexOf("#"));
        window.history.replaceState({}, document.title, clean_uri);
    };

    {# Go back 1 step on browser history #}

    goBackBrowser = function(){
        cleanURLHash();
        history.back();
    };

    {# Clean url hash on page load: All modals should be closed on load #}

    if(window.location.href.indexOf("modal-fullscreen") > -1) {
        cleanURLHash();
    }

    {# Open full screen modal and url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-open", function(e) {
        e.preventDefault();
        var modal_url_hash = jQueryNuvem(this).data("modalUrl");
        window.location.hash = modal_url_hash;
    });

    {# Close full screen modal: Remove url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-close", function(e) {
        e.preventDefault();
        goBackBrowser();
    });

    {# Hide panels or modals on browser backbutton #}

    window.onhashchange = function() {
        if(window.location.href.indexOf("modal-fullscreen") <= -1) {

            {# Close opened modal #}

            if(jQueryNuvem(".js-fullscreen-modal").hasClass("modal-show")){

                {# Remove body lock only if a single modal is visible on screen #}

                if(jQueryNuvem(".js-modal.modal-show").length == 1){
                    jQueryNuvem("body").removeClass("overflow-none");
                }

                var $opened_modal = jQueryNuvem(".js-fullscreen-modal.modal-show");
                var $opened_modal_overlay = $opened_modal.prev();

                $opened_modal.removeClass("modal-show");
                setTimeout(() => $opened_modal.hide(), 500);
                $opened_modal_overlay.fadeOut(500);

            }
        }
    }
}

jQueryNuvem(document).on("click", ".js-modal-open", function(e) {
    e.preventDefault(); 
    var modal_id = jQueryNuvem(this).data('toggle');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="' + modal_id + '"]');
    if (jQueryNuvem(modal_id).hasClass("modal-show")) {
        let modal = jQueryNuvem(modal_id).removeClass("modal-show");
        setTimeout(() => modal.hide(), 500);
    } else {

        {# Lock body scroll if there is no modal visible on screen #}
        
        if(!jQueryNuvem(".js-modal.modal-show").length){
            jQueryNuvem("body").addClass("overflow-none");
        }
        $overlay_id.fadeIn(400);
        jQueryNuvem(modal_id).detach().appendTo("body");
        $overlay_id.detach().insertBefore(modal_id);
        jQueryNuvem(modal_id).show().addClass("modal-show");
    }             
});

jQueryNuvem(document).on("click", ".js-modal-close", function(e) {
    e.preventDefault();  
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var $modal = jQueryNuvem(this).closest(".js-modal");
    var modal_id = $modal.attr('id');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="#' + modal_id + '"]');
    $modal.removeClass("modal-show");
    setTimeout(() => $modal.hide(), 500);
    $overlay_id.fadeOut(500);

    {# Close full screen modal: Remove url hash #}

    if ((window.innerWidth < 768) && (jQueryNuvem(this).hasClass(".js-fullscreen-modal-close"))) {
        goBackBrowser();
    }    
});

jQueryNuvem(document).on("click", ".js-modal-overlay", function(e) {
    e.preventDefault();
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var modal_id = jQueryNuvem(this).data('modalId');
    let modal = jQueryNuvem(modal_id).removeClass("modal-show");
    setTimeout(() => modal.hide(), 500); 
    jQueryNuvem(this).fadeOut(500);   

    if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) {
        cleanURLHash();
    }
});

Traducciones

En este paso agregamos los textos para las traducciones en el archivo config/translations.txt

es "Categorías"
pt "Categorias"
en "Categories"
es_mx "Categorías"


es "Ver todos los productos"
pt "Ver todos os produtos"
en "See all products"
es_mx "Ver todos los productos"


es "Ver toda esta categoría"
pt "Ver tudo desta categoria"
en "See all this category"
es_mx "Ver toda esta categoría"

¡Felicitaciones! Ya tenés la navegación de categorías mobile en tu diseño