feat: Animate Add to Cart List interactions (UX)
- Increased qty in cart on clicking add to cart for existing item
- Simplified macro arguments
- Navbar cart icon animation
- Explore button for template item in card
- Add to cart button animation
diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
index a5f38c6..c2c94a3 100644
--- a/erpnext/e_commerce/shopping_cart/cart.py
+++ b/erpnext/e_commerce/shopping_cart/cart.py
@@ -138,7 +138,7 @@
"additional_notes": additional_notes
})
else:
- quotation_items[0].qty = qty
+ quotation_items[0].qty = qty + 1
quotation_items[0].additional_notes = additional_notes
apply_cart_settings(quotation=quotation)
@@ -153,9 +153,8 @@
set_cart_count(quotation)
- context = get_cart_quotation(quotation)
-
if cint(with_items):
+ context = get_cart_quotation(quotation)
return {
"items": frappe.render_template("templates/includes/cart/cart_items.html",
context),
@@ -164,8 +163,7 @@
}
else:
return {
- 'name': quotation.name,
- 'shopping_cart_menu': get_shopping_cart_menu(context)
+ 'name': quotation.name
}
@frappe.whitelist()
diff --git a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
index fe061d5..889a228 100644
--- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
+++ b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
@@ -25,8 +25,7 @@
{%- if item -%}
{%- set item = frappe.get_doc("Item", item) -%}
{{ item_card(
- item.item_name, item.image, item.route, item.description,
- None, item.item_group, values['card_' + index + '_featured'],
+ item, is_featured=values['card_' + index + '_featured'],
True, "Center"
) }}
{%- endif -%}
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 6553801..bcfa983 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -93,9 +93,6 @@
btn: opts.btn,
callback: function(r) {
shopping_cart.set_cart_count();
- if (r.message.shopping_cart_menu) {
- $('.shopping-cart-menu').html(r.message.shopping_cart_menu);
- }
if(opts.callback)
opts.callback(r);
}
@@ -129,6 +126,10 @@
if(cart_count) {
$badge.html(cart_count);
+ $cart.addClass('cart-animate');
+ setTimeout(() => {
+ $cart.removeClass('cart-animate');
+ }, 500);
} else {
$badge.remove();
}
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 4317279..8380f6c 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -363,6 +363,31 @@
}
}
+.cart-animate {
+ animation: wiggle 0.5s linear;
+}
+@keyframes wiggle {
+ 8%,
+ 41% {
+ transform: translateX(-10px);
+ }
+ 25%,
+ 58% {
+ transform: translate(10px);
+ }
+ 75% {
+ transform: translate(-5px);
+ }
+ 92% {
+ transform: translate(5px);
+ }
+ 0%,
+ 100% {
+ transform: translate(0);
+ }
+}
+
+
#page-cart {
.shopping-cart-header {
@@ -557,16 +582,50 @@
}
}
-.btn-add-to-cart-list {
+.btn-explore-variants {
+ box-shadow: none;
+ margin: var(--margin-sm) 0;
+ margin-left: 18px;
+ max-height: 30px; // to avoid resizing on window resize
+ flex: none;
+ transition: 0.3s ease;
+ color: var(--orange-500);
+ background-color: white;
+ border: 1px solid var(--orange-500);
+
+ &:hover {
+ color: white;
+ background-color: var(--orange-500);
+ }
+}
+
+.btn-add-to-cart-list{
+ box-shadow: none;
+ margin: var(--margin-sm) 0;
+ max-height: 30px; // to avoid resizing on window resize
+ flex: none;
+ transition: 0.3s ease;
+}
+
+.not-added {
+ margin-left: 18px;
color: var(--blue-500);
background-color: white;
- box-shadow: none;
border: 1px solid var(--blue-500);
- margin: var(--margin-sm) 0;
- flex: none;
&:hover {
background-color: var(--blue-500);
color: white;
}
}
+
+.added-to-cart {
+ margin-left: 18px;
+ background-color: var(--dark-green-400);
+ color: white;
+ border: 2px solid var(--green-300);
+
+ &:hover {
+ color: white;
+ }
+}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 73c8745..818316c 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -59,13 +59,17 @@
{% endmacro %}
-{%- macro item_card(title, image, url, description, rate, category, in_stock=None, is_featured=False, is_full_width=False, align="Left") -%}
+{%- macro item_card(item, is_featured=False, is_full_width=False, align="Left") -%}
{%- set align_items_class = resolve_class({
'align-items-end': align == 'Right',
'align-items-center': align == 'Center',
'align-items-start': align == 'Left',
}) -%}
{%- set col_size = 3 if is_full_width else 4 -%}
+{%- set title = item.item_name or item.item_code -%}
+{%- set image = item.website_image or item.image -%}
+{%- set description = item.website_description or item.description-%}
+
{% if is_featured %}
<div class="col-sm-{{ col_size*2 }} item-card">
<div class="card featured-item {{ align_items_class }}">
@@ -75,12 +79,12 @@
<img class="card-img" src="{{ image }}" alt="{{ title }}">
</div>
<div class="col-md-6">
- {{ item_card_body(title, description, url, rate, category, is_featured, align) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
</div>
{% else %}
<div class="col-md-12">
- {{ item_card_body(title, description, url, rate, category, is_featured, align) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
{% endif %}
</div>
@@ -90,24 +94,24 @@
<div class="card {{ align_items_class }}">
{% if image %}
<div class="card-img-container">
- <a href="/{{ url or '#' }}" style="text-decoration: none;">
+ <a href="/{{ item.route or '#' }}" style="text-decoration: none;">
<img class="card-img" src="{{ image }}" alt="{{ title }}">
</a>
</div>
{% else %}
- <a href="/{{ url or '#' }}" style="text-decoration: none;">
+ <a href="/{{ item.route or '#' }}" style="text-decoration: none;">
<div class="card-img-top no-image">
{{ frappe.utils.get_abbr(title) }}
</div>
</a>
{% endif %}
- {{ item_card_body(title, description, url, rate, category, is_featured, align, in_stock) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
</div>
{% endif %}
{%- endmacro -%}
-{%- macro item_card_body(title, description, url, rate, category, is_featured, align, in_stock=None) -%}
+{%- macro item_card_body(title, description, item, is_featured, align) -%}
{%- set align_class = resolve_class({
'text-right': align == 'Right',
'text-center': align == 'Center' and not is_featured,
@@ -116,33 +120,44 @@
<div class="card-body {{ align_class }}" style="width:100%">
<div style="margin-top: 16px; display: flex;">
- <a href="/{{ url or '#' }}">
+ <a href="/{{ item.route or '#' }}">
<div class="product-title">{{ title or '' }}</div>
</a>
- {% if in_stock %}
- <span class="indicator {{ in_stock }} card-indicator"></span>
+ {% if item.in_stock %}
+ <span class="indicator {{ item.in_stock }} card-indicator"></span>
{% endif %}
- <input class="level-item list-row-checkbox hidden-xs"
- type="checkbox" data-name="{{ title }}" style="display: none !important;">
- <div class="like-action"
- data-name="{{ title }}" data-doctype="Item">
- <svg class="icon sm">
- <use class="wish-icon" href="#icon-heart"></use>
- </svg>
- </div>
+ {% if not item.has_variants %}
+ <input class="level-item list-row-checkbox hidden-xs"
+ type="checkbox" data-name="{{ title }}" style="display: none !important;">
+ <div class="like-action"
+ data-name="{{ title }}" data-doctype="Item">
+ <svg class="icon sm">
+ <use class="wish-icon" href="#icon-heart"></use>
+ </svg>
+ </div>
+ {% endif %}
</div>
{% if is_featured %}
- <div class="product-price">{{ rate or '' }}</div>
+ <div class="product-price">{{ item.formatted_price or '' }}</div>
<div class="product-description ellipsis">{{ description or '' }}</div>
{% else %}
- <div class="product-category">{{ category or '' }}</div>
+ <div class="product-category">{{ item.item_group or '' }}</div>
<div style="display: flex;">
- {% if rate %}
- <div class="product-price" style="width: 60%;">{{ rate or '' }}</div>
+ {% if item.formatted_price %}
+ <div class="product-price">{{ item.formatted_price or '' }}</div>
{% endif %}
- <div class="btn btn-sm btn-add-to-cart-list">
- {{ _('Add to Cart') }}
- </div>
+ {% if item.has_variants %}
+ <a href="/{{ item.route or '#' }}">
+ <div class="btn btn-sm btn-explore-variants">
+ {{ _('Explore') }}
+ </div>
+ </a>
+ {% else %}
+ <div id="{{ item.name }}" class="btn btn-sm btn-add-to-cart-list not-added"
+ data-item-code="{{ item.item_code }}">
+ {{ _('Add to Cart') }}
+ </div>
+ {% endif %}
</div>
{% endif %}
</div>
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 37e07f4..4572ee7 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -2,6 +2,7 @@
class ProductListing {
constructor() {
this.bind_filters();
+ this.bind_card_actions();
this.bind_search();
this.restore_filters_state();
}
@@ -71,8 +72,35 @@
}, 1000));
}
- make_filters() {
+ bind_card_actions() {
+ $('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
+ const $btn = $(e.currentTarget);
+ $btn.prop('disabled', true);
+ this.animate_add_to_cart($btn);
+
+ const item_code = $btn.data('item-code');
+ erpnext.shopping_cart.update_cart({
+ item_code,
+ qty: 1
+ });
+
+ });
+ }
+
+ animate_add_to_cart(button) {
+ // Create 'added to cart' animation
+ let btn_id = "#" + button[0].id;
+ button.removeClass('not-added');
+ button.addClass('added-to-cart');
+ $(btn_id).text('Added to Cart');
+
+ // undo
+ setTimeout(() => {
+ button.removeClass('added-to-cart');
+ button.addClass('not-added');
+ $(btn_id).text('Add to Cart');
+ }, 2000);
}
bind_search() {
diff --git a/erpnext/www/all-products/item_row.html b/erpnext/www/all-products/item_row.html
index 665936d..072e359 100644
--- a/erpnext/www/all-products/item_row.html
+++ b/erpnext/www/all-products/item_row.html
@@ -1,6 +1,4 @@
{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
-{{ item_card(
- item.item_name or item.name, item.website_image or item.image, item.route, item.website_description or item.description,
- item.formatted_price, item.item_group, in_stock=item.in_stock
-) }}
+{{ item_card(item) }}
+