zz-shopify-components 0.36.1-beta.7 → 0.36.1-beta.9

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.
@@ -314,8 +314,22 @@ class BuyNowBottomBar extends HTMLElement {
314
314
  // 计算折扣
315
315
  const discountEl = this.querySelectorAll('.price-discouter');
316
316
  if (discountEl.length > 0) {
317
- const percent = total_price.times(100).dividedBy(total_before);
318
- const discountPercent = 100 - percent;
317
+ const discountPercent = total_before
318
+ .minus(total_price)
319
+ .times(100)
320
+ .dividedBy(total_before)
321
+ .toNumber();
322
+ const percentRound = this.dataset.percentRound || 'floor';
323
+ let roundedDiscountPercent = discountPercent;
324
+
325
+ if (percentRound === 'round') {
326
+ roundedDiscountPercent = Math.round(discountPercent);
327
+ } else if (percentRound === 'ceil') {
328
+ roundedDiscountPercent = Math.ceil(discountPercent);
329
+ } else {
330
+ roundedDiscountPercent = Math.floor(discountPercent);
331
+ }
332
+
319
333
  const discountValue = total_before.minus(total_price);
320
334
 
321
335
  discountEl.forEach((el) => {
@@ -323,7 +337,7 @@ class BuyNowBottomBar extends HTMLElement {
323
337
  el.classList.remove('tw-hidden');
324
338
  el.textContent =
325
339
  this.dataset.discountType === 'percent'
326
- ? '- ' + Math.round(discountPercent) + '%'
340
+ ? '- ' + roundedDiscountPercent + '%'
327
341
  : 'Save ' + this.currency + discountValue;
328
342
  } else {
329
343
  el.classList.add('tw-hidden');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zz-shopify-components",
3
- "version": "0.36.1-beta.7",
3
+ "version": "0.36.1-beta.9",
4
4
  "description": "Reusable Shopify components for theme projects",
5
5
  "keywords": [
6
6
  "shopify",
@@ -28,6 +28,17 @@
28
28
  ],
29
29
  "default": "percent"
30
30
  },
31
+ {
32
+ "type": "select",
33
+ "id": "percent_round",
34
+ "label": "购买区折扣百分比取整方式",
35
+ "options": [
36
+ { "value": "floor", "label": "向下取整" },
37
+ { "value": "round", "label": "四舍五入" },
38
+ { "value": "ceil", "label": "向上取整" }
39
+ ],
40
+ "default": "floor"
41
+ },
31
42
  {
32
43
  "type": "select",
33
44
  "id": "bar_discount_type",
@@ -38,6 +49,17 @@
38
49
  ],
39
50
  "default": "percent"
40
51
  },
52
+ {
53
+ "type": "select",
54
+ "id": "bar_percent_round",
55
+ "label": "底部Bar折扣百分比取整方式",
56
+ "options": [
57
+ { "value": "floor", "label": "向下取整" },
58
+ { "value": "round", "label": "四舍五入" },
59
+ { "value": "ceil", "label": "向上取整" }
60
+ ],
61
+ "default": "floor"
62
+ },
41
63
  {
42
64
  "type": "header",
43
65
  "content": "Main Settings"
@@ -229,6 +251,7 @@
229
251
  blocks: section.blocks,
230
252
  show_basic: section.settings.show_basic_price,
231
253
  discount_type: section.settings.discount_type,
254
+ percent_round: section.settings.percent_round,
232
255
  basic_title: section.settings.basic_title
233
256
  %}
234
257
  </div>
@@ -250,6 +273,7 @@
250
273
  btn_out_of_stock: section.settings.bar_btn_out_of_stock,
251
274
  product_title: section.settings.title,
252
275
  discount_type: section.settings.bar_discount_type,
276
+ percent_round: section.settings.bar_percent_round,
253
277
  %}
254
278
 
255
279
 
@@ -49,6 +49,26 @@
49
49
  "label": "白色"
50
50
  }
51
51
  ]
52
+ },
53
+ {
54
+ "type": "checkbox",
55
+ "id": "show_active_underline",
56
+ "label": "是否下划线",
57
+ "default": true,
58
+ "info": "开启时,选中的 Tab 会显示下划线"
59
+ },
60
+ {
61
+ "type": "checkbox",
62
+ "id": "show_divider",
63
+ "label": "是否有中间线",
64
+ "default": false,
65
+ "info": "开启时,Tab 之间显示分隔中间线"
66
+ },
67
+ {
68
+ "type": "checkbox",
69
+ "id": "smooth_scroll",
70
+ "label": "是否平滑滚动",
71
+ "default": false,
52
72
  }
53
73
  ],
54
74
  "blocks": [
@@ -87,15 +107,19 @@
87
107
 
88
108
  {%- assign top_offset = section.settings.top_offset | default: 50 -%}
89
109
  {%- assign text_color_mode = section.settings.text_color_mode | default: 'black' -%}
110
+ {%- assign show_active_underline = section.settings.show_active_underline -%}
111
+ {%- assign show_divider = section.settings.show_divider -%}
90
112
  {%- if text_color_mode == 'white' -%}
91
113
  {%- assign tab_text_color = 'rgba(255,255,255,0.8)' -%}
92
114
  {%- assign tab_active_text_color = '#FFFFFF' -%}
115
+ {%- assign divider_color = 'rgba(255,255,255,0.6)' -%}
93
116
  {%- else -%}
94
117
  {%- assign tab_text_color = 'rgba(45,45,45,0.8)' -%}
95
118
  {%- assign tab_active_text_color = '#2D2D2D' -%}
119
+ {%- assign divider_color = 'rgba(45,45,45,0.6)' -%}
96
120
  {%- endif -%}
97
121
 
98
- <nav class="zz-product-subnav" aria-label="Product sub navigation">
122
+ <nav class="zz-product-subnav{% if show_active_underline %} zz-product-subnav--underline{% endif %}{% if show_divider %} zz-product-subnav--divider{% endif %}" aria-label="Product sub navigation">
99
123
  <div class="zz-product-subnav__wrap">
100
124
  <div class="zz-product-subnav__track" role="tablist">
101
125
  {%- for block in section.blocks -%}
@@ -123,7 +147,7 @@
123
147
  #shopify-section-{{ section.id }} {
124
148
  position: sticky;
125
149
  top: {{ top_offset }}px;
126
- z-index: 30;
150
+ z-index: 501;
127
151
  }
128
152
 
129
153
  #shopify-section-{{ section.id }} .zz-product-subnav {
@@ -178,7 +202,7 @@
178
202
  white-space: nowrap;
179
203
  }
180
204
 
181
- #shopify-section-{{ section.id }} .zz-product-subnav__tab.is-active {
205
+ #shopify-section-{{ section.id }} .zz-product-subnav--underline .zz-product-subnav__tab.is-active {
182
206
  border-bottom-color: {{ tab_active_text_color }};
183
207
  }
184
208
 
@@ -187,6 +211,21 @@
187
211
  color: {{ tab_active_text_color }};
188
212
  }
189
213
 
214
+ #shopify-section-{{ section.id }} .zz-product-subnav--divider .zz-product-subnav__tab {
215
+ position: relative;
216
+ }
217
+
218
+ #shopify-section-{{ section.id }} .zz-product-subnav--divider .zz-product-subnav__tab:not(:first-child)::before {
219
+ content: "";
220
+ position: absolute;
221
+ top: 50%;
222
+ left: -30px;
223
+ transform: translateY(-50%);
224
+ width: 1px;
225
+ height: 13px;
226
+ background: {{ divider_color }};
227
+ }
228
+
190
229
  @media (max-width: 1023px) {
191
230
  #shopify-section-{{ section.id }} .zz-product-subnav__wrap {
192
231
  padding: 0 20px;
@@ -204,7 +243,16 @@
204
243
  #shopify-section-{{ section.id }} .zz-product-subnav__tabText {
205
244
  font-size: 14px;
206
245
  }
246
+
247
+ #shopify-section-{{ section.id }} .zz-product-subnav--divider .zz-product-subnav__tab:not(:first-child)::before {
248
+ left: -12px;
249
+ }
207
250
  }
251
+ {% if section.settings.smooth_scroll == false %}
252
+ html {
253
+ scroll-behavior: auto;
254
+ }
255
+ {% endif %}
208
256
  </style>
209
257
 
210
258
  <script>
@@ -270,7 +318,11 @@
270
318
  if (!target) return;
271
319
  const rect = target.getBoundingClientRect();
272
320
  const offsetTop = rect.top + window.scrollY - getScrollOffset();
273
- window.scrollTo({ top: offsetTop, behavior: 'smooth' });
321
+ {% if section.settings.smooth_scroll == false %}
322
+ window.scrollTo(0, offsetTop);
323
+ {% else %}
324
+ window.scrollTo({ top: offsetTop, behavior: 'smooth' });
325
+ {% endif %}
274
326
  }
275
327
 
276
328
  // Click to scroll
@@ -361,6 +413,10 @@
361
413
  }
362
414
 
363
415
  function tryStartSpyWithWait() {
416
+ if (window.USER_AGENT && window.USER_AGENT.isApp) {
417
+ // top 设置成 top_offset-50px
418
+ sectionEl.style.top = '0px';
419
+ }
364
420
  if (startSpy()) return;
365
421
 
366
422
  // Targets not in DOM yet: wait for insertion
@@ -21,7 +21,7 @@
21
21
  >
22
22
  <div class="tw-box-border tw-mx-auto tw-max-w-[1220px] lg:tw-py-[80px] max-lg:tw-pt-[48px] max-lg:tw-pb-[30px]">
23
23
  {%- if section.settings.title != blank -%}
24
- <div class="tw-mb-[3rem] lg:tw-mb-[100px] tw-text-center tw-font-bold tw-text-[#2d2d2d] tw-leading-[1] tw-text-[2.8rem] lg:tw-text-[40px]">
24
+ <div class="tw-mb-[3rem] lg:tw-mb-[100px] max-lg:tw-px-[2rem] tw-text-center tw-font-bold tw-text-[#2d2d2d] tw-leading-[1] tw-text-[2.8rem] lg:tw-text-[40px]">
25
25
  {{ section.settings.title | escape }}
26
26
  </div>
27
27
  {%- endif -%}
@@ -190,15 +190,21 @@
190
190
 
191
191
  var isVisible = false;
192
192
  var ticking = false;
193
+ var lastNavOffset = -1;
193
194
 
194
- {%- comment -%} Height of the visible content-navigation bar (mobile vs pc) {%- endcomment -%}
195
- function getNavHeight() {
196
- var pc = document.querySelector('.content-navigation-pc');
197
- var mobile = document.querySelector('.content-navigation-mobile');
198
- {%- comment -%} the hidden one (wrong breakpoint) reports offsetHeight 0 {%- endcomment -%}
199
- var pcH = pc ? pc.offsetHeight : 0;
200
- var mobileH = mobile ? mobile.offsetHeight : 0;
201
- return Math.max(pcH, mobileH, 0);
195
+ {%- comment -%}
196
+ Offset of the floating head.
197
+ 没有 content-nagivation--visible 元素时贴顶 0px;
198
+ 存在时取导航条实际高度(PC / 移动端), 取不到则 PC 40px / 移动端 80px。
199
+ {%- endcomment -%}
200
+ function getNavOffset() {
201
+ var visibleNav = document.querySelector('.content-nagivation--visible');
202
+ if (!visibleNav) { return 0; }
203
+ var isDesktop = window.innerWidth > 1024;
204
+ var el = document.querySelector(isDesktop ? '.content-nagivation-pc' : '.content-nagivation-mobile');
205
+ var h = el ? el.offsetHeight : 0;
206
+ if (h) { return h; }
207
+ return isDesktop ? 40 : 80;
202
208
  }
203
209
 
204
210
  {%- comment -%} Match float bar geometry to the scroller (width, left, padding, column sizing) {%- endcomment -%}
@@ -209,7 +215,7 @@
209
215
  var padRight = parseFloat(cs.paddingRight) || 0;
210
216
 
211
217
  floatEl.style.left = scRect.left + 'px';
212
- floatEl.style.top = getNavHeight() + 'px';
218
+ floatEl.style.top = getNavOffset() + 'px';
213
219
  floatEl.style.width = scRect.width + 'px';
214
220
  floatInner.style.paddingLeft = padLeft + 'px';
215
221
  floatInner.style.paddingRight = padRight + 'px';
@@ -240,16 +246,23 @@
240
246
 
241
247
  function update() {
242
248
  ticking = false;
243
- var navH = getNavHeight();
249
+ var navOffset = getNavOffset();
244
250
  var headRect = header.getBoundingClientRect();
245
251
  var contentRect = content.getBoundingClientRect();
246
252
  var floatH = floatEl.offsetHeight || headRect.height;
247
253
 
248
254
  {%- comment -%} show once product row scrolls under the nav, hide when content bottom passes {%- endcomment -%}
249
- var shouldShow = headRect.bottom <= navH && contentRect.bottom > navH + floatH;
255
+ var shouldShow = headRect.bottom <= navOffset && contentRect.bottom > navOffset + floatH;
256
+
257
+ {%- comment -%} nav offset changed (navigation shown/hidden or breakpoint switch): re-sync {%- endcomment -%}
258
+ if (isVisible && navOffset !== lastNavOffset) {
259
+ lastNavOffset = navOffset;
260
+ syncLayout();
261
+ }
250
262
 
251
263
  if (shouldShow && !isVisible) {
252
264
  isVisible = true;
265
+ lastNavOffset = navOffset;
253
266
  syncLayout();
254
267
  syncScrollX();
255
268
  floatEl.classList.add('is-visible');
@@ -302,7 +315,7 @@
302
315
  {
303
316
  "type": "select",
304
317
  "id": "from_type",
305
- "label": "文案颜色模式",
318
+ "label": "价格前后缀",
306
319
  "default": "prefix",
307
320
  "options": [
308
321
  {
@@ -318,7 +331,7 @@
318
331
  {
319
332
  "type": "text",
320
333
  "id": "price_prefix",
321
- "label": "Price prefix suffix",
334
+ "label": "Price prefix",
322
335
  "default": "from"
323
336
  }
324
337
  ],
@@ -18,6 +18,7 @@
18
18
  data-currency='{{ cart.currency.symbol }}'
19
19
  data-btn-text='{{ btn }}'
20
20
  data-discount-type='{{ discount_type }}'
21
+ data-percent-round='{{ percent_round | default: "floor" }}'
21
22
  data-btn-out-of-stock-text='{{ btn_out_of_stock }}'
22
23
  data-care-guide='{{ care_guide_type }}'
23
24
  data-is-edu-product='{{ is_edu_product }}'
@@ -50,7 +51,8 @@
50
51
  {% render 'zz-price-discouter',
51
52
  before: product.selected_or_first_available_variant.compare_at_price,
52
53
  after: product.selected_or_first_available_variant.price,
53
- discount_type: discount_type
54
+ discount_type: discount_type,
55
+ percent_round: percent_round
54
56
  %}
55
57
  </div>
56
58
  <div class='bar-title-desc max-md:tw-mb-[12px]'>
@@ -98,7 +100,8 @@
98
100
  {% render 'zz-price-discouter',
99
101
  before: product.selected_or_first_available_variant.compare_at_price,
100
102
  after: product.selected_or_first_available_variant.price,
101
- discount_type: discount_type
103
+ discount_type: discount_type,
104
+ percent_round: percent_round
102
105
  %}
103
106
  </div>
104
107
  {% if has_installment %}
@@ -1,7 +1,18 @@
1
+ {% comment %}
2
+ percent_round: floor (default) | round | ceil — 对折扣百分比 (before-after)/before*100 取整
3
+ {% endcomment %}
1
4
  {% assign discount_value = before | minus: after %}
2
5
  {% assign discount_value = discount_value | money_without_trailing_zeros %}
3
- {%- assign percent = after | times: 100.0 | divided_by: before | round -%}
4
- {%- assign percent = 100 | minus: percent -%}
6
+ {% assign percent_round = percent_round | default: 'floor' %}
7
+ {% assign percent = before | minus: after | times: 100.0 | divided_by: before %}
8
+ {% case percent_round %}
9
+ {% when 'round' %}
10
+ {% assign percent = percent | round %}
11
+ {% when 'ceil' %}
12
+ {% assign percent = percent | ceil %}
13
+ {% else %}
14
+ {% assign percent = percent | floor %}
15
+ {% endcase %}
5
16
  <span
6
17
  class='discount_percentage tw-whitespace-nowrap {% if before == blank or before <= after %} tw-hidden {% endif %} price-discouter lg:tw-px-[8px] tw-px-[4px] tw-inline-flex tw-justify-center tw-leading-[19px] tw-items-center tw-h-[19px] tw-text-[10px] tw-rounded-lg tw-text-white tw-bg-[#FC6C0F]'
7
18
  >
@@ -37,7 +37,8 @@
37
37
  option_type: option_type,
38
38
  show_basic: show_basic,
39
39
  basic_title: basic_title,
40
- discount_type: discount_type
40
+ discount_type: discount_type,
41
+ percent_round: percent_round
41
42
  %}
42
43
  </div>
43
44
  {% else %}
@@ -72,13 +72,21 @@
72
72
  opacity 0.25s ease,
73
73
  transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
74
74
  }
75
- .product-swiper-container:hover .product-swiper-list-thumbs {
76
- opacity: 1;
77
- transform: translateY(0) scale(1);
75
+ /* 触屏上 :hover 会粘滞,会导致滑动后分页点被隐藏,故只对鼠标设备生效 */
76
+ @media (hover: hover) and (pointer: fine) {
77
+ .product-swiper-container:hover .product-swiper-list-thumbs {
78
+ opacity: 1;
79
+ transform: translateY(0) scale(1);
80
+ }
81
+ .product-swiper-container:hover .swiper-pagination {
82
+ display: none;
83
+ transition: none;
84
+ }
78
85
  }
79
- .product-swiper-container:hover .swiper-pagination {
80
- display: none;
81
- transition: none;
86
+ @media (hover: none) {
87
+ .product-swiper-list-thumbs {
88
+ pointer-events: none;
89
+ }
82
90
  }
83
91
 
84
92
  .product-swiper-list-thumbs .swiper-slide {
@@ -94,7 +94,8 @@
94
94
  {% render 'zz-price-discouter',
95
95
  before: variant_for_value.compare_at_price,
96
96
  after: variant_for_value.price,
97
- discount_type: section.settings.discount_type
97
+ discount_type: section.settings.discount_type,
98
+ percent_round: section.settings.percent_round
98
99
  %}
99
100
  {% endif %}
100
101
  </div>
@@ -110,7 +110,8 @@
110
110
  {% render 'zz-price-discouter',
111
111
  before: variant_for_value.compare_at_price,
112
112
  after: variant_for_value.price,
113
- discount_type: discount_type
113
+ discount_type: discount_type,
114
+ percent_round: percent_round
114
115
  %}
115
116
  {% endif %}
116
117
  </div>
@@ -110,7 +110,8 @@
110
110
  {% render 'zz-price-discouter',
111
111
  before: variant_for_value.compare_at_price,
112
112
  after: variant_for_value.price,
113
- discount_type: discount_type
113
+ discount_type: discount_type,
114
+ percent_round: percent_round
114
115
  %}
115
116
  {% endif %}
116
117
  </div>