srcdev-nuxt-components 2.2.0 → 2.2.2

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,41 +1,62 @@
1
1
  <template>
2
- <div class="slider-gallery" ref="sliderGalleryWrapper">
3
- <div class="list" ref="sliderGalleryImagesList">
4
- <div v-for="item in galleryData" 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 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 in galleryData" class="item">
20
- <img :src="item.src" />
21
- <div class="content">
22
- <div class="title">Name Slider</div>
23
- <div class="description">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" ref="timeDom"></div>
41
+ <div class="time"></div>
42
+ </div>
34
43
  </div>
35
44
  </template>
36
45
 
37
46
  <script setup lang="ts">
38
47
  const props = defineProps({
48
+ autoRun: {
49
+ type: Boolean,
50
+ default: true,
51
+ },
52
+ autoRunInterval: {
53
+ type: Number,
54
+ default: 7000,
55
+ },
56
+ animationDuration: {
57
+ type: Number,
58
+ default: 3000,
59
+ },
39
60
  styleClassPassthrough: {
40
61
  type: Array as PropType<string[]>,
41
62
  default: () => [],
@@ -49,6 +70,10 @@ interface IGalleryData {
49
70
  title?: string;
50
71
  category?: string;
51
72
  description?: string;
73
+ thumbnail?: {
74
+ title: string;
75
+ description: string;
76
+ };
52
77
  }
53
78
 
54
79
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
@@ -57,31 +82,82 @@ const galleryData = defineModel<IGalleryData[]>('galleryData');
57
82
  const sliderGalleryWrapper = useTemplateRef('sliderGalleryWrapper');
58
83
  const sliderGalleryImagesList = useTemplateRef('sliderGalleryImagesList');
59
84
  const sliderGalleryThumbnailsList = useTemplateRef('sliderGalleryThumbnailsList');
60
- const timeDom = useTemplateRef('timeDom');
61
85
 
62
- const timeRunning = 3000;
63
- const timeAutoNext = 7000;
86
+ const isLoading = ref(true);
87
+ const loadedImages = ref<Set<number>>(new Set());
88
+ const preloadedImages = ref<Array<HTMLImageElement>>([]);
89
+
90
+ onMounted(async () => {
91
+ await nextTick();
92
+
93
+ // If no images or galleryData is empty, stop loading
94
+ if (!galleryData.value || galleryData.value.length === 0) {
95
+ isLoading.value = false;
96
+ return;
97
+ }
98
+
99
+ // Create an array to hold image loading promises
100
+ const imageLoadPromises: Promise<void>[] = [];
101
+
102
+ // Preload the first image at minimum
103
+ const firstImageIndex = 0;
104
+ if (galleryData.value[firstImageIndex]) {
105
+ const img = new Image();
106
+ img.src = galleryData.value[firstImageIndex].src;
107
+
108
+ const promise = new Promise<void>((resolve) => {
109
+ img.onload = () => {
110
+ console.log('Image preloaded:', firstImageIndex);
111
+ loadedImages.value.add(firstImageIndex);
112
+ resolve();
113
+ };
114
+ img.onerror = () => {
115
+ console.error('Failed to preload image:', firstImageIndex);
116
+ loadedImages.value.add(firstImageIndex); // Count as loaded anyway
117
+ resolve();
118
+ };
119
+ });
120
+
121
+ imageLoadPromises.push(promise);
122
+ preloadedImages.value.push(img);
123
+ }
124
+
125
+ // Wait for at least the first image to load
126
+ await Promise.race(imageLoadPromises);
127
+
128
+ setTimeout(() => {
129
+ isLoading.value = false;
130
+ }, 500);
131
+ });
132
+
133
+ const handleImageLoad = (index: number) => {
134
+ console.log('Image loaded:', index);
135
+ loadedImages.value.add(index);
136
+ };
137
+
138
+ const handleImageError = (index: number) => {
139
+ console.error(`Failed to load image at index ${index}`);
140
+ loadedImages.value.add(index);
141
+ };
64
142
 
65
143
  const doNext = () => {
144
+ if (isLoading.value) return;
66
145
  showSlider('next');
67
146
  };
68
147
 
69
148
  const doPrevious = () => {
149
+ if (isLoading.value) return;
70
150
  showSlider('prev');
71
151
  };
72
152
 
73
153
  let runTimeOut: any;
74
- let runNextAuto = setTimeout(() => {
75
- doNext();
76
- }, timeAutoNext);
154
+ let runNextAuto: any = null;
77
155
 
78
156
  function showSlider(type: string) {
79
- // Get fresh references to all items by querying the DOM directly
80
157
  const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
81
158
  const currentThumbnailItems = Array.from(sliderGalleryThumbnailsList.value?.children || []);
82
159
 
83
160
  if (type === 'next') {
84
- // Move the first item to the end
85
161
  if (currentSliderItems.length) {
86
162
  const firstItem = currentSliderItems[0];
87
163
  sliderGalleryImagesList.value?.appendChild(firstItem);
@@ -94,52 +170,129 @@ function showSlider(type: string) {
94
170
 
95
171
  sliderGalleryWrapper.value?.classList.add('next');
96
172
  } else {
97
- // Move the last item to the beginning
98
173
  if (currentSliderItems.length) {
99
174
  const lastItem = currentSliderItems[currentSliderItems.length - 1];
175
+ lastItem.classList.add('prepend-item');
100
176
  sliderGalleryImagesList.value?.prepend(lastItem);
101
177
  }
102
178
 
103
179
  if (currentThumbnailItems.length) {
104
180
  const lastThumb = currentThumbnailItems[currentThumbnailItems.length - 1];
181
+ lastThumb.classList.add('prepend-item');
105
182
  sliderGalleryThumbnailsList.value?.prepend(lastThumb);
106
183
  }
107
184
 
185
+ sliderGalleryWrapper.value?.offsetWidth; // Force reflow
108
186
  sliderGalleryWrapper.value?.classList.add('prev');
109
187
  }
110
188
 
111
189
  clearTimeout(runTimeOut);
112
190
  runTimeOut = setTimeout(() => {
191
+ // Your existing cleanup code
113
192
  if (sliderGalleryWrapper.value) {
114
193
  sliderGalleryWrapper.value.classList.remove('next');
115
194
  sliderGalleryWrapper.value.classList.remove('prev');
195
+
196
+ // Remove helper classes
197
+ const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
198
+ items?.forEach((item) => item.classList.remove('prepend-item'));
199
+
200
+ const thumbs = sliderGalleryThumbnailsList.value?.querySelectorAll('.prepend-item');
201
+ thumbs?.forEach((thumb) => thumb.classList.remove('prepend-item'));
116
202
  }
117
- }, timeRunning);
203
+ }, props.animationDuration);
118
204
 
205
+ // Reset auto-run timer
119
206
  clearTimeout(runNextAuto);
120
207
  runNextAuto = setTimeout(() => {
208
+ if (!props.autoRun || isLoading.value) return;
121
209
  doNext();
122
- }, timeAutoNext);
210
+ }, props.autoRunInterval);
123
211
  }
124
212
 
213
+ // Initialize auto-run only after loading completes
214
+ watch(isLoading, (isLoadingNow) => {
215
+ if (!isLoadingNow && props.autoRun) {
216
+ clearTimeout(runNextAuto);
217
+ runNextAuto = setTimeout(() => {
218
+ doNext();
219
+ }, props.autoRunInterval);
220
+ }
221
+ });
222
+
125
223
  watch(
126
224
  () => props.styleClassPassthrough,
127
225
  () => {
128
226
  resetElementClasses(props.styleClassPassthrough);
129
227
  }
130
228
  );
229
+
230
+ onBeforeUnmount(() => {
231
+ clearTimeout(runTimeOut);
232
+ clearTimeout(runNextAuto);
233
+ });
131
234
  </script>
132
235
 
133
236
  <style lang="css">
134
237
  /* slider-gallery */
135
238
  .slider-gallery {
239
+ --_animationDuration: v-bind(animationDuration + 'ms');
240
+
241
+ --_thembnailAspectRatio: 150 /220;
242
+
136
243
  height: 100svh;
137
- /* margin-top: -50px; */
138
244
  width: 100vw;
139
245
  overflow: hidden;
140
246
  position: absolute;
141
247
  inset: 0 0 0 0;
142
248
 
249
+ .loading-state {
250
+ position: absolute;
251
+ inset: 0 0 0 0;
252
+ z-index: 1000;
253
+ display: flex;
254
+ flex-direction: column;
255
+ background-color: var(--page-bg);
256
+ align-items: center;
257
+ justify-content: center;
258
+ color: var(--grayscale-text-body);
259
+ opacity: 1;
260
+ transition: display 0.5s, opacity 0.5s;
261
+ transition-behavior: allow-discrete;
262
+
263
+ &.loaded {
264
+ display: none;
265
+ opacity: 0;
266
+ }
267
+
268
+ .loading-spinner {
269
+ width: 50px;
270
+ height: 50px;
271
+ border: 5px solid rgba(0, 0, 0, 0.1);
272
+ border-radius: 50%;
273
+ border-top-color: #f1683a;
274
+ animation: spinner 1s ease-in-out infinite;
275
+ margin-bottom: 20px;
276
+ }
277
+
278
+ p {
279
+ font-size: 1.2em;
280
+ font-weight: 500;
281
+ }
282
+ }
283
+
284
+ .gallery-content {
285
+ width: 100%;
286
+ height: 100%;
287
+ position: relative;
288
+ /* opacity: 0; */
289
+ /* transition: opacity 0.5s ease-in-out; */
290
+
291
+ &.loaded {
292
+ /* opacity: 1; */
293
+ }
294
+ }
295
+
143
296
  .list {
144
297
  .item {
145
298
  width: 100%;
@@ -246,6 +399,32 @@ watch(
246
399
  flex-shrink: 0;
247
400
  position: relative;
248
401
 
402
+ border: var(--_thumbnailBorder, 1px solid transparent);
403
+ outline: var(--_thumbnailOutline, 1px solid transparent);
404
+ border-radius: var(--_thumbnailBorderRadius, 20px);
405
+
406
+ .inner {
407
+ position: absolute;
408
+ inset: 0 0 0 0;
409
+ background-color: #0004;
410
+ border-radius: var(--_thumbnailBorderRadius, 20px);
411
+ z-index: 2;
412
+ }
413
+
414
+ &:first-child {
415
+ /* Add the animated border effect */
416
+ &::before {
417
+ content: '';
418
+ position: absolute;
419
+ inset: -3px; /* Border outside the thumbnail */
420
+ border-radius: 20px;
421
+ /* background: conic-gradient(transparent 0deg, transparent 360deg); */
422
+ z-index: 1;
423
+ animation: none;
424
+ pointer-events: none;
425
+ }
426
+ }
427
+
249
428
  img {
250
429
  width: 100%;
251
430
  height: 100%;
@@ -339,6 +518,12 @@ watch(
339
518
  animation: effectNext 0.5s linear 1 forwards;
340
519
 
341
520
  .item {
521
+ &:first-child {
522
+ /* Add the animated border effect */
523
+ &::before {
524
+ animation: countdownBorder 7s linear 1 forwards;
525
+ }
526
+ }
342
527
  &:nth-last-child(1) {
343
528
  overflow: hidden;
344
529
  animation: showThumbnail 0.5s linear 1 forwards;
@@ -347,7 +532,7 @@ watch(
347
532
  }
348
533
 
349
534
  .time {
350
- animation: runningTime 3s linear 1 forwards;
535
+ animation: runningTime var(--_animationDuration) linear 1 forwards;
351
536
  }
352
537
  }
353
538
 
@@ -378,6 +563,11 @@ watch(
378
563
  z-index: 100;
379
564
  }
380
565
  }
566
+
567
+ .item.prepend-item {
568
+ z-index: 1; /* Ensure it's visible */
569
+ /* Any initial styles needed */
570
+ }
381
571
  }
382
572
 
383
573
  .arrows {
@@ -387,16 +577,30 @@ watch(
387
577
  }
388
578
 
389
579
  .thumbnail {
580
+ /* Add a transform to the entire thumbnail container */
581
+ animation: effectPrev 0.5s linear 1 forwards;
582
+
390
583
  .item {
584
+ &:first-child {
585
+ /* Add the animated border effect */
586
+ &::before {
587
+ animation: countdownBorder 7s linear 1 forwards;
588
+ }
589
+ }
391
590
  &:nth-child(1) {
392
591
  overflow: hidden;
393
- opacity: 0;
394
- animation: showThumbnail 0.5s linear 1 forwards;
592
+ animation: showThumbnailPrev 0.5s linear 1 forwards;
395
593
  }
396
594
  }
595
+
596
+ .item.prepend-item {
597
+ opacity: 0;
598
+ transform: translateX(-20px);
599
+ /* Initial state for thumbnail animation */
600
+ }
397
601
  }
398
602
  .time {
399
- animation: runningTime 3s linear 1 forwards;
603
+ animation: runningTime var(--_animationDuration) linear 1 forwards;
400
604
  }
401
605
  }
402
606
  }
@@ -458,6 +662,36 @@ watch(
458
662
  opacity: 0;
459
663
  }
460
664
  }
665
+
666
+ @keyframes effectPrev {
667
+ from {
668
+ transform: translateX(-150px);
669
+ }
670
+ to {
671
+ transform: translateX(0);
672
+ }
673
+ }
674
+
675
+ @keyframes showThumbnailPrev {
676
+ from {
677
+ opacity: 0;
678
+ transform: translateX(-20px);
679
+ }
680
+ to {
681
+ opacity: 1;
682
+ transform: translateX(0);
683
+ }
684
+ }
685
+
686
+ @keyframes spinner {
687
+ 0% {
688
+ transform: rotate(0deg);
689
+ }
690
+ 100% {
691
+ transform: rotate(360deg);
692
+ }
693
+ }
694
+
461
695
  @media screen and (max-width: 678px) {
462
696
  .slider-gallery .list .item .content {
463
697
  padding-right: 0;
@@ -466,4 +700,14 @@ watch(
466
700
  font-size: 30px;
467
701
  }
468
702
  }
703
+
704
+ /* Keyframe for the border animation */
705
+ @keyframes countdownBorder {
706
+ 0% {
707
+ background: conic-gradient(brightgreen 0deg, transparent 0deg);
708
+ }
709
+ 100% {
710
+ background: conic-gradient(brightgreen 0deg, brightgreen 360deg);
711
+ }
712
+ }
469
713
  </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.0",
4
+ "version": "2.2.2",
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",