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
This commit is contained in:
marination 2021-03-11 21:24:47 +05:30
parent 16b9c8c383
commit 4f64d1c7f2
7 changed files with 142 additions and 44 deletions

View File

@ -138,7 +138,7 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
"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 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
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 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
}
else:
return {
'name': quotation.name,
'shopping_cart_menu': get_shopping_cart_menu(context)
'name': quotation.name
}
@frappe.whitelist()

View File

@ -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 -%}

View File

@ -93,9 +93,6 @@ $.extend(shopping_cart, {
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 @@ $.extend(shopping_cart, {
if(cart_count) {
$badge.html(cart_count);
$cart.addClass('cart-animate');
setTimeout(() => {
$cart.removeClass('cart-animate');
}, 500);
} else {
$badge.remove();
}

View File

@ -363,6 +363,31 @@ body.product-page {
}
}
.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 @@ body.product-page {
}
}
.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;
}
}

View File

@ -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 %}
{% 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 %}
<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>
</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 %}
{% 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 class="btn btn-sm btn-add-to-cart-list">
{{ _('Add to Cart') }}
</div>
</div>
{% endif %}
</div>

View File

@ -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() {

View File

@ -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) }}