Filtros y orden de productos

En este tutorial vamos a ver cómo agregar los filtros de categorías y propiedades, así como la posibilidad de ordenar los productos.

Entre las propiedades a filtrar se encuentran:

  • Variantes del producto
  • Marca
  • Precio
  • Campos personalizados

En caso que sólo te interese tener el filtro de precios, te recomendamos ver este artículo.


HTML

1. Lo primero que vamos a hacer, actualizar el código del snipplet filters.tpl dentro de la carpeta snipplets/grid con el siguiente código:

{% if applied_filters %}
    
    {# Applied filters chips #}

    {% if has_applied_filters %}
        <div class="col-12 mb-3">
            <div class="d-md-inline-block mr-md-2 mb-3">{{ 'Filtrado por:' | translate }}</div>
            {% for product_filter in product_filters %}
                {% for value in product_filter.values %}

                    {# List applied filters as tags #}
                    
                    {% if value.selected %}
                        <button class="js-remove-filter chip" data-filter-name="{{ product_filter.key }}" data-filter-value="{{ value.name }}">
                            {{ value.pill_label }}
                            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline chip-remove-icon"} %}
                        </button>
                    {% endif %}
                {% endfor %}
            {% endfor %}
            <a href="#" class="js-remove-all-filters d-inline-block px-0">{{ 'Borrar filtros' | translate }}</a> 
        </div>
    {% endif %}
{% else %}
    {% if product_filters is not empty %}
        <div id="filters" data-store="filters-nav">
            {% for product_filter in product_filters %}
                {% if product_filter.type == 'price' %}

                    {{ component(
                        'price-filter',
                        {'group_class': 'filters-container mb-5', 'title_class': 'h6 mb-3', 'button_class': 'btn btn-default px-2 px-md-3 align-bottom' }
                    ) }}

                {% else %}
                    {% if product_filter.has_products %}
                        <div class="filters-container mb-5" data-store="filters-group">
                            <h6 class="mb-3">{{product_filter.name}}</h6>
                            {% set index = 0 %}
                            {% for value in product_filter.values %}
                                {% if value.product_count > 0 %}
                                    {% set index = index + 1 %}
                                    <label class="js-filter-checkbox {% if not value.selected %}js-apply-filter{% else %}js-remove-filter{% endif %} checkbox-container font-weight-bold {% if mobile %}mb-3{% else %}mb-2{% endif %}" data-filter-name="{{ product_filter.key }}" data-filter-value="{{ value.name }}">
                                        <span class="checkbox">
                                            <input type="checkbox" autocomplete='off' {% if value.selected %}checked{% endif %}>
                                            <span class="checkbox-icon"></span>
                                            <span class="checkbox-text">{{ value.name }} ({{ value.product_count }})</span>
                                            {% if product_filter.type == 'color' and value.color_type == 'insta_color' %}
                                                <span class="checkbox-color" style="background-color: {{ value.color_hexa }};"></span>
                                            {% endif %}
                                        </span>
                                    </label>
                                    {% if index == 8 and product_filter.values_with_products > 8 %}
                                        <div class="js-accordion-container" style="display: none;">
                                    {% endif %}
                                {% endif %}
                                {% if loop.last and product_filter.values_with_products > 8 %}
                                    </div>
                                    <a href="#" class="js-accordion-toggle btn-link d-inline-block mt-1 pl-0">
                                        <span class="js-accordion-toggle-inactive">
                                            {{ 'Ver todos' | translate }}
                                        </span>
                                        <span class="js-accordion-toggle-active" style="display: none;">
                                            {{ 'Ver menos' | translate }}
                                        </span>
                                    </a>
                                {% endif %}
                            {% endfor %}
                        </div>
                    {% endif %}
                {% endif %}
            {% endfor %}
        </div>
    {% endif %}
{% endif %}

2. Luego, vamos a crear un nuevo snipplet categories.tpl dentro de la misma carpeta snipplets/grid, donde usamos el siguiente código:

{% if filter_categories %}
    <div class="filters-container mb-5">
        <h6 class="mb-3">{{ "Categorías" | translate }}</h6>
        <ul class="list-unstyled"> 
            {% for category in filter_categories %}
                <li data-item="{{ loop.index }}" class="mb-3">
                    <a href="{{ category.url }}" title="{{ category.name }}" class="text-primary">
                        {{ category.name }}
                    </a>
                </li>

                {% if loop.index == 8 and filter_categories | length > 8 %}
                    <div class="js-accordion-container" style="display: none;">
                {% endif %}
                {% if loop.last and filter_categories | length > 8 %}
                    </div>
                    <a href="#" class="js-accordion-toggle btn-link d-inline-block mt-1 pl-0">
                        <span class="js-accordion-toggle-inactive">
                            {{ 'Ver más' | translate }}
                        </span>
                        <span class="js-accordion-toggle-active" style="display: none;">
                            {{ 'Ver menos' | translate }}
                        </span>
                    </a>
                {% endif %}
            {% endfor %}
        </ul>
    </div>
{% endif %}

3. 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
 
 
#Head
    // Block - modal_head
#Body
    // Block - modal_body
#Footer
    // Block - modal_footer
 
#}
 
{% set modal_overlay = modal_overlay | default(true) %}
 
<div id="{{ modal_id }}" class="js-modal {% if modal_mobile_full_screen %}js-fullscreen-modal{% endif %} modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft" style="display: none;">
    {% if modal_form_action %}
    <form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}" {% if modal_form_hook %}data-store="{{ modal_form_hook }}"{% endif %}>
    {% endif %}
    <div class="js-modal-close {% if modal_mobile_full_screen %}js-fullscreen-modal-close{% endif %} modal-header">
        <span class="modal-close">
            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
        </span>
        {% block modal_head %}{% endblock %}
    </div>
    <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>

4. Vamos a agregar el archivo sort-by.tpl dentro de la carpeta snipplets/grid para la funcionalidad de orden de productos. 

{% set sort_text = {
'user': 'Destacado',
'price-ascending': 'Precio: Menor a Mayor',
'price-descending': 'Precio: Mayor a Menor',
'alpha-ascending': 'A - Z',
'alpha-descending': 'Z - A',
'created-ascending': 'Más Viejo al más Nuevo',
'created-descending': 'Más Nuevo al más Viejo',
'best-selling': 'Más Vendidos',
} %}
{% embed "snipplets/forms/form-select.tpl" with{select_label: false, select_custom_class: 'js-sort-by', select_group_custom_class: 'mb-0', select_aria_label: 'Ordenar por:' | translate } %}
    {% block select_options %}
        {% for sort_method in sort_methods %}
            {# This is done so we only show the user sorting method when the user chooses it #}
            {% if sort_method != 'user' or category.sort_method == 'user' %}
                <option value="{{ sort_method }}" {% if sort_by == sort_method %}selected{% endif %}>{{ sort_text[sort_method] | t }}</option>
            {% endif %}
        {% endfor %}
    {% endblock select_options%}
{% endembed %}

5. Para poder usar el snipplet que creamos en el paso anterior vamos a tener que crear un nuevo componente para el select o desplegable. Para esto creamos el archivo form-select.tpl dentro de la carpeta snipplets/forms con el siguiente código

{# /*============================================================================
  #Form select
==============================================================================*/
 
#Properties
 
#Group
    //select_group_custom_class for custom CSS classes
#Label 
    // select_label_name for name
    // select_label_id for ID
    // select_for for label for
    // select_label_custom_class for custom CSS classes
#Select 
    // select_id for id
    // select_name for name
    // select_custom_class for custom CSS classes 
    // input_rows for textarea rows
    // select_options to insert select options
    // select_aria_label for aria-label attribute
 
#}
 
<div class="form-group {{ select_group_custom_class }}">
    {% if select_label %}
        <label {% if select_label_id%}id="{{ select_label_id }}"{% endif %} class="form-label {{ select_label_custom_class }}" {% if select_for %}for="{{ select_for }}"{% endif %}>{{ select_label_name }}</label>
    {% endif %}
    <select 
        {% if select_id %}id="{{ select_id }}"{% endif %}
        class="form-select {{ select_custom_class }} {% if select_inline %}form-control-inline{% endif %}"
        {% if select_data %}data-{{select_data}}="{{select_data_value}}"{% endif %}
        {% if select_name %}name="{{ select_name }}"{% endif %}
        {% if select_aria_label %}aria-label="{{ select_aria_label }}"{% endif %}>
        {% block select_options %}
        {% endblock select_options %}
    </select>
    <div class="form-select-icon">
        {% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "icon-inline icon-w-14 icon-lg svg-icon-text"} %}
    </div>
</div>

6. Casi terminando vamos a incluir ambos archivos creados en los pasos 1 y 2, en el template category.tpl a través del modal creado en el paso 3. Arriba de la parte donde incluimos la grilla de productos, la cual comienza con {% if products %}, vamos a incluir lo siguiente:

{% set has_filters_available = products and has_filters_enabled and (filter_categories is not empty or product_filters is not empty) %}
<div class="js-category-controls-prev category-controls-sticky-detector"></div>
<div class="js-category-controls row align-items-center mb-md-3 category-controls">
    {% if products %}
        {% set columns = settings.grid_columns %}
        <div class="col-6{% if columns == 2 %} col-md-9{% else %} col-md-9{% endif %}">
        {% if has_filters_available %}
            <a href="#" class="js-modal-open filter-link" data-toggle="#nav-filters">
                {{ 'Filtrar' | t }} {% include "snipplets/svg/filter.tpl" with {svg_custom_class: "icon-inline icon-w-16"} %} 
            </a>           
            {% embed "snipplets/modal.tpl" with{modal_id: 'nav-filters', modal_class: 'filters modal-docked-small', modal_position: 'left', modal_transition: 'slide', modal_width: 'full'} %}
                {% block modal_head %}
                    {{'Filtros' | translate }}
                {% endblock %}
                {% block modal_body %}
                    {% if filter_categories is not empty %}
                        {% snipplet "grid/categories.tpl" %}
                    {% endif %}
                    {% if product_filters is not empty %}
                        {% snipplet "grid/filters.tpl" %}
                    {% endif %}
                    <div class="js-filters-overlay filters-overlay" style="display: none;">
                        <div class="filters-updating-message">
                            <h3 class="js-applying-filter" style="display: none;">{{ 'Aplicando filtro...' | translate }}</h3>
                            <h3 class="js-removing-filter" style="display: none;">{{ 'Borrando filtro...' | translate }}</h3>
                        </div>
                    </div>
                {% endblock %}
            {% endembed %}
        {% endif %}
        </div>
        <div class="col-6{% if columns == 2 %} col-md-3{% else %} col-md-3{% endif %} text-right">
            {% include 'snipplets/grid/sort-by.tpl' %}
        </div>
    {% endif %}
</div>
<div class="row">
    {% include "snipplets/grid/filters.tpl" with {applied_filters: true} %}
</div>    

7. Por último para la parte de HTML, dentro de la carpeta snipplets/SVG vamos sumar los SVGs que usamos para el select, el icono de filtrado y el modal.

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-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>

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>

filter.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M463.952 0H48.057C5.419 0-16.094 51.731 14.116 81.941L176 243.882V416c0 15.108 7.113 29.335 19.2 40l64 47.066c31.273 21.855 76.8 1.538 76.8-38.4V243.882L497.893 81.941C528.042 51.792 506.675 0 463.952 0zM288 224v240l-64-48V224L48 48h416L288 224z"/></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 static/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:

{# /* // Mixins */ #}
{# This mixin adds browser prefixes to a CSS property #}
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}
 
{# /* // Links */ #}
 
a {
  color: $main-foreground;
  fill: $main-foreground;
  @include prefix(transition, all 0.4s ease, webkit ms moz o);
  &:hover,
  &:focus{
    color: rgba($main-foreground, .5);
    fill: rgba($main-foreground, .5);
  }
}
 
.link-contrast {
  color: $main-background;
  fill: $main-background;
  &:hover,
  &:focus{
    color: rgba($main-background, .8);
    fill: rgba($main-background, .8);
  }
}
 
.btn-link{
  color: $primary-color;
  fill: $primary-color;
  text-transform: uppercase;
  border-bottom: 1px solid;
  font-weight: bold;
  cursor: pointer;
  &:hover,
  &:focus{
    color: rgba($primary-color, .5);
    fill: rgba($primary-color, .5);
  }
}
 
{# /* // Chips */ #}
 
.chip {
  color: $main-foreground;
  background: rgba($main-foreground,0.1);
  border: 0;
 
  &-remove-icon {
    fill: $main-foreground;
  }
}
 
{# /* // Modals */ #}
 
.modal{
  color: $main-foreground;
  background-color:$main-background;
}
 
{# /* // Forms */ #}
 
input,
textarea {
  font-family: $body-font;
}
 
.form-control {
  display: block;
  padding: 8px;
  width: 100%;
  font-size: 16px; /* Hack to avoid autozoom on IOS */
  border: 0;
  border-bottom: 1px solid rgba($main-foreground, .5);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  color: $main-foreground;
  background-color: $main-background;
  &:focus{
    outline: 0;
  }
  &-inline{
    display: inline;
  }
}
 
.form-control::-webkit-input-placeholder { 
  color: $main-foreground;
}
.form-control:-moz-placeholder {
  color: $main-foreground;
}
.form-control::-moz-placeholder {
  color: $main-foreground;
}
.form-control:-ms-input-placeholder {
  color: $main-foreground;
}
 
.form-select{
  display: block;
  padding: 10px 0;
  width: 100%;
  font-size: 16px; /* Hack to avoid autozoom on IOS */
  border: 0;
  border-bottom: 1px solid rgba($main-foreground, .5);
  border-radius: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  color: $main-foreground;
  background-color: $main-background;
  @extend %body-font;
  &-icon{
    background: $main-background;
  }
}
 
.checkbox-container{
  .checkbox {
    color: $main-foreground;
    &-color {
      border: 1px solid rgba($main-foreground,.1);
    }
    &-icon {
      background: $main-background;
      border: 1px solid $main-foreground;
      &:after {
        border: solid $main-foreground;
        border-width: 0 2px 2px 0;
      }
    }
    &:hover {
      color: rgba($main-foreground,.8);
    }
  }
}

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.

{# /* // Forms */ #}
 
.form-group {
  position: relative;
  width: 100%;
}
.form-group .form-select-icon{
  position: absolute;
  bottom: 12px;
  right: 0;
  pointer-events: none;
}
 
{# /* // Category header */ #}
 
.filter-link {
  display: inline-block;
  width: 100%;
  padding: 10px 0;
}

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

{# /* // Mixins */ #}
 
{# This mixin adds browser prefixes to a CSS property #}
 
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    #{'-' + $prefix + '-' + $property}: $value;
  }
  #{$property}: $value;
}
 
{# /* // 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-md{
    width: 100%;
  }
  &-docked-small{
    width: 80%;
  }
  &-top{
    top: -100%;
    left: 0;
  }
  &-bottom{
    top: 100%;
    left: 0;
  }
  &-left{
    left: -100%;
  }
  &-right{
    right: -100%;
  }
  &-centered{
    height: 100%;
    width: 100%;
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
  }
  &-bottom-sheet {
    top: initial;
    bottom: -100%;
    height: auto;
    &.modal-show {
      top: initial;
      bottom: 0;
      height: auto;
    }
  }
  &-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;
  }
}
 
.modal-overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #00000047;
  z-index: 10000;
}
 
{# /* // Forms */ #}
 
.form-group{
  @extend %element-margin;
  .form-label{
    float: left;
    width: 100%;
    margin-bottom: 10px;
  }
  .alert{
    margin: 10px 0 0 0;
  }
}
 
.checkbox-container{
  .checkbox {
    position: relative;
    display: block;
    margin-bottom: 15px;
    padding-left: 30px;
    line-height: 20px;
    cursor: pointer;
    @include prefix(user-select, none, webkit ms moz o);
 
    &-color {
      display: inline-block;
      width: 10px;
      height: 10px;
      margin: 0 0 2px 5px;
      vertical-align: middle;
      border-radius: 100%;
    }
 
    input {
      display: none;
      &:checked ~ .checkbox-icon:after {
        display: block;
      }
    }
 
    &-icon {
      position: absolute;
      top: -1px;
      left: 0;
      width: 20px;
      height: 20px;
 
      &:after {
        position: absolute;
        top: 1px;
        left: 6px;
        display: none;
        width: 7px;
        height: 12px;
        content: '';
        @include prefix(transform, rotate(45deg), webkit ms moz o);
      }
    }
  }
}
 
.form-select {
  display: block;
  width: 100%;
  &:focus{
    outline:0;
  }
  &::-ms-expand {
    display: none;
  }
}

.filter-input-price-container {
  display: inline-block;
  width: 85px;
  margin-right: 5px;
  .filter-input-price {
    padding: 10px;
  }
}
 
{#/*============================================================================
  #Media queries
==============================================================================*/ #}
 
{# /* // Min width 768px */ #}
 
@media (min-width: 768px) { 
 
  {# /* //// Components */ #}
 
  {# /* Modals */ #}
 
  .modal{
    &-centered{
      height: 80%;
      width: 80%;
      left: 10%;
      margin: 5% auto;
    }
    &-docked-md{
      width: 500px;
      &-centered{
        left: calc(50% - 250px);
        bottom: auto;
        height: auto;
      }
    }
    &-bottom-sheet {
      top: 100%;
      &.modal-show {
        top: 0;
        bottom: auto;
      }
    }
    &-docked-small{
      width: 350px;
    }
  }
}

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.

El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS).  Agregamos el siguiente código para los modals:

{#/*============================================================================
  #Modals
==============================================================================*/ #}
 
{# 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();
    }
});
 
{#/*============================================================================
  #Product grid
==============================================================================*/ #}
 
var $category_controls = jQueryNuvem(".js-category-controls");
var mobile_nav_height = jQueryNuvem(".js-head-main").innerHeight();

{% if template == 'category' %}

    {# /* // Fixed category controls */ #}

    if (window.innerWidth < 768) {
        {% if settings.head_fix %}
            $category_controls.css("top" , mobile_nav_height.toString() + 'px');
        {% else %}
            jQueryNuvem(".js-category-controls").css("top" , "0px");
        {% endif %}

        {# Detect if category controls are sticky and add css #}

        var observer = new IntersectionObserver(function(entries) {
            if(entries[0].intersectionRatio === 0)
                jQueryNuvem(".js-category-controls").addClass("is-sticky");
            else if(entries[0].intersectionRatio === 1)
                jQueryNuvem(".js-category-controls").removeClass("is-sticky");
            }, { threshold: [0,1] 
        });

        observer.observe(document.querySelector(".js-category-controls-prev"));
    }

    {# /* // Filters */ #}

    jQueryNuvem(document).on("click", ".js-apply-filter, .js-remove-filter", function(e) {
        e.preventDefault();
        var filter_name = jQueryNuvem(this).data('filterName');
        var filter_value = jQueryNuvem(this).data('filterValue');
        if(jQueryNuvem(this).hasClass("js-apply-filter")){
            jQueryNuvem(this).find("[type=checkbox]").prop("checked", true);
            LS.urlAddParam(
                filter_name,
                filter_value,
                true
            );
        }else{
            jQueryNuvem(this).find("[type=checkbox]").prop("checked", false);
            LS.urlRemoveParam(
                filter_name,
                filter_value
            );
        }

        {# Toggle class to avoid adding double parameters in case of double click and show applying changes feedback #}

        if (jQueryNuvem(this).hasClass("js-filter-checkbox")){
            if (window.innerWidth < 768) {
                jQueryNuvem(".js-filters-overlay").show();
                if(jQueryNuvem(this).hasClass("js-apply-filter")){
                    jQueryNuvem(".js-applying-filter").show();
                }else{
                    jQueryNuvem(".js-removing-filter").show();
                }
            }
            jQueryNuvem(this).toggleClass("js-apply-filter js-remove-filter");
        }
    });

    jQueryNuvem(document).on("click", ".js-remove-all-filters", function(e) {
        e.preventDefault();
        LS.urlRemoveAllParams();
    });

    {# /* // Sort by */ #}

    jQueryNuvem('.js-sort-by').on("change", function (e) {
        var params = LS.urlParams;
        params['sort_by'] = jQueryNuvem(e.currentTarget).val();
        var sort_params_array = [];
        for (var key in params) {
            if (!['results_only', 'page'].includes(key)) {
                sort_params_array.push(key + '=' + params[key]);
            }
        }
        var sort_params = sort_params_array.join('&');
        window.location = window.location.pathname + '?' + sort_params;
    });

{% endif %}

{#/*============================================================================
  #Accordions
==============================================================================*/ #}

jQueryNuvem(document).on("click", ".js-accordion-toggle", function(e) {
    e.preventDefault();
    if(jQueryNuvem(this).hasClass("js-accordion-show-only")){
        jQueryNuvem(this).hide();
    }else{
        jQueryNuvem(this).find(".js-accordion-toggle-inactive").toggle();
        jQueryNuvem(this).find(".js-accordion-toggle-active").toggle();
    }
    jQueryNuvem(this).prev(".js-accordion-container").slideToggle();
});

Configuraciones

En el archivo config/settings.txt vamos a agregar un checkbox para activar y desactivar la funcionalidad de filtros. Vamos a ubicarlo dentro de la sección Listado de productos.

title
        title = Filtros
    description
        description = Elegí qué filtros vas a mostrar en el listado de productos.
    checkbox
        name = product_filters
        description = Variantes del producto
    checkbox
        name = brand_filters
        description = Marca
    checkbox
        name = price_filters
        description = Precio

Traducciones

Para terminar agregamos los textos para las traducciones en el archivo config/translations.txt

--- --- Config

es "Elegí qué filtros vas a mostrar en el listado de productos."
pt "Escolha quais filtros serão exibidos na lista de produtos."
es_mx "Elige qué filtros vas a mostrar en el listado de productos."

es "Variantes del producto"
pt "Variações do produto"
es_mx "Variantes del producto"

es "Marca"
pt "Marca"
es_mx "Marca"

--- --- Sort By

es "Ordenar por:"
pt "Ordenar por:"
en "Sort by:"
es_mx "Ordenar por:"

es "Destacado"
pt "Destaque"
en "Featured"
es_mx "Destacado"

es "Precio: Menor a Mayor"
pt "Preço: Menor ao Maior"
en "Price: Low to High"
es_mx "Precio: Menor a Mayor"

es "Precio: Mayor a Menor"
pt "Preço: Maior ao Menor"
en "Price: High to Low"
es_mx "Precio: Mayor a Menor"

es "A - Z"
pt "A - Z"
en "A - Z"
es_mx "A - Z"

es "Z - A"
pt "Z - A"
en "Z - A"
es_mx "Z - A"

es "Más Viejo al más Nuevo"
pt "Mais Antigo ao mais Novo"
en "Oldest to Newest"
es_mx "Más Viejo al más Nuevo"

es "Más Nuevo al más Viejo"
pt "Mais Novo ao mais Antigo"
en "Newest to Oldest"
es_mx "Más Nuevo al más Viejo"

es "Más Vendidos"
pt "Mais Vendidos"
en "Best Selling"
es_mx "Más Vendidos"

--- --- Filtros

es "Filtrar por:"
pt "Filtrar por:"
en "Filter by:"
es_mx "Filtrar por:"

es "Filtros"
pt "Filtros"
en "Filters"
es_mx "Filtros"

es "Filtrado por:"
pt "Filtrado por:"
en "Filtered by:"
es_mx "Filtrado por:"

es "Filtro aplicado:"
pt "Filtro aplicado:"
en "Applied filter:"
es_mx "Filtro aplicado:"

es "Aplicando filtro..."
pt "Aplicando filtro..."
en "Applying filter..."
es_mx "Aplicando filtro..."

es "Borrando filtro..."
pt "Removendo filtro..."
en "Removing filter..."
es_mx "Borrando filtro..."

es "Filtrar"
pt "Filtrar"
en "Filter"
es_mx "Filtrar"

es "Ver todos"
pt "Ver todos"
en "View all"
es_mx "Ver todos"

es "Ver más"
pt "Ver mais"
en "View more"
es_mx "Ver más"

es "Ver menos"
pt "Ver menos"
en "View less"
es_mx "Ver menos"

es "Borrar filtros"
pt "Limpar filtros"
en "Clean filters"
es_mx "Borrar filtros"

es "No tenemos productos en esas variantes. Por favor, intentá con otros filtros."
pt "Não temos produtos com estas variações. Por favor, tente com outros filtros"
en "We don&#39;t have any product with those variants. Please, try with other filters"
es_mx "No tenemos productos con esas variables. Intenta con otros filtros."

es "Más colores"
pt "Mais cores"
en "More colors"
es_mx "Más colores"

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

es "Categorías principales"
pt "Categorias principais"
en "Main categories"
es_mx "Categorías principales"

es "Mostrar más categorías"
pt "Mostrar mais categorias"
en "Show more categories"
es_mx "Mostrar más categorías"

es "Talle"
pt "Tamanho"
en "Size"
es_mx "Talla"

es "Color"
pt "Cor"
en "Color"
es_mx "Color"

Activación

Por último podés activar los filtros desde el Administrador nube, en la sección de Personalizar tu diseño actual dentro de las Listado de productos.

Listo, ya tenés en tu diseño ambas funcionalidad aplicadas ¡Excelente!