@runwell/shopify-toolkit 0.18.0 → 0.21.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/bin/runwell-shopify +10 -1
- package/lib/list.js +22 -9
- package/lib/qa-bundles.js +117 -0
- package/lib/qa.js +147 -13
- package/modules/INDEX.md +14 -5
- package/modules/bundle-builder/README.md +6 -1
- package/modules/bundle-builder/module.json +5 -1
- package/modules/cart-cross-sell/snippets/runwell-cart-xsell.liquid +16 -0
- package/modules/runwell-bundle-system/README.md +35 -0
- package/modules/runwell-bundle-system/admin-metafields.json +46 -0
- package/modules/runwell-bundle-system/assets/runwell-bundle-system.css +861 -0
- package/modules/runwell-bundle-system/assets/runwell-bundle-system.js +287 -0
- package/modules/runwell-bundle-system/module.json +126 -0
- package/modules/runwell-bundle-system/qa/mobile-checklist.md +105 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-cart-xsell.liquid +59 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-collection.liquid +121 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-home-stacks.liquid +77 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-banner.liquid +50 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-pairs-with.liquid +72 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp.liquid +105 -0
- package/modules/runwell-bundle-system/settings.json +25 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-card.liquid +70 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-cross-supplier.liquid +18 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-data.liquid +67 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-fomo.liquid +32 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-free-gift.liquid +34 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-multi-product.liquid +86 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-pricing.liquid +30 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-quantity-tiers.liquid +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-free-gift.liquid.
|
|
3
|
+
Wraps the gift-with-purchase module. The bundle's free-gift handle is
|
|
4
|
+
passed in. Display only here; the actual auto-add to cart logic lives
|
|
5
|
+
in the gift-with-purchase module's runwell-gwp.js.
|
|
6
|
+
|
|
7
|
+
Inputs:
|
|
8
|
+
bundle_free_gift_handle (product handle of the gift)
|
|
9
|
+
{%- endcomment -%}
|
|
10
|
+
|
|
11
|
+
{%- if bundle_free_gift_handle != blank -%}
|
|
12
|
+
{%- assign gift_product = all_products[bundle_free_gift_handle] -%}
|
|
13
|
+
{%- if gift_product != blank -%}
|
|
14
|
+
<div class="runwell-bundle-system__free-gift" data-runwell-bundle-free-gift data-gift-handle="{{ bundle_free_gift_handle }}">
|
|
15
|
+
<p class="runwell-bundle-system__free-gift-label">Plus a free gift:</p>
|
|
16
|
+
<div class="runwell-bundle-system__free-gift-product">
|
|
17
|
+
{%- if gift_product.featured_image -%}
|
|
18
|
+
{{ gift_product.featured_image | image_url: width: 80 | image_tag:
|
|
19
|
+
width: 60,
|
|
20
|
+
height: 60,
|
|
21
|
+
loading: 'lazy',
|
|
22
|
+
class: 'runwell-bundle-system__free-gift-thumb',
|
|
23
|
+
alt: gift_product.title
|
|
24
|
+
}}
|
|
25
|
+
{%- endif -%}
|
|
26
|
+
<span class="runwell-bundle-system__free-gift-name">{{ gift_product.title }}</span>
|
|
27
|
+
<span class="runwell-bundle-system__free-gift-price">
|
|
28
|
+
<s>{{ gift_product.price | money }}</s>
|
|
29
|
+
<strong>Free</strong>
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
{%- endif -%}
|
|
34
|
+
{%- endif -%}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-multi-product.liquid.
|
|
3
|
+
Mode B render. Lists the bundle's component products (thumbnail, qty,
|
|
4
|
+
name, individual price), the bundle pricing block (delegated to
|
|
5
|
+
runwell-bundle-pricing), and the single Add to Cart button that adds
|
|
6
|
+
the bundle product as one line item.
|
|
7
|
+
|
|
8
|
+
Inputs:
|
|
9
|
+
product
|
|
10
|
+
bundle_pricing_model
|
|
11
|
+
bundle_components (array of {product_handle, qty})
|
|
12
|
+
bundle_subtotal (sum of component prices)
|
|
13
|
+
bundle_price (the bundle product's price)
|
|
14
|
+
bundle_savings_amount
|
|
15
|
+
bundle_savings_pct
|
|
16
|
+
{%- endcomment -%}
|
|
17
|
+
|
|
18
|
+
{% render 'runwell-bundle-pricing',
|
|
19
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
20
|
+
bundle_price: bundle_price,
|
|
21
|
+
bundle_subtotal: bundle_subtotal,
|
|
22
|
+
bundle_savings_amount: bundle_savings_amount,
|
|
23
|
+
bundle_savings_pct: bundle_savings_pct
|
|
24
|
+
%}
|
|
25
|
+
|
|
26
|
+
<div class="runwell-bundle-system__components">
|
|
27
|
+
<p class="runwell-bundle-system__components-heading">Includes:</p>
|
|
28
|
+
<ul class="runwell-bundle-system__components-list" role="list">
|
|
29
|
+
{%- if bundle_components and bundle_components.size > 0 -%}
|
|
30
|
+
{%- for component in bundle_components -%}
|
|
31
|
+
{%- assign comp_product = all_products[component.product_handle] -%}
|
|
32
|
+
{%- assign comp_qty = component.qty | default: 1 -%}
|
|
33
|
+
<li class="runwell-bundle-system__component">
|
|
34
|
+
{%- if comp_product != blank and comp_product.featured_image -%}
|
|
35
|
+
{{ comp_product.featured_image | image_url: width: 120 | image_tag:
|
|
36
|
+
width: 60,
|
|
37
|
+
height: 60,
|
|
38
|
+
loading: 'lazy',
|
|
39
|
+
class: 'runwell-bundle-system__component-thumb',
|
|
40
|
+
alt: comp_product.title
|
|
41
|
+
}}
|
|
42
|
+
{%- else -%}
|
|
43
|
+
<span class="runwell-bundle-system__component-thumb runwell-bundle-system__component-thumb--placeholder" aria-hidden="true"></span>
|
|
44
|
+
{%- endif -%}
|
|
45
|
+
|
|
46
|
+
<span class="runwell-bundle-system__component-qty">{{ comp_qty }}x</span>
|
|
47
|
+
|
|
48
|
+
<span class="runwell-bundle-system__component-name">
|
|
49
|
+
{%- if comp_product != blank -%}
|
|
50
|
+
{{ comp_product.title }}
|
|
51
|
+
{%- else -%}
|
|
52
|
+
<em>(unavailable)</em>
|
|
53
|
+
{%- endif -%}
|
|
54
|
+
</span>
|
|
55
|
+
|
|
56
|
+
{%- if comp_product != blank -%}
|
|
57
|
+
<span class="runwell-bundle-system__component-price">{{ comp_product.price | money }}</span>
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
</li>
|
|
60
|
+
{%- endfor -%}
|
|
61
|
+
{%- else -%}
|
|
62
|
+
<li class="runwell-bundle-system__component runwell-bundle-system__component--empty">
|
|
63
|
+
<em>No components configured. Set the runwell.bundle_components metafield on this bundle product.</em>
|
|
64
|
+
</li>
|
|
65
|
+
{%- endif -%}
|
|
66
|
+
</ul>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{%- form 'product', product, class: 'runwell-bundle-system__form', novalidate: 'novalidate' -%}
|
|
70
|
+
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
|
|
71
|
+
<input type="hidden" name="quantity" value="1">
|
|
72
|
+
<button
|
|
73
|
+
type="submit"
|
|
74
|
+
name="add"
|
|
75
|
+
class="runwell-bundle-system__atc button button--primary button--full-width"
|
|
76
|
+
{% if product.available == false %}disabled{% endif %}
|
|
77
|
+
>
|
|
78
|
+
<span class="runwell-bundle-system__atc-label">
|
|
79
|
+
{%- if product.available -%}
|
|
80
|
+
Add bundle to cart
|
|
81
|
+
{%- else -%}
|
|
82
|
+
Sold out
|
|
83
|
+
{%- endif -%}
|
|
84
|
+
</span>
|
|
85
|
+
</button>
|
|
86
|
+
{%- endform -%}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-pricing.liquid.
|
|
3
|
+
Shared price + strikethrough subtotal + savings badge. Used by Mode B
|
|
4
|
+
(multi-product) and bundle cards on Surfaces 1 to 5.
|
|
5
|
+
|
|
6
|
+
Inputs:
|
|
7
|
+
bundle_price (the bundle product's selling price, in cents)
|
|
8
|
+
bundle_subtotal (sum of component prices, in cents; 0 if not multi-product)
|
|
9
|
+
bundle_savings_amount (subtotal - price, in cents; 0 if no savings)
|
|
10
|
+
bundle_savings_pct (savings_amount / subtotal * 100; 0 if no savings)
|
|
11
|
+
bundle_pricing_model (used to choose dollar vs percent badge format)
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
|
|
14
|
+
<div class="runwell-bundle-system__pricing">
|
|
15
|
+
<span class="runwell-bundle-system__pricing-current">{{ bundle_price | money }}</span>
|
|
16
|
+
|
|
17
|
+
{%- if bundle_subtotal > bundle_price -%}
|
|
18
|
+
<span class="runwell-bundle-system__pricing-strikethrough">{{ bundle_subtotal | money }}</span>
|
|
19
|
+
{%- endif -%}
|
|
20
|
+
|
|
21
|
+
{%- if bundle_savings_amount > 0 -%}
|
|
22
|
+
<span class="runwell-bundle-system__pricing-badge">
|
|
23
|
+
{%- if bundle_pricing_model == 'percent_off_subtotal' -%}
|
|
24
|
+
Save {{ bundle_savings_pct }}%
|
|
25
|
+
{%- else -%}
|
|
26
|
+
Save {{ bundle_savings_amount | money_without_trailing_zeros }}
|
|
27
|
+
{%- endif -%}
|
|
28
|
+
</span>
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-quantity-tiers.liquid.
|
|
3
|
+
Mode A radio picker. Renders one row per tier in bundle_quantity_tiers
|
|
4
|
+
metafield. Default selection is the highest discount_pct tier (last in
|
|
5
|
+
the array, by convention). Display only; the discount itself is applied
|
|
6
|
+
by a Shopify Function (see module.json admin_steps:
|
|
7
|
+
configure-quantity-tier-discount-function).
|
|
8
|
+
|
|
9
|
+
Inputs (passed via render):
|
|
10
|
+
product
|
|
11
|
+
bundle_pricing_model
|
|
12
|
+
bundle_quantity_tiers (array of {qty, discount_pct})
|
|
13
|
+
section
|
|
14
|
+
{%- endcomment -%}
|
|
15
|
+
|
|
16
|
+
{%- if bundle_quantity_tiers and bundle_quantity_tiers.size > 0 -%}
|
|
17
|
+
{%- assign default_tier_index = bundle_quantity_tiers.size | minus: 1 -%}
|
|
18
|
+
|
|
19
|
+
{%- form 'product', product, class: 'runwell-bundle-system__form', novalidate: 'novalidate' -%}
|
|
20
|
+
<fieldset class="runwell-bundle-system__tiers" aria-label="Choose quantity">
|
|
21
|
+
<legend class="visually-hidden">Choose quantity</legend>
|
|
22
|
+
{%- for tier in bundle_quantity_tiers -%}
|
|
23
|
+
{%- assign discount_amount = product.price | times: tier.discount_pct | divided_by: 100 -%}
|
|
24
|
+
{%- assign per_unit_price = product.price | minus: discount_amount -%}
|
|
25
|
+
{%- assign total_price = per_unit_price | times: tier.qty -%}
|
|
26
|
+
{%- assign full_price = product.price | times: tier.qty -%}
|
|
27
|
+
{%- assign savings = full_price | minus: total_price -%}
|
|
28
|
+
|
|
29
|
+
<label class="runwell-bundle-system__tier" data-qty="{{ tier.qty }}" data-discount-pct="{{ tier.discount_pct }}">
|
|
30
|
+
<input
|
|
31
|
+
type="radio"
|
|
32
|
+
name="quantity"
|
|
33
|
+
value="{{ tier.qty }}"
|
|
34
|
+
class="runwell-bundle-system__tier-input"
|
|
35
|
+
{% if forloop.index0 == default_tier_index %}checked{% endif %}
|
|
36
|
+
>
|
|
37
|
+
<span class="runwell-bundle-system__tier-row">
|
|
38
|
+
<span class="runwell-bundle-system__tier-qty">{{ tier.qty }}{% if tier.qty == 1 %} unit{% else %} units{% endif %}</span>
|
|
39
|
+
{%- if tier.discount_pct > 0 -%}
|
|
40
|
+
<span class="runwell-bundle-system__tier-savings-badge">Save {{ tier.discount_pct }}%</span>
|
|
41
|
+
{%- endif -%}
|
|
42
|
+
</span>
|
|
43
|
+
<span class="runwell-bundle-system__tier-row runwell-bundle-system__tier-row--prices">
|
|
44
|
+
<span class="runwell-bundle-system__tier-total">{{ total_price | money }}</span>
|
|
45
|
+
{%- if tier.discount_pct > 0 -%}
|
|
46
|
+
<span class="runwell-bundle-system__tier-strikethrough">{{ full_price | money }}</span>
|
|
47
|
+
{%- endif -%}
|
|
48
|
+
<span class="runwell-bundle-system__tier-per-unit">{{ per_unit_price | money }} / unit</span>
|
|
49
|
+
</span>
|
|
50
|
+
</label>
|
|
51
|
+
{%- endfor -%}
|
|
52
|
+
</fieldset>
|
|
53
|
+
|
|
54
|
+
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
|
|
55
|
+
|
|
56
|
+
<button
|
|
57
|
+
type="submit"
|
|
58
|
+
name="add"
|
|
59
|
+
class="runwell-bundle-system__atc button button--primary button--full-width"
|
|
60
|
+
{% if product.selected_or_first_available_variant.available == false %}disabled{% endif %}
|
|
61
|
+
>
|
|
62
|
+
<span class="runwell-bundle-system__atc-label">
|
|
63
|
+
{%- if product.selected_or_first_available_variant.available -%}
|
|
64
|
+
Add to cart
|
|
65
|
+
{%- else -%}
|
|
66
|
+
Sold out
|
|
67
|
+
{%- endif -%}
|
|
68
|
+
</span>
|
|
69
|
+
</button>
|
|
70
|
+
{%- endform -%}
|
|
71
|
+
{%- else -%}
|
|
72
|
+
<p class="runwell-bundle-system__empty">No quantity tiers configured for this bundle.</p>
|
|
73
|
+
{%- endif -%}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runwell/shopify-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Reusable Shopify theme modules from Runwell. Replaces typically app-driven features (reviews, wishlist, urgency, FAQ, post-purchase upsell, exit popups, free-ship progress, sticky ATC, testimonials, badges, bundles) with native Liquid + JS + CSS that ship across multiple client themes via a config-driven sync CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|