zz-shopify-components 0.25.0 → 0.25.1-beta.1

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.
@@ -0,0 +1,255 @@
1
+ <zz-product-swiper class=' tw-w-full product-swiper-container tw-h-full tw-relative'>
2
+ <div class='swiper product-swiper-list tw-w-full tw-h-full tw-relative'>
3
+ <div class='swiper-wrapper'>
4
+ {% assign product_images = product.selected_or_first_available_variant.metafields.custom.images.value | default: product.images %}
5
+ {% for image in product_images %}
6
+ <div class='swiper-slide tw-flex tw-justify-center'>
7
+ {% assign fetchpriority = 'auto' %}
8
+ {% if forloop.first %}
9
+ {% assign fetchpriority = 'high' %}
10
+ {% endif %}
11
+ {{
12
+ image
13
+ | image_url: width: 3000
14
+ | image_tag:
15
+ loading: 'lazy',
16
+ class: 'tw-w-auto tw-h-full tw-max-w-full tw-object-cover tw-m-auto',
17
+ sizes: '(min-width: 750px) 700px, calc(100vw - 30px)',
18
+ widths: '3000, 4000, 5000',
19
+ fetchpriority: fetchpriority
20
+ }}
21
+ </div>
22
+ {% endfor %}
23
+ </div>
24
+ <div class='swiper-pagination'></div>
25
+ <div class='product-swiper-button-prev'>
26
+ {% render 'zz-prev-next-blur-icon', type: 'prev', %}
27
+ </div>
28
+ <div class='product-swiper-button-next'>
29
+ {% render 'zz-prev-next-blur-icon', type: 'next' %}
30
+ </div>
31
+ </div>
32
+
33
+ <div class=' tw-absolute tw-bottom-[20px] tw-left-1/2 tw-transform tw-translate-x-[-50%] tw-z-10 tw-w-[80%] '>
34
+ <div class='swiper product-swiper-list-thumbs tw-opacity-0 tw-invisible'>
35
+ <div class='swiper-wrapper tw-flex tw-justify-center'>
36
+ {% for image in product_images %}
37
+ <div class='swiper-slide !tw-w-auto tw-opacity-30'>
38
+ {{
39
+ image
40
+ | image_url: width: 200
41
+ | image_tag:
42
+ loading: 'lazy',
43
+ class: 'lg:tw-w-[40px] tw-w-[30px] tw-aspect-square tw-object-cover tw-rounded-[6px] ',
44
+ sizes: '(min-width: 750px) 50px, calc(100vw - 30px)',
45
+ widths: '50, 100, 150'
46
+ }}
47
+ </div>
48
+ {% endfor %}
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </zz-product-swiper>
53
+
54
+ <style>
55
+ zz-product-swiper .product-swiper-button-prev path,
56
+ zz-product-swiper .product-swiper-button-next path {
57
+ fill: white;
58
+ }
59
+ .product-swiper-container .swiper-pagination {
60
+ --swiper-pagination-color: black;
61
+ --swiper-pagination-bottom: 10px;
62
+ }
63
+ .product-swiper-container .swiper-pagination,
64
+ .product-swiper-list-thumbs {
65
+ transition: opacity 0.2s ease-in-out;
66
+ }
67
+ .product-swiper-list-thumbs {
68
+ transform: translateY(12px) scale(0.96);
69
+ transform-origin: bottom center;
70
+ will-change: opacity, transform;
71
+ transition:
72
+ opacity 0.25s ease,
73
+ transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
74
+ }
75
+ .product-swiper-container:hover .product-swiper-list-thumbs {
76
+ opacity: 1;
77
+ transform: translateY(0) scale(1);
78
+ }
79
+ .product-swiper-container:hover .swiper-pagination {
80
+ display: none;
81
+ transition: none;
82
+ }
83
+
84
+ .product-swiper-list-thumbs .swiper-slide {
85
+ border: 1px solid black;
86
+ border-radius: 6px;
87
+ }
88
+
89
+ .product-swiper-list-thumbs .swiper-slide-thumb-active {
90
+ opacity: 1;
91
+ border: 1px solid black;
92
+ border-radius: 6px;
93
+ }
94
+ .product-swiper-button-prev,
95
+ .product-swiper-button-next {
96
+ position: absolute;
97
+ top: 50%;
98
+ transform: translateY(-50%);
99
+ z-index: 10;
100
+ cursor: pointer;
101
+ pointer-events: auto;
102
+ }
103
+ @media screen and (max-width: 1024px) {
104
+ .product-swiper-button-prev { display: none; }
105
+ .product-swiper-button-next { display: none; }
106
+ }
107
+ .product-swiper-button-prev { left: 20px; }
108
+ .product-swiper-button-next { right: 20px; }
109
+ </style>
110
+ <script>
111
+ ;(() => {
112
+ class ZZProductSwiper extends HTMLElement {
113
+ constructor() {
114
+ super();
115
+ this._swiper = null;
116
+ this._thumbs = null;
117
+ this._listEl = null;
118
+ this._thumbsEl = null;
119
+ this._paginationEl = null;
120
+ this._btnPrev = null;
121
+ this._btnNext = null;
122
+ }
123
+
124
+ connectedCallback() {
125
+ console.log(typeof Swiper,121212121);
126
+ if (typeof Swiper !== 'undefined') {
127
+ this._ensureStructure();
128
+ this._initSwipers();
129
+ } else {
130
+ document.addEventListener('DOMContentLoaded', () => {
131
+ this._ensureStructure();
132
+ this._initSwipers();
133
+ });}
134
+ }
135
+
136
+ disconnectedCallback() {
137
+ this._destroySwipers();
138
+ }
139
+
140
+ setImages(imageUrls) {
141
+ const newImages = {{product.selected_or_first_available_variant.metafields.custom.images.value | json}};
142
+ console.log(newImages);
143
+ if (!Array.isArray(imageUrls)) return;
144
+ this._rebuildSlides(imageUrls);
145
+ this._reinitSwipers(imageUrls.length);
146
+ }
147
+
148
+ _ensureStructure() {
149
+ this._listEl = this.querySelector('.product-swiper-list');
150
+ if (!this._listEl) {
151
+ const list = document.createElement('div');
152
+ list.className = 'swiper product-swiper-list tw-w-full tw-h-full tw-relative';
153
+ list.innerHTML = `
154
+ <div class="swiper-wrapper"></div>
155
+ <div class="swiper-pagination"></div>
156
+ <div class="product-swiper-button-prev"></div>
157
+ <div class="product-swiper-button-next"></div>
158
+ `;
159
+ this.appendChild(list);
160
+ this._listEl = list;
161
+ }
162
+ this._thumbsEl = this.querySelector('.product-swiper-list-thumbs');
163
+ if (!this._thumbsEl) {
164
+ const wrap = document.createElement('div');
165
+ wrap.className = ' tw-absolute tw-bottom-[20px] tw-left-1/2 tw-transform tw-translate-x-[-50%] tw-z-10 tw-w-[80%] ';
166
+ const thumbs = document.createElement('div');
167
+ thumbs.className = 'swiper product-swiper-list-thumbs tw-opacity-0 tw-invisible';
168
+ thumbs.innerHTML = `<div class="swiper-wrapper tw-flex tw-justify-center"></div>`;
169
+ wrap.appendChild(thumbs);
170
+ this.appendChild(wrap);
171
+ this._thumbsEl = thumbs;
172
+ }
173
+ this._paginationEl = this._listEl.querySelector('.swiper-pagination');
174
+ this._btnPrev = this._listEl.querySelector('.product-swiper-button-prev');
175
+ this._btnNext = this._listEl.querySelector('.product-swiper-button-next');
176
+ }
177
+
178
+ _initSwipers() {
179
+ if (typeof Swiper === 'undefined') return;
180
+ // Thumbs
181
+ this._thumbs = new Swiper(this._thumbsEl, {
182
+ slidesPerView: 3,
183
+ spaceBetween: 8,
184
+ watchSlidesProgress: true,
185
+
186
+ slideToClickedSlide: true,
187
+ breakpoints: {
188
+ 0: { slidesPerView: 4, spaceBetween: 6 },
189
+ 768: { slidesPerView: 5, spaceBetween: 8 },
190
+ 1024:{ slidesPerView: 6, spaceBetween: 10 }
191
+ }
192
+ });
193
+ // Main
194
+ const initialCount = this._listEl.querySelectorAll('.swiper-slide').length;
195
+ this._swiper = new Swiper(this._listEl, {
196
+ loop: true,
197
+ effect: 'fade',
198
+ loopedSlides: initialCount,
199
+ slidesPerView: 1,
200
+ spaceBetween: 10,
201
+ pagination: {
202
+ el: this._paginationEl,
203
+ clickable: true
204
+ },
205
+ navigation: {
206
+ nextEl: this._btnNext,
207
+ prevEl: this._btnPrev
208
+ },
209
+ thumbs: { swiper: this._thumbs }
210
+ });
211
+
212
+ this._thumbsEl.classList.remove('tw-invisible');
213
+ }
214
+
215
+ _destroySwipers() {
216
+ try { this._swiper && this._swiper.destroy(true, true); } catch(_) {}
217
+ try { this._thumbs && this._thumbs.destroy(true, true); } catch(_) {}
218
+ this._swiper = null;
219
+ this._thumbs = null;
220
+ }
221
+
222
+ _rebuildSlides(imageUrls) {
223
+ const listWrapper = this._listEl.querySelector('.swiper-wrapper');
224
+ const thumbsWrapper = this._thumbsEl.querySelector('.swiper-wrapper');
225
+ if (!listWrapper || !thumbsWrapper) return;
226
+ // Clear
227
+ listWrapper.innerHTML = '';
228
+ thumbsWrapper.innerHTML = '';
229
+ // Build
230
+ imageUrls.forEach((url) => {
231
+ const slide = document.createElement('div');
232
+ slide.className = 'swiper-slide tw-flex tw-justify-center';
233
+ slide.innerHTML =
234
+ `<img src="${url}" alt="product image" class="tw-w-auto tw-h-full tw-max-w-full tw-object-cover tw-m-auto">`;
235
+ listWrapper.appendChild(slide);
236
+
237
+ const tSlide = document.createElement('div');
238
+ tSlide.className = 'swiper-slide !tw-w-auto tw-opacity-30';
239
+ tSlide.innerHTML =
240
+ `<img src="${url}" alt="thumb" class="lg:tw-w-[50px] tw-w-[40px] tw-aspect-square tw-object-cover tw-rounded-[6px]">`;
241
+ thumbsWrapper.appendChild(tSlide);
242
+ });
243
+ }
244
+
245
+ _reinitSwipers(count) {
246
+ this._destroySwipers();
247
+ this._initSwipers();
248
+ }
249
+ }
250
+
251
+ if (!customElements.get('zz-product-swiper')) {
252
+ customElements.define('zz-product-swiper', ZZProductSwiper);
253
+ }
254
+ })();
255
+ </script>
@@ -0,0 +1,240 @@
1
+ {%- assign intro_video = product.metafields.custom.intro_video.value -%}
2
+ {%- assign livestream_id = product.metafields.custom.livestream_id.value -%}
3
+ {%- assign livestream_video = product.metafields.custom.livestream_video.value -%}
4
+ <script
5
+ async
6
+ type='text/javascript'
7
+ src='//asset.fwcdn3.com/js/integrations/shopify.js'
8
+ ></script>
9
+ <script
10
+ async
11
+ type='text/javascript'
12
+ src='//asset.fwcdn3.com/js/fwn-async.js'
13
+ ></script>
14
+ <product-video-tab
15
+ class=' tw-w-full tw-h-full '
16
+ {%- if intro_video -%}
17
+ data-default-tab='intro'
18
+ {%- elsif livestream_video -%}
19
+ data-default-tab='livestream'
20
+ {%- endif -%}
21
+ >
22
+ {%- if intro_video
23
+ and intro_video.sources
24
+ and intro_video.sources.size > 0
25
+ -%}
26
+ <video
27
+ id='product-video-intro-{{ product.id }}'
28
+ data-type='intro'
29
+ src='{{ intro_video.sources[0].url }}'
30
+ poster='{{ intro_video.preview_image | image_url: width: 1000 }}'
31
+ controls
32
+ playsinline
33
+ muted
34
+ preload='metadata'
35
+ class=' tw-w-full tw-h-full tw-bg-black '
36
+ ></video>
37
+ {%- endif -%}
38
+
39
+ {%- if livestream_id -%}
40
+ <div
41
+ id='firework-video-{{ product.id }}'
42
+ data-type='livestream'
43
+ class="tw-w-full tw-h-full tw-bg-black tw-relative 2xl:tw-py-[84px] lg:tw-py-[64px] lg:tw-px-[20px]"
44
+ role='tabpanel'
45
+ aria-labelledby='product-video-tab-livestream-{{ product.id }}'
46
+ >
47
+ <fw-storyblock
48
+ channel='firework'
49
+ playlist='{{ livestream_id }}'
50
+ player_minimize='false'
51
+ autoplay='true'
52
+ style=" height: 100%; width: 100%; border-radius: 0px;"
53
+ >
54
+ </fw-storyblock>
55
+ </div>
56
+ {% elsif livestream_video %}
57
+ <video
58
+ id='product-video-intro-{{ product.id }}'
59
+ role='tabpanel'
60
+ aria-labelledby='product-video-tab-livestream-{{ product.id }}'
61
+ data-type='livestream'
62
+ src='{{ livestream_video.sources[0].url }}'
63
+ poster='{{ livestream_video.preview_image | image_url: width: 1000 }}'
64
+ controls
65
+ playsinline
66
+ muted
67
+ preload='metadata'
68
+ class=' tw-w-full tw-h-full tw-bg-black '
69
+ ></video>
70
+ {%- endif -%}
71
+
72
+ {%- assign tabs_count = 0 -%}
73
+ {%- if intro_video
74
+ and intro_video.sources
75
+ and intro_video.sources.size > 0
76
+ -%}
77
+ {%- assign tabs_count = tabs_count | plus: 1 -%}
78
+ {%- endif -%}
79
+ {%- if livestream_video -%}
80
+ {%- assign tabs_count = tabs_count | plus: 1 -%}
81
+ {%- endif -%}
82
+
83
+ {%- if tabs_count > 1 -%}
84
+ <div
85
+ class=' product-video-tabs tw-absolute tw-left-1/2 tw--translate-x-1/2 tw-top-[30px] tw-bg-white '
86
+ role='tablist'
87
+ aria-label='Product video tabs'
88
+ >
89
+ <div class=' tw-grid tw-grid-cols-2 tw-relative'>
90
+ {%- if intro_video
91
+ and intro_video.sources
92
+ and intro_video.sources.size > 0
93
+ -%}
94
+ <button
95
+ type='button'
96
+ id='product-video-tab-intro-{{ product.id }}'
97
+ class='product-video-tab-item'
98
+ data-tab='intro'
99
+ role='tab'
100
+ aria-controls='product-video-intro-{{ product.id }}'
101
+ aria-selected='true'
102
+ >
103
+ Introduction
104
+ </button>
105
+ {%- endif -%}
106
+ {%- if livestream_video -%}
107
+ <button
108
+ type='button'
109
+ id='product-video-tab-livestream-{{ product.id }}'
110
+ class='product-video-tab-item'
111
+ data-tab='livestream'
112
+ role='tab'
113
+ aria-controls='firework-video-{{ product.id }}'
114
+ aria-selected='false'
115
+ >
116
+ Livestream
117
+ </button>
118
+ {%- endif -%}
119
+ <div
120
+ class='product-video-tab-slider tw-z-1 tw-rounded-full !tw-block tw-absolute tw-top-0 tw-w-1/2 tw-h-full tw-bg-white'
121
+ ></div>
122
+ </div>
123
+ </div>
124
+ {%- endif -%}
125
+ </product-video-tab>
126
+
127
+ <style>
128
+ .product-video-tabs {
129
+ border-radius: 999px;
130
+ background-color: rgba(255, 255, 255, 0.3);
131
+ backdrop-filter: blur(10px);
132
+ }
133
+ .product-video-tab-item {
134
+ color: #fff;
135
+ z-index: 10;
136
+ text-align: center;
137
+ padding: 6px 20px;
138
+ cursor: pointer;
139
+ font-size: 16px;
140
+ transition: all 0.3s ease;
141
+ }
142
+ @media screen and (max-width: 1024px) {
143
+ .product-video-tab-item {
144
+ padding: 4px 12px;
145
+ font-size: 10px;
146
+ }
147
+ }
148
+ .product-video-tab-slider {
149
+ transition: transform 0.3s ease;
150
+ }
151
+
152
+ #firework-video-{{ product.id }} .storyblock-scope {
153
+ border-radius: 0px;
154
+ height: 100% !important;
155
+ }
156
+ </style>
157
+
158
+ <script>
159
+ (() => {
160
+ class ProductVideoTab extends HTMLElement {
161
+ constructor() {
162
+ super();
163
+ this.currentTab = this.dataset.defaultTab || 'intro';
164
+ }
165
+
166
+ connectedCallback() {
167
+ this.cacheDom();
168
+ if (!this.panels.length) return;
169
+ if (this.panels.length < 2 && this.tabsContainer) {
170
+ this.tabsContainer.style.display = 'none';
171
+ }
172
+ this.changeTab(true);
173
+ this.attachEvents();
174
+ }
175
+
176
+ cacheDom() {
177
+ this.tabsContainer = this.querySelector('.product-video-tabs');
178
+ this.buttons = Array.from(
179
+ this.querySelectorAll('.product-video-tab-item')
180
+ );
181
+ this.panels = Array.from(this.querySelectorAll('[data-type]'));
182
+ this.slider = this.querySelector('.product-video-tab-slider');
183
+ }
184
+
185
+ attachEvents() {
186
+ this.buttons.forEach((button) => {
187
+ button.addEventListener('click', () => {
188
+ const nextTab = button.dataset.tab;
189
+ if (!nextTab || nextTab === this.currentTab) return;
190
+ this.currentTab = nextTab;
191
+ this.changeTab();
192
+ });
193
+ });
194
+ }
195
+
196
+ isMedia(el) {
197
+ return el && (el.tagName === 'VIDEO' || el.tagName === 'AUDIO');
198
+ }
199
+
200
+ changeTab(isInitial = false) {
201
+ this.panels.forEach((panel) => {
202
+ const isActive = panel.dataset.type === this.currentTab;
203
+ panel.style.display = isActive ? 'block' : 'none';
204
+ if (this.isMedia(panel)) {
205
+ try {
206
+ if (isActive) {
207
+ panel.muted = true;
208
+ const playPromise = panel.play && panel.play();
209
+ if (
210
+ playPromise &&
211
+ typeof playPromise.catch === 'function' &&
212
+ !isInitial
213
+ ) {
214
+ playPromise.catch(() => {});
215
+ }
216
+ } else {
217
+ panel.pause && panel.pause();
218
+ }
219
+ } catch (e) {}
220
+ }
221
+ });
222
+
223
+ this.buttons.forEach((button) => {
224
+ const isActive = button.dataset.tab === this.currentTab;
225
+ button.style.color = isActive ? '#000' : '#fff';
226
+ button.setAttribute('aria-selected', isActive ? 'true' : 'false');
227
+ });
228
+
229
+ if (!this.slider) return;
230
+ if (this.currentTab === 'intro') {
231
+ this.slider.style.transform = 'translateX(0%)';
232
+ } else {
233
+ this.slider.style.transform = 'translateX(100%)';
234
+ }
235
+ }
236
+ }
237
+
238
+ customElements.define('product-video-tab', ProductVideoTab);
239
+ })();
240
+ </script>