@mayurgodhani/ecomtools-cli
Version:
E-commerce tools MCP server for Shopify development
396 lines (351 loc) • 10.3 kB
Plain Text
Title: Shopify Cart Drawer
Description: An AJAX-powered sliding cart drawer for Shopify stores that updates in real-time.
Code:
<!-- Cart Drawer HTML -->
<div id="cart-drawer" class="cart-drawer">
<div class="cart-drawer__header">
<h2>Your Cart</h2>
<button class="cart-drawer__close" aria-label="Close cart">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="cart-drawer__content">
<div class="cart-drawer__items">
{%- if cart.item_count > 0 -%}
<form action="{{ routes.cart_url }}" method="post" id="cart-drawer-form">
{%- for item in cart.items -%}
<div class="cart-drawer__item" data-item-key="{{ item.key }}">
<div class="cart-drawer__item-image">
<img src="{{ item.image | img_url: '120x' }}" alt="{{ item.title | escape }}">
</div>
<div class="cart-drawer__item-details">
<a href="{{ item.url }}" class="cart-drawer__item-title">
{{ item.product.title }}
</a>
<div class="cart-drawer__item-variant">
{{ item.variant.title }}
</div>
<div class="cart-drawer__item-price">
{{ item.final_price | money }}
</div>
<div class="cart-drawer__item-quantity">
<button type="button" class="quantity-button" data-action="decrease">-</button>
<input type="number" name="updates[{{ item.key }}]" value="{{ item.quantity }}" min="0" aria-label="Quantity">
<button type="button" class="quantity-button" data-action="increase">+</button>
</div>
<button type="button" class="cart-drawer__item-remove" data-item-key="{{ item.key }}">
Remove
</button>
</div>
</div>
{%- endfor -%}
<div class="cart-drawer__footer">
<div class="cart-drawer__subtotal">
<span>Subtotal</span>
<span>{{ cart.total_price | money }}</span>
</div>
<div class="cart-drawer__taxes">
{%- if cart.taxes_included -%}
<small>Tax included. <a href="{{ shop.policies.shipping_policy.url }}">Shipping</a> calculated at checkout.</small>
{%- else -%}
<small>Tax and <a href="{{ shop.policies.shipping_policy.url }}">shipping</a> calculated at checkout.</small>
{%- endif -%}
</div>
<button type="submit" name="checkout" class="cart-drawer__checkout-button">
Checkout
</button>
<a href="{{ routes.cart_url }}" class="cart-drawer__view-cart">
View Cart
</a>
</div>
</form>
{%- else -%}
<div class="cart-drawer__empty">
<p>Your cart is currently empty.</p>
<a href="{{ routes.all_products_collection_url }}" class="cart-drawer__continue-shopping">
Continue Shopping
</a>
</div>
{%- endif -%}
</div>
</div>
</div>
<!-- Cart Drawer JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const cartDrawer = document.getElementById('cart-drawer');
const cartToggle = document.querySelectorAll('.cart-toggle');
const cartClose = document.querySelector('.cart-drawer__close');
const cartOverlay = document.getElementById('cart-overlay');
// Open cart drawer
cartToggle.forEach(function(toggle) {
toggle.addEventListener('click', function(e) {
e.preventDefault();
openCartDrawer();
});
});
// Close cart drawer
if (cartClose) {
cartClose.addEventListener('click', closeCartDrawer);
}
if (cartOverlay) {
cartOverlay.addEventListener('click', closeCartDrawer);
}
// Close cart drawer when pressing ESC key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCartDrawer();
}
});
// Handle quantity buttons
const quantityButtons = document.querySelectorAll('.quantity-button');
quantityButtons.forEach(function(button) {
button.addEventListener('click', function() {
const input = this.parentNode.querySelector('input');
const currentValue = parseInt(input.value);
if (this.dataset.action === 'increase') {
input.value = currentValue + 1;
} else if (this.dataset.action === 'decrease' && currentValue > 1) {
input.value = currentValue - 1;
}
updateCart(input.name.replace('updates[', '').replace(']', ''), input.value);
});
});
// Handle remove buttons
const removeButtons = document.querySelectorAll('.cart-drawer__item-remove');
removeButtons.forEach(function(button) {
button.addEventListener('click', function() {
const itemKey = this.dataset.itemKey;
updateCart(itemKey, 0);
});
});
// Update cart via AJAX
function updateCart(itemKey, quantity) {
fetch('/cart/change.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: itemKey,
quantity: quantity
})
})
.then(response => response.json())
.then(cart => {
refreshCart();
})
.catch(error => {
console.error('Error updating cart:', error);
});
}
// Refresh cart contents
function refreshCart() {
fetch('/cart?view=drawer')
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const newContent = doc.querySelector('.cart-drawer__content');
if (newContent) {
document.querySelector('.cart-drawer__content').innerHTML = newContent.innerHTML;
}
// Re-attach event listeners
attachEventListeners();
})
.catch(error => {
console.error('Error refreshing cart:', error);
});
}
function openCartDrawer() {
cartDrawer.classList.add('cart-drawer--open');
document.body.classList.add('cart-drawer-open');
if (cartOverlay) {
cartOverlay.classList.add('cart-overlay--active');
}
}
function closeCartDrawer() {
cartDrawer.classList.remove('cart-drawer--open');
document.body.classList.remove('cart-drawer-open');
if (cartOverlay) {
cartOverlay.classList.remove('cart-overlay--active');
}
}
function attachEventListeners() {
// Re-attach event listeners after cart refresh
// Repeat the event listener attachment code for quantity buttons and remove buttons
}
});
</script>
<!-- Cart Drawer CSS -->
<style>
.cart-drawer {
position: fixed;
top: 0;
right: 0;
width: 400px;
max-width: 90vw;
height: 100vh;
background-color: #fff;
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
flex-direction: column;
}
.cart-drawer--open {
transform: translateX(0);
}
.cart-drawer__header {
padding: 1rem;
border-bottom: 1px solid #e5e5e5;
display: flex;
justify-content: space-between;
align-items: center;
}
.cart-drawer__close {
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
}
.cart-drawer__content {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.cart-drawer__item {
display: flex;
margin-bottom: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid #e5e5e5;
}
.cart-drawer__item-image {
width: 80px;
margin-right: 1rem;
}
.cart-drawer__item-details {
flex: 1;
}
.cart-drawer__item-title {
font-weight: 500;
margin-bottom: 0.25rem;
display: block;
text-decoration: none;
color: #333;
}
.cart-drawer__item-variant {
font-size: 0.875rem;
color: #666;
margin-bottom: 0.5rem;
}
.cart-drawer__item-price {
font-weight: 500;
margin-bottom: 0.5rem;
}
.cart-drawer__item-quantity {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.cart-drawer__item-quantity input {
width: 40px;
text-align: center;
margin: 0 0.5rem;
border: 1px solid #e5e5e5;
padding: 0.25rem;
}
.quantity-button {
background: none;
border: 1px solid #e5e5e5;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.cart-drawer__item-remove {
background: none;
border: none;
color: #666;
text-decoration: underline;
cursor: pointer;
padding: 0;
font-size: 0.875rem;
}
.cart-drawer__footer {
margin-top: 2rem;
}
.cart-drawer__subtotal {
display: flex;
justify-content: space-between;
font-weight: 500;
margin-bottom: 1rem;
}
.cart-drawer__taxes {
margin-bottom: 1.5rem;
font-size: 0.875rem;
color: #666;
}
.cart-drawer__checkout-button {
width: 100%;
padding: 0.75rem 1rem;
background-color: #000;
color: #fff;
border: none;
cursor: pointer;
font-weight: 500;
margin-bottom: 0.75rem;
}
.cart-drawer__view-cart {
display: block;
text-align: center;
color: #333;
text-decoration: none;
}
.cart-drawer__empty {
text-align: center;
padding: 2rem 0;
}
.cart-drawer__continue-shopping {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #000;
color: #fff;
text-decoration: none;
}
.cart-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
}
.cart-overlay--active {
opacity: 1;
visibility: visible;
}
@media (max-width: 480px) {
.cart-drawer {
width: 100%;
max-width: 100%;
}
}
</style>
<!-- Don't forget to add this overlay element to your theme.liquid -->
<div id="cart-overlay" class="cart-overlay"></div>
<!-- Add this button somewhere in your navigation -->
<button class="cart-toggle">
Cart
<span class="cart-count">{{ cart.item_count }}</span>
</button>