srcdev-nuxt-components 2.2.1 → 2.2.3

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.
@@ -1,36 +1,45 @@
1
1
  <template>
2
- <div class="slider-gallery" :class="elementClasses" ref="sliderGalleryWrapper">
3
- <div class="list" ref="sliderGalleryImagesList">
4
- <div v-for="(item, index) in galleryData" :key="index" class="item">
5
- <img :src="item.src" />
6
- <div class="content">
7
- <div class="author">{{ item.stylist }}</div>
8
- <div class="title">{{ item.title }}</div>
9
- <div class="topic">{{ item.category }}</div>
10
- <div class="des">{{ item.description }}</div>
11
- <div class="buttons">
12
- <button>SEE MORE</button>
2
+ <div class="slider-gallery" :class="[elementClasses]" ref="sliderGalleryWrapper">
3
+ <div class="loading-state" :class="[{ loaded: !isLoading }]">
4
+ <div class="loading-spinner"></div>
5
+ <p>Loading gallery...</p>
6
+ </div>
7
+
8
+ <div v-if="showGallery" class="gallery-content" :class="[{ loaded: isLoading }]">
9
+ <div class="list" ref="sliderGalleryImagesList">
10
+ <div v-for="(item, index) in galleryData" :key="index" class="item">
11
+ <img :src="item.src" :alt="item.alt" @load="handleImageLoad(index)" @error="handleImageError(index)" loading="lazy" />
12
+ <div class="content">
13
+ <div class="author">{{ item.stylist }}</div>
14
+ <div class="title">{{ item.title }}</div>
15
+ <div class="topic">{{ item.category }}</div>
16
+ <div class="des">{{ item.description }}</div>
17
+ <div class="buttons">
18
+ <button>SEE MORE</button>
19
+ </div>
13
20
  </div>
14
21
  </div>
15
22
  </div>
16
- </div>
17
23
 
18
- <div class="thumbnail" ref="sliderGalleryThumbnailsList">
19
- <div v-for="(item, index) in galleryData" :key="index" class="item">
20
- <img :src="item.src" />
21
- <div class="content">
22
- <div class="title" v-show="item.thumbnail?.title !== ''">{{ item.thumbnail?.title }}</div>
23
- <div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}</div>
24
+ <div class="thumbnail" ref="sliderGalleryThumbnailsList">
25
+ <div v-for="(item, index) in galleryData" :key="index" class="item">
26
+ <div class="inner">
27
+ <img :src="item.src" :alt="item.alt" loading="lazy" />
28
+ <div class="content">
29
+ <div class="title" v-show="item.thumbnail?.title !== ''">{{ item.thumbnail?.title }}</div>
30
+ <div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}</div>
31
+ </div>
32
+ </div>
24
33
  </div>
25
34
  </div>
26
- </div>
27
35
 
28
- <div class="arrows">
29
- <button id="prev" ref="prevDom" @click.prevent="doPrevious()"><</button>
30
- <button id="next" ref="nextDom" @click.prevent="doNext()">></button>
31
- </div>
36
+ <div class="arrows">
37
+ <button id="prev" ref="prevDom" @click.prevent="doPrevious()"><</button>
38
+ <button id="next" ref="nextDom" @click.prevent="doNext()">></button>
39
+ </div>
32
40
 
33
- <div class="time"></div>
41
+ <div class="time"></div>
42
+ </div>
34
43
  </div>
35
44
  </template>
36
45
 
@@ -74,22 +83,76 @@ const sliderGalleryWrapper = useTemplateRef('sliderGalleryWrapper');
74
83
  const sliderGalleryImagesList = useTemplateRef('sliderGalleryImagesList');
75
84
  const sliderGalleryThumbnailsList = useTemplateRef('sliderGalleryThumbnailsList');
76
85
 
77
- // const animationDuration = 3000;
78
- const autoRunInterval = 7000;
86
+ const isLoading = ref(true);
87
+ const showGallery = ref(false);
88
+ const loadedImages = ref<Set<number>>(new Set());
89
+ const preloadedImages = ref<Array<HTMLImageElement>>([]);
90
+
91
+ onMounted(async () => {
92
+ await nextTick();
93
+
94
+ // If no images or galleryData is empty, stop loading
95
+ if (!galleryData.value || galleryData.value.length === 0) {
96
+ isLoading.value = false;
97
+ return;
98
+ }
99
+
100
+ // Create an array to hold image loading promises
101
+ const imageLoadPromises: Promise<void>[] = [];
102
+
103
+ // Preload the first image at minimum
104
+ const firstImageIndex = 0;
105
+ if (galleryData.value[firstImageIndex]) {
106
+ const img = new Image();
107
+ img.src = galleryData.value[firstImageIndex].src;
108
+
109
+ const promise = new Promise<void>((resolve) => {
110
+ img.onload = () => {
111
+ console.log('Image preloaded:', firstImageIndex);
112
+ loadedImages.value.add(firstImageIndex);
113
+ resolve();
114
+ };
115
+ img.onerror = () => {
116
+ console.error('Failed to preload image:', firstImageIndex);
117
+ loadedImages.value.add(firstImageIndex); // Count as loaded anyway
118
+ resolve();
119
+ };
120
+ });
121
+
122
+ imageLoadPromises.push(promise);
123
+ preloadedImages.value.push(img);
124
+ }
125
+
126
+ // Wait for at least the first image to load
127
+ await Promise.race(imageLoadPromises);
128
+
129
+ setTimeout(() => {
130
+ isLoading.value = false;
131
+ }, 500);
132
+ });
133
+
134
+ const handleImageLoad = (index: number) => {
135
+ console.log('Image loaded:', index);
136
+ loadedImages.value.add(index);
137
+ };
138
+
139
+ const handleImageError = (index: number) => {
140
+ console.error(`Failed to load image at index ${index}`);
141
+ loadedImages.value.add(index);
142
+ };
79
143
 
80
144
  const doNext = () => {
145
+ if (isLoading.value) return;
81
146
  showSlider('next');
82
147
  };
83
148
 
84
149
  const doPrevious = () => {
150
+ if (isLoading.value) return;
85
151
  showSlider('prev');
86
152
  };
87
153
 
88
154
  let runTimeOut: any;
89
- let runNextAuto = setTimeout(() => {
90
- if (!props.autoRun) return;
91
- doNext();
92
- }, autoRunInterval);
155
+ let runNextAuto: any = null;
93
156
 
94
157
  function showSlider(type: string) {
95
158
  const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
@@ -108,38 +171,30 @@ function showSlider(type: string) {
108
171
 
109
172
  sliderGalleryWrapper.value?.classList.add('next');
110
173
  } else {
111
- // For prev animation:
112
- // 1. First modify the DOM (prepend the items)
113
174
  if (currentSliderItems.length) {
114
175
  const lastItem = currentSliderItems[currentSliderItems.length - 1];
115
- // Set initial state before prepending (if needed)
116
176
  lastItem.classList.add('prepend-item');
117
177
  sliderGalleryImagesList.value?.prepend(lastItem);
118
178
  }
119
179
 
120
180
  if (currentThumbnailItems.length) {
121
181
  const lastThumb = currentThumbnailItems[currentThumbnailItems.length - 1];
122
- // Set initial state before prepending (if needed)
123
182
  lastThumb.classList.add('prepend-item');
124
183
  sliderGalleryThumbnailsList.value?.prepend(lastThumb);
125
184
  }
126
185
 
127
- // 2. Force reflow to ensure the DOM changes are applied
128
- // This is a standard technique to ensure CSS transitions work properly
129
- // when you need to apply styles immediately after DOM changes
130
- sliderGalleryWrapper.value?.offsetWidth;
131
-
132
- // 3. Add the class for animation
186
+ sliderGalleryWrapper.value?.offsetWidth; // Force reflow
133
187
  sliderGalleryWrapper.value?.classList.add('prev');
134
188
  }
135
189
 
136
190
  clearTimeout(runTimeOut);
137
191
  runTimeOut = setTimeout(() => {
192
+ // Your existing cleanup code
138
193
  if (sliderGalleryWrapper.value) {
139
194
  sliderGalleryWrapper.value.classList.remove('next');
140
195
  sliderGalleryWrapper.value.classList.remove('prev');
141
196
 
142
- // Remove any helper classes we added
197
+ // Remove helper classes
143
198
  const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
144
199
  items?.forEach((item) => item.classList.remove('prepend-item'));
145
200
 
@@ -148,19 +203,39 @@ function showSlider(type: string) {
148
203
  }
149
204
  }, props.animationDuration);
150
205
 
206
+ // Reset auto-run timer
151
207
  clearTimeout(runNextAuto);
152
208
  runNextAuto = setTimeout(() => {
153
- if (!props.autoRun) return;
209
+ if (!props.autoRun || isLoading.value) return;
154
210
  doNext();
155
- }, autoRunInterval);
211
+ }, props.autoRunInterval);
156
212
  }
157
213
 
214
+ // Initialize auto-run only after loading completes
215
+ watch(isLoading, (isLoadingNow) => {
216
+ if (!isLoadingNow && props.autoRun) {
217
+ clearTimeout(runNextAuto);
218
+ runNextAuto = setTimeout(() => {
219
+ doNext();
220
+ }, props.autoRunInterval);
221
+ }
222
+ });
223
+
158
224
  watch(
159
225
  () => props.styleClassPassthrough,
160
226
  () => {
161
227
  resetElementClasses(props.styleClassPassthrough);
162
228
  }
163
229
  );
230
+
231
+ onBeforeUnmount(() => {
232
+ clearTimeout(runTimeOut);
233
+ clearTimeout(runNextAuto);
234
+ });
235
+
236
+ onMounted(() => {
237
+ showGallery.value = true;
238
+ });
164
239
  </script>
165
240
 
166
241
  <style lang="css">
@@ -171,12 +246,58 @@ watch(
171
246
  --_thembnailAspectRatio: 150 /220;
172
247
 
173
248
  height: 100svh;
174
- /* margin-top: -50px; */
175
249
  width: 100vw;
176
250
  overflow: hidden;
177
251
  position: absolute;
178
252
  inset: 0 0 0 0;
179
253
 
254
+ .loading-state {
255
+ position: absolute;
256
+ inset: 0 0 0 0;
257
+ z-index: 1000;
258
+ display: flex;
259
+ flex-direction: column;
260
+ background-color: var(--page-bg);
261
+ align-items: center;
262
+ justify-content: center;
263
+ color: var(--grayscale-text-body);
264
+ opacity: 1;
265
+ transition: display 0.5s, opacity 0.5s;
266
+ transition-behavior: allow-discrete;
267
+
268
+ &.loaded {
269
+ display: none;
270
+ opacity: 0;
271
+ }
272
+
273
+ .loading-spinner {
274
+ width: 50px;
275
+ height: 50px;
276
+ border: 5px solid rgba(0, 0, 0, 0.1);
277
+ border-radius: 50%;
278
+ border-top-color: #f1683a;
279
+ animation: spinner 1s ease-in-out infinite;
280
+ margin-bottom: 20px;
281
+ }
282
+
283
+ p {
284
+ font-size: 1.2em;
285
+ font-weight: 500;
286
+ }
287
+ }
288
+
289
+ .gallery-content {
290
+ width: 100%;
291
+ height: 100%;
292
+ position: relative;
293
+ /* opacity: 0; */
294
+ /* transition: opacity 0.5s ease-in-out; */
295
+
296
+ &.loaded {
297
+ /* opacity: 1; */
298
+ }
299
+ }
300
+
180
301
  .list {
181
302
  .item {
182
303
  width: 100%;
@@ -287,6 +408,28 @@ watch(
287
408
  outline: var(--_thumbnailOutline, 1px solid transparent);
288
409
  border-radius: var(--_thumbnailBorderRadius, 20px);
289
410
 
411
+ .inner {
412
+ position: absolute;
413
+ inset: 0 0 0 0;
414
+ background-color: #0004;
415
+ border-radius: var(--_thumbnailBorderRadius, 20px);
416
+ z-index: 2;
417
+ }
418
+
419
+ &:first-child {
420
+ /* Add the animated border effect */
421
+ &::before {
422
+ content: '';
423
+ position: absolute;
424
+ inset: -3px; /* Border outside the thumbnail */
425
+ border-radius: 20px;
426
+ /* background: conic-gradient(transparent 0deg, transparent 360deg); */
427
+ z-index: 1;
428
+ animation: none;
429
+ pointer-events: none;
430
+ }
431
+ }
432
+
290
433
  img {
291
434
  width: 100%;
292
435
  height: 100%;
@@ -380,6 +523,12 @@ watch(
380
523
  animation: effectNext 0.5s linear 1 forwards;
381
524
 
382
525
  .item {
526
+ &:first-child {
527
+ /* Add the animated border effect */
528
+ &::before {
529
+ animation: countdownBorder 7s linear 1 forwards;
530
+ }
531
+ }
383
532
  &:nth-last-child(1) {
384
533
  overflow: hidden;
385
534
  animation: showThumbnail 0.5s linear 1 forwards;
@@ -437,6 +586,12 @@ watch(
437
586
  animation: effectPrev 0.5s linear 1 forwards;
438
587
 
439
588
  .item {
589
+ &:first-child {
590
+ /* Add the animated border effect */
591
+ &::before {
592
+ animation: countdownBorder 7s linear 1 forwards;
593
+ }
594
+ }
440
595
  &:nth-child(1) {
441
596
  overflow: hidden;
442
597
  animation: showThumbnailPrev 0.5s linear 1 forwards;
@@ -533,6 +688,15 @@ watch(
533
688
  }
534
689
  }
535
690
 
691
+ @keyframes spinner {
692
+ 0% {
693
+ transform: rotate(0deg);
694
+ }
695
+ 100% {
696
+ transform: rotate(360deg);
697
+ }
698
+ }
699
+
536
700
  @media screen and (max-width: 678px) {
537
701
  .slider-gallery .list .item .content {
538
702
  padding-right: 0;
@@ -541,4 +705,14 @@ watch(
541
705
  font-size: 30px;
542
706
  }
543
707
  }
708
+
709
+ /* Keyframe for the border animation */
710
+ @keyframes countdownBorder {
711
+ 0% {
712
+ background: conic-gradient(brightgreen 0deg, transparent 0deg);
713
+ }
714
+ 100% {
715
+ background: conic-gradient(brightgreen 0deg, brightgreen 360deg);
716
+ }
717
+ }
544
718
  </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "2.2.1",
4
+ "version": "2.2.3",
5
5
  "main": "nuxt.config.ts",
6
6
  "scripts": {
7
7
  "clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",