@magic-spells/cart-panel 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -190
- package/dist/cart-panel.cjs.css +2 -9
- package/dist/cart-panel.cjs.js +185 -77
- package/dist/cart-panel.cjs.js.map +1 -1
- package/dist/cart-panel.css +2 -9
- package/dist/cart-panel.esm.css +2 -9
- package/dist/cart-panel.esm.js +185 -77
- package/dist/cart-panel.esm.js.map +1 -1
- package/dist/cart-panel.js +1637 -1254
- package/dist/cart-panel.js.map +1 -1
- package/dist/cart-panel.min.css +1 -1
- package/dist/cart-panel.min.js +1 -1
- package/dist/cart-panel.scss +2 -12
- package/package.json +2 -2
- package/src/cart-panel.js +185 -78
- package/src/cart-panel.scss +2 -12
package/README.md
CHANGED
|
@@ -42,44 +42,44 @@ Or include directly in your HTML:
|
|
|
42
42
|
```html
|
|
43
43
|
<!-- Trigger button -->
|
|
44
44
|
<button aria-haspopup="dialog" aria-controls="my-cart" aria-expanded="false">
|
|
45
|
-
|
|
45
|
+
Open Cart (3 items)
|
|
46
46
|
</button>
|
|
47
47
|
|
|
48
48
|
<!-- Cart modal dialog -->
|
|
49
49
|
<cart-dialog id="my-cart" aria-labelledby="cart-title">
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
50
|
+
<cart-panel>
|
|
51
|
+
<div class="cart-header">
|
|
52
|
+
<h2 id="cart-title">Shopping Cart</h2>
|
|
53
|
+
<button data-action="hide-cart" aria-label="Close cart">×</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="cart-body">
|
|
57
|
+
<!-- Cart items using @magic-spells/cart-item -->
|
|
58
|
+
<cart-item data-key="shopify-line-item-123">
|
|
59
|
+
<cart-item-content>
|
|
60
|
+
<div class="product-info">
|
|
61
|
+
<img src="product.jpg" alt="Product" />
|
|
62
|
+
<div>
|
|
63
|
+
<h4>Awesome T-Shirt</h4>
|
|
64
|
+
<div class="price">$29.99</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="quantity-controls">
|
|
68
|
+
<input type="number" data-cart-quantity value="1" min="1" />
|
|
69
|
+
<button data-action="remove">Remove</button>
|
|
70
|
+
</div>
|
|
71
|
+
</cart-item-content>
|
|
72
|
+
<cart-item-processing>
|
|
73
|
+
<div>Processing...</div>
|
|
74
|
+
</cart-item-processing>
|
|
75
|
+
</cart-item>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="cart-footer">
|
|
79
|
+
<div class="cart-total">Total: $29.99</div>
|
|
80
|
+
<button class="checkout-btn">Checkout</button>
|
|
81
|
+
</div>
|
|
82
|
+
</cart-panel>
|
|
83
83
|
</cart-dialog>
|
|
84
84
|
```
|
|
85
85
|
|
|
@@ -98,6 +98,7 @@ The component automatically handles:
|
|
|
98
98
|
- Fetching cart data from `/cart.json` on show
|
|
99
99
|
- Updating cart items via `/cart/change.json` API calls
|
|
100
100
|
- Managing cart item states and animations through integrated `@magic-spells/cart-item`
|
|
101
|
+
- Filtering out cart items with `_hidden` property from display and calculations
|
|
101
102
|
- Emitting events for cart updates and state changes
|
|
102
103
|
|
|
103
104
|
## Configuration
|
|
@@ -132,28 +133,28 @@ Example:
|
|
|
132
133
|
```html
|
|
133
134
|
<!-- Minimal cart modal -->
|
|
134
135
|
<cart-dialog id="simple-cart">
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
<cart-panel>
|
|
137
|
+
<h2>Cart</h2>
|
|
138
|
+
<button data-action="hide-cart">Close</button>
|
|
139
|
+
<!-- Cart content here -->
|
|
140
|
+
</cart-panel>
|
|
140
141
|
</cart-dialog>
|
|
141
142
|
|
|
142
143
|
<!-- Complete cart with all features -->
|
|
143
144
|
<cart-dialog id="full-cart" aria-modal="true" aria-labelledby="cart-heading">
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
<cart-overlay></cart-overlay>
|
|
146
|
+
<cart-panel>
|
|
147
|
+
<header class="cart-header">
|
|
148
|
+
<h2 id="cart-heading">Shopping Cart</h2>
|
|
149
|
+
<button data-action="hide-cart" aria-label="Close cart">×</button>
|
|
150
|
+
</header>
|
|
151
|
+
<div class="cart-content">
|
|
152
|
+
<!-- Cart items will be rendered here -->
|
|
153
|
+
</div>
|
|
154
|
+
<footer class="cart-footer">
|
|
155
|
+
<button class="checkout-btn">Checkout</button>
|
|
156
|
+
</footer>
|
|
157
|
+
</cart-panel>
|
|
157
158
|
</cart-dialog>
|
|
158
159
|
```
|
|
159
160
|
|
|
@@ -166,52 +167,52 @@ The component provides complete styling control through CSS custom properties an
|
|
|
166
167
|
```css
|
|
167
168
|
/* Customize modal positioning and sizing */
|
|
168
169
|
cart-dialog {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
--cart-panel-width: min(500px, 95vw);
|
|
171
|
+
--cart-panel-z-index: 9999;
|
|
172
|
+
--cart-overlay-z-index: 9998;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
/* Customize overlay appearance */
|
|
175
176
|
cart-overlay {
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
--cart-overlay-background: rgba(0, 0, 0, 0.3);
|
|
178
|
+
--cart-overlay-backdrop-filter: blur(8px);
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
/* Customize panel styling */
|
|
181
182
|
cart-panel {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
--cart-panel-background: #ffffff;
|
|
184
|
+
--cart-panel-shadow: -10px 0 30px rgba(0, 0, 0, 0.2);
|
|
185
|
+
--cart-panel-border-radius: 12px 0 0 12px;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/* Customize animations */
|
|
188
189
|
cart-dialog {
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
--cart-transition-duration: 400ms;
|
|
191
|
+
--cart-transition-timing: cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
/* Style your cart content layout */
|
|
194
195
|
cart-panel {
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
.cart-header {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
padding: 1.5rem;
|
|
202
|
+
border-bottom: 1px solid #eee;
|
|
203
|
+
background: #f8f9fa;
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
.cart-content {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
flex: 1;
|
|
208
|
+
overflow-y: auto;
|
|
209
|
+
padding: 1rem;
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
.cart-footer {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
padding: 1.5rem;
|
|
214
|
+
border-top: 1px solid #eee;
|
|
215
|
+
background: #f8f9fa;
|
|
215
216
|
}
|
|
216
217
|
```
|
|
217
218
|
|
|
@@ -238,26 +239,26 @@ The component supports both CSS custom properties and SCSS variables for maximum
|
|
|
238
239
|
```css
|
|
239
240
|
/* Dramatic slide-in effect */
|
|
240
241
|
.dramatic-cart {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
--cart-transition-duration: 600ms;
|
|
243
|
+
--cart-transition-timing: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
244
|
+
--cart-overlay-background: rgba(0, 0, 0, 0.4);
|
|
245
|
+
--cart-overlay-backdrop-filter: blur(10px);
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
/* Subtle minimal styling */
|
|
248
249
|
.minimal-cart {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
--cart-panel-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
251
|
+
--cart-panel-border-radius: 8px;
|
|
252
|
+
--cart-transition-duration: 200ms;
|
|
253
|
+
--cart-overlay-background: rgba(0, 0, 0, 0.05);
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
/* Mobile-optimized full-width */
|
|
256
257
|
@media (max-width: 768px) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
.mobile-cart {
|
|
259
|
+
--cart-panel-width: 100vw;
|
|
260
|
+
--cart-panel-border-radius: 0;
|
|
261
|
+
}
|
|
261
262
|
}
|
|
262
263
|
```
|
|
263
264
|
|
|
@@ -276,8 +277,8 @@ $cart-overlay-background: rgba(0, 0, 0, 0.25);
|
|
|
276
277
|
@import '@magic-spells/cart-panel/css';
|
|
277
278
|
|
|
278
279
|
.my-store cart-dialog {
|
|
279
|
-
|
|
280
|
-
|
|
280
|
+
--cart-transition-duration: 400ms;
|
|
281
|
+
--cart-panel-background: #f8f9fa;
|
|
281
282
|
}
|
|
282
283
|
```
|
|
283
284
|
|
|
@@ -330,32 +331,32 @@ await cartDialog.refreshCart();
|
|
|
330
331
|
|
|
331
332
|
// Event emitter pattern (recommended)
|
|
332
333
|
cartDialog
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
334
|
+
.on('cart-dialog:show', (e) => {
|
|
335
|
+
console.log('Cart opened by:', e.detail.triggerElement);
|
|
336
|
+
})
|
|
337
|
+
.on('cart-dialog:data-changed', (cartData) => {
|
|
338
|
+
console.log('Cart updated:', cartData);
|
|
339
|
+
// Update header cart count, etc.
|
|
340
|
+
});
|
|
340
341
|
|
|
341
342
|
// Traditional event listeners (also supported)
|
|
342
343
|
cartDialog.addEventListener('cart-item:remove', (e) => {
|
|
343
|
-
|
|
344
|
+
console.log('Remove requested:', e.detail.cartKey);
|
|
344
345
|
|
|
345
|
-
|
|
346
|
-
|
|
346
|
+
// The component handles the API calls automatically
|
|
347
|
+
// Just listen for the data changes
|
|
347
348
|
});
|
|
348
349
|
|
|
349
350
|
cartDialog.addEventListener('cart-item:quantity-change', (e) => {
|
|
350
|
-
|
|
351
|
-
|
|
351
|
+
console.log('Quantity changed:', e.detail.quantity);
|
|
352
|
+
// Component automatically syncs with Shopify
|
|
352
353
|
});
|
|
353
354
|
|
|
354
355
|
// Listen for all cart changes
|
|
355
356
|
cartDialog.on('cart-dialog:data-changed', (cartData) => {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
// Update your UI when cart changes
|
|
358
|
+
updateCartBadge(cartData.item_count);
|
|
359
|
+
updateCartTotal(cartData.total_price);
|
|
359
360
|
});
|
|
360
361
|
```
|
|
361
362
|
|
|
@@ -373,6 +374,196 @@ The component is optimized for:
|
|
|
373
374
|
|
|
374
375
|
## Integration Examples
|
|
375
376
|
|
|
377
|
+
### Line Item Properties
|
|
378
|
+
|
|
379
|
+
The cart panel supports several Shopify line item properties for enhanced functionality:
|
|
380
|
+
|
|
381
|
+
#### Cart Item Filtering (`_hide_in_cart`)
|
|
382
|
+
|
|
383
|
+
Cart items can be hidden from display by setting the `_hide_in_cart` property. Hidden items are excluded from:
|
|
384
|
+
|
|
385
|
+
- Cart item display and rendering
|
|
386
|
+
- Cart count calculations
|
|
387
|
+
- Subtotal calculations
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
// Example: Hide a cart item from display
|
|
391
|
+
{
|
|
392
|
+
"items": [
|
|
393
|
+
{
|
|
394
|
+
"key": "item-123",
|
|
395
|
+
"properties": {
|
|
396
|
+
"_hide_in_cart": "true" // Hide from cart display
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### Custom Templates (`_cart_template`)
|
|
404
|
+
|
|
405
|
+
Different cart item templates can be specified using the `_cart_template` property:
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
// Example: Use different templates for different item types
|
|
409
|
+
{
|
|
410
|
+
"items": [
|
|
411
|
+
{
|
|
412
|
+
"key": "subscription-item",
|
|
413
|
+
"properties": {
|
|
414
|
+
"_cart_template": "subscription" // Use subscription template
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"key": "bundle-item",
|
|
419
|
+
"properties": {
|
|
420
|
+
"_cart_template": "bundle" // Use bundle template
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Then set up custom templates in JavaScript:
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
import { CartItem } from '@magic-spells/cart-panel';
|
|
431
|
+
|
|
432
|
+
// Set up different templates
|
|
433
|
+
CartItem.setTemplate('subscription', (itemData, cartData) => {
|
|
434
|
+
return `
|
|
435
|
+
<div class="subscription-item">
|
|
436
|
+
<div class="recurring-badge">🔄 Subscription</div>
|
|
437
|
+
<h4>${itemData.product_title}</h4>
|
|
438
|
+
<div class="price">$${(itemData.price / 100).toFixed(2)} every month</div>
|
|
439
|
+
<quantity-modifier value="${itemData.quantity}"></quantity-modifier>
|
|
440
|
+
</div>
|
|
441
|
+
`;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
CartItem.setTemplate('bundle', (itemData, cartData) => {
|
|
445
|
+
return `
|
|
446
|
+
<div class="bundle-item">
|
|
447
|
+
<div class="bundle-badge">📦 Bundle Deal</div>
|
|
448
|
+
<h4>${itemData.product_title}</h4>
|
|
449
|
+
<div class="savings">Save 20%!</div>
|
|
450
|
+
<div class="price">$${(itemData.price / 100).toFixed(2)}</div>
|
|
451
|
+
</div>
|
|
452
|
+
`;
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Item Grouping (`_group_id` and `_group_role`)
|
|
457
|
+
|
|
458
|
+
Items can be grouped together using `_group_id` and `_group_role` properties. This is commonly used for bundle products where multiple items should be displayed as a single unit.
|
|
459
|
+
|
|
460
|
+
**Use Cases:**
|
|
461
|
+
- Bundle products (main product + accessories)
|
|
462
|
+
- Gift with purchase promotions
|
|
463
|
+
- Subscription boxes with multiple items
|
|
464
|
+
- Product kits and sets
|
|
465
|
+
|
|
466
|
+
**How it works:**
|
|
467
|
+
1. All items in a group share the same `_group_id` (a unique identifier like a UUID)
|
|
468
|
+
2. One item has `_group_role: "parent"` (typically with `_cart_template: "bundle"`)
|
|
469
|
+
3. Other items have `_group_role: "child"` (typically with `_hide_in_cart: true`)
|
|
470
|
+
4. The bundle template renders all grouped items together in one display
|
|
471
|
+
|
|
472
|
+
**Example usage:**
|
|
473
|
+
```javascript
|
|
474
|
+
// Bundle: T-shirt + Hat + Sticker (shown as one item in cart)
|
|
475
|
+
{
|
|
476
|
+
"items": [
|
|
477
|
+
{
|
|
478
|
+
"key": "bundle-parent",
|
|
479
|
+
"properties": {
|
|
480
|
+
"_group_id": "Q6RT1B48",
|
|
481
|
+
"_group_role": "parent",
|
|
482
|
+
"_cart_template": "bundle"
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"key": "bundle-child-1",
|
|
487
|
+
"properties": {
|
|
488
|
+
"_group_id": "Q6RT1B48",
|
|
489
|
+
"_group_role": "child",
|
|
490
|
+
"_hide_in_cart": "true"
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"key": "bundle-child-2",
|
|
495
|
+
"properties": {
|
|
496
|
+
"_group_id": "Q6RT1B48",
|
|
497
|
+
"_group_role": "child",
|
|
498
|
+
"_hide_in_cart": "true"
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Bundle template example:**
|
|
506
|
+
```javascript
|
|
507
|
+
CartItem.setTemplate('bundle', (itemData, cartData) => {
|
|
508
|
+
// Find all items in this group
|
|
509
|
+
const groupId = itemData.properties._group_id;
|
|
510
|
+
const groupItems = cartData.items.filter(item =>
|
|
511
|
+
item.properties?._group_id === groupId
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
return `
|
|
515
|
+
<div class="bundle-item">
|
|
516
|
+
<div class="bundle-badge">📦 Bundle Deal</div>
|
|
517
|
+
<h4>${itemData.product_title}</h4>
|
|
518
|
+
<div class="bundle-contents">
|
|
519
|
+
${groupItems.map(item => `
|
|
520
|
+
<div class="bundle-item-detail">
|
|
521
|
+
• ${item.product_title} (${item.quantity})
|
|
522
|
+
</div>
|
|
523
|
+
`).join('')}
|
|
524
|
+
</div>
|
|
525
|
+
<div class="bundle-price">$${(groupItems.reduce((sum, item) => sum + item.line_price, 0) / 100).toFixed(2)}</div>
|
|
526
|
+
</div>
|
|
527
|
+
`;
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### Subtotal Exclusion (`_ignore_price_in_subtotal`)
|
|
532
|
+
|
|
533
|
+
Items can be excluded from subtotal calculations using the `_ignore_price_in_subtotal` property. This is useful for promotional items that receive automatic discounts at checkout.
|
|
534
|
+
|
|
535
|
+
**Use Cases:**
|
|
536
|
+
- Gift with purchase items (free items that show $0 at checkout)
|
|
537
|
+
- Promotional items with automatic discounts applied later
|
|
538
|
+
- Service fees handled by other systems
|
|
539
|
+
- Items with complex pricing logic
|
|
540
|
+
|
|
541
|
+
**Usage:**
|
|
542
|
+
```javascript
|
|
543
|
+
// Gift with purchase item - shows in cart but excluded from subtotal
|
|
544
|
+
{
|
|
545
|
+
"key": "gift-item",
|
|
546
|
+
"properties": {
|
|
547
|
+
"_ignore_price_in_subtotal": "true"
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Implementation:**
|
|
553
|
+
The cart panel automatically excludes these items when calculating visible subtotals, but they remain in the cart for Shopify's checkout process where discounts are applied.
|
|
554
|
+
|
|
555
|
+
#### Supported Properties
|
|
556
|
+
|
|
557
|
+
| Property | Purpose | Example Values |
|
|
558
|
+
| -------------------------- | --------------------------------------------- | -------------------------------------- |
|
|
559
|
+
| `_hide_in_cart` | Hide items from cart display | `"true"`, `true` |
|
|
560
|
+
| `_cart_template` | Specify custom template for rendering | `"subscription"`, `"bundle"`, `"gift"` |
|
|
561
|
+
| `_group_id` | Group items together with shared UUID | `"Q6RT1B48"`, `"ABC123XYZ"` |
|
|
562
|
+
| `_group_role` | Role within a group | `"parent"`, `"child"` |
|
|
563
|
+
| `_ignore_price_in_subtotal` | Exclude from subtotal calculations | `"true"`, `true` |
|
|
564
|
+
|
|
565
|
+
These properties follow Shopify's line item properties pattern and are commonly used for gift-with-purchase items, subscription products, bundles, and other special cart items.
|
|
566
|
+
|
|
376
567
|
### Shopify Integration
|
|
377
568
|
|
|
378
569
|
The cart panel automatically integrates with Shopify's AJAX Cart API. Simply add the component to your theme and it handles all cart operations:
|
|
@@ -380,43 +571,43 @@ The cart panel automatically integrates with Shopify's AJAX Cart API. Simply add
|
|
|
380
571
|
```html
|
|
381
572
|
<!-- In your Shopify theme layout -->
|
|
382
573
|
<button
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
574
|
+
aria-haspopup="dialog"
|
|
575
|
+
aria-controls="shopify-cart"
|
|
576
|
+
aria-expanded="false"
|
|
577
|
+
class="cart-trigger">
|
|
578
|
+
Cart ({{ cart.item_count }})
|
|
388
579
|
</button>
|
|
389
580
|
|
|
390
581
|
<cart-dialog id="shopify-cart" aria-labelledby="cart-heading">
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
582
|
+
<cart-panel>
|
|
583
|
+
<header class="cart-header">
|
|
584
|
+
<h2 id="cart-heading">Your Cart</h2>
|
|
585
|
+
<button data-action="hide-cart" aria-label="hide cart">X</button>
|
|
586
|
+
</header>
|
|
587
|
+
|
|
588
|
+
<div class="cart-content">
|
|
589
|
+
<!-- Cart items will be populated automatically in javascript -->
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<footer class="cart-footer">
|
|
593
|
+
<div class="cart-total"></div>
|
|
594
|
+
<a href="/checkout" class="button"> Checkout </a>
|
|
595
|
+
</footer>
|
|
596
|
+
</cart-panel>
|
|
406
597
|
</cart-dialog>
|
|
407
598
|
|
|
408
599
|
<script>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
600
|
+
// Optional: Listen for cart updates to sync with other UI elements
|
|
601
|
+
document.querySelector('cart-dialog').on('cart-dialog:data-changed', (cartData) => {
|
|
602
|
+
// Update cart count in header
|
|
603
|
+
document.querySelector('.cart-trigger').textContent = `Cart (${cartData.item_count})`;
|
|
604
|
+
|
|
605
|
+
// Update cart total
|
|
606
|
+
document.querySelector('[data-cart-total]').textContent = new Intl.NumberFormat('en-US', {
|
|
607
|
+
style: 'currency',
|
|
608
|
+
currency: 'USD',
|
|
609
|
+
}).format(cartData.total_price / 100);
|
|
610
|
+
});
|
|
420
611
|
</script>
|
|
421
612
|
```
|
|
422
613
|
|
|
@@ -425,63 +616,63 @@ The cart panel automatically integrates with Shopify's AJAX Cart API. Simply add
|
|
|
425
616
|
```javascript
|
|
426
617
|
// Example for non-Shopify platforms
|
|
427
618
|
class CustomCartManager {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
619
|
+
constructor() {
|
|
620
|
+
this.cartDialog = document.querySelector('cart-dialog');
|
|
621
|
+
this.setupEventListeners();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
setupEventListeners() {
|
|
625
|
+
// Listen for cart data changes
|
|
626
|
+
this.cartDialog.on('cart-dialog:data-changed', (cartData) => {
|
|
627
|
+
this.updateCartUI(cartData);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// Override default cart operations for custom API
|
|
631
|
+
this.cartDialog.getCart = this.customGetCart.bind(this);
|
|
632
|
+
this.cartDialog.updateCartItem = this.customUpdateCartItem.bind(this);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async customGetCart() {
|
|
636
|
+
try {
|
|
637
|
+
const response = await fetch('/api/cart');
|
|
638
|
+
return await response.json();
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.error('Failed to fetch cart:', error);
|
|
641
|
+
return { error: true, message: error.message };
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async customUpdateCartItem(itemId, quantity) {
|
|
646
|
+
try {
|
|
647
|
+
const response = await fetch('/api/cart/update', {
|
|
648
|
+
method: 'POST',
|
|
649
|
+
headers: { 'Content-Type': 'application/json' },
|
|
650
|
+
body: JSON.stringify({ itemId, quantity }),
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
if (!response.ok) throw new Error(response.statusText);
|
|
654
|
+
|
|
655
|
+
// Return updated cart data
|
|
656
|
+
return this.customGetCart();
|
|
657
|
+
} catch (error) {
|
|
658
|
+
console.error('Failed to update cart:', error);
|
|
659
|
+
return { error: true, message: error.message };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
updateCartUI(cartData) {
|
|
664
|
+
// Update cart count in navigation
|
|
665
|
+
const cartCount = document.querySelector('.cart-count');
|
|
666
|
+
if (cartCount) {
|
|
667
|
+
cartCount.textContent = cartData.items?.length || 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Update cart total display
|
|
671
|
+
const cartTotal = document.querySelector('.cart-total-display');
|
|
672
|
+
if (cartTotal && cartData.total) {
|
|
673
|
+
cartTotal.textContent = cartData.total;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
485
676
|
}
|
|
486
677
|
|
|
487
678
|
// Initialize
|