@yatoday/astro-ui 0.17.2 → 0.17.5

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.
@@ -22,9 +22,34 @@ const { container: containerClass = '', swiper: swiperClass = '', swiperThumb: s
22
22
  opacity: 1;
23
23
  border: 1px solid #666;
24
24
  }
25
+
26
+ /* Hide slides before Swiper initializes to prevent FOUC */
27
+ [data-image-gallery-ikea]:not([data-initialized]) swiper-slide {
28
+ display: none;
29
+ }
30
+ [data-image-gallery-ikea]:not([data-initialized]) swiper-slide:first-child {
31
+ display: block;
32
+ }
33
+
34
+ /* Hide loader after initialization */
35
+ [data-image-gallery-ikea][data-initialized] .gallery-loader {
36
+ display: none;
37
+ }
38
+
39
+ /* Loading spinner animation */
40
+ @keyframes gallery-spin {
41
+ to { transform: rotate(360deg); }
42
+ }
43
+ .gallery-spinner {
44
+ animation: gallery-spin 1s linear infinite;
45
+ }
25
46
  </style>
26
47
 
27
48
  <div class={cn('@container relative', containerClass)} data-image-gallery-ikea={id}>
49
+ <!-- Loading skeleton -->
50
+ <div class="gallery-loader absolute inset-0 z-30 flex items-center justify-center bg-muted/50 rounded-lg">
51
+ <div class="gallery-spinner size-10 border-4 border-muted-foreground/20 border-t-primary rounded-full"></div>
52
+ </div>
28
53
  <div class="absolute left-0 top-0 z-20 hidden md:block group w-20">
29
54
  <!-- Thumb Slider -->
30
55
  <button
@@ -204,6 +229,9 @@ const { container: containerClass = '', swiper: swiperClass = '', swiperThumb: s
204
229
  Object.assign(mainSwiperEl, {});
205
230
 
206
231
  (mainSwiperEl as any)?.initialize();
232
+
233
+ // Mark as initialized to show content and hide loader
234
+ elem.setAttribute('data-initialized', 'true');
207
235
  });
208
236
  };
209
237
 
@@ -70,6 +70,9 @@
70
70
 
71
71
  Object.assign(mainSwiperEl, {});
72
72
  (mainSwiperEl as any)?.initialize();
73
+
74
+ // Mark as initialized to show content and hide loader
75
+ elem.setAttribute('data-initialized', 'true');
73
76
  };
74
77
 
75
78
  init();
@@ -82,9 +85,34 @@
82
85
  opacity: 1;
83
86
  border: 1px solid #666;
84
87
  }
88
+
89
+ /* Hide slides before Swiper initializes to prevent FOUC */
90
+ :global([data-image-gallery-ikea]:not([data-initialized]) swiper-slide) {
91
+ display: none;
92
+ }
93
+ :global([data-image-gallery-ikea]:not([data-initialized]) swiper-slide:first-child) {
94
+ display: block;
95
+ }
96
+
97
+ /* Hide loader after initialization */
98
+ :global([data-image-gallery-ikea][data-initialized] .gallery-loader) {
99
+ display: none;
100
+ }
101
+
102
+ /* Loading spinner animation */
103
+ @keyframes gallery-spin {
104
+ to { transform: rotate(360deg); }
105
+ }
106
+ .gallery-spinner {
107
+ animation: gallery-spin 1s linear infinite;
108
+ }
85
109
  </style>
86
110
 
87
111
  <div bind:this={ref} class={cn('@container relative', containerClass)} data-image-gallery-ikea={id} {...rest}>
112
+ <!-- Loading skeleton -->
113
+ <div class="gallery-loader absolute inset-0 z-30 flex items-center justify-center bg-muted/50 rounded-lg">
114
+ <div class="gallery-spinner size-10 border-4 border-muted-foreground/20 border-t-primary rounded-full"></div>
115
+ </div>
88
116
  <div class="absolute left-0 top-0 z-20 hidden md:block group w-20">
89
117
  <!-- Thumb Slider -->
90
118
  <button
@@ -12,6 +12,7 @@ const {
12
12
  subtitle = await Astro.slots.render('subtitle'),
13
13
  tagline = await Astro.slots.render('tagline'),
14
14
  imagesFolder,
15
+ alt,
15
16
  position = 'center',
16
17
  asHeader = 'h2',
17
18
  asSubtitle = 'p',
@@ -51,13 +52,24 @@ const imagePaths = Object.keys(images).filter((imagePath) => {
51
52
  {
52
53
  imagePaths && (
53
54
  <SwiperSlider id={id} withNavigation={withNavigation} {...rest}>
54
- {imagePaths.map(async (imagePath) => {
55
+ {imagePaths.map(async (imagePath, index) => {
55
56
  let image = images[imagePath]();
56
57
  let optimizedImage = await getImage({
57
58
  src: image,
58
59
  width: 860,
59
60
  });
60
61
 
62
+ // Generate alt text: alt prop > title-based > filename > default
63
+ const filename = imagePath.split('/').pop()?.replace(/\.[^/.]+$/, '') || '';
64
+ const imageNumber = index + 1;
65
+ const imageName = filename || 'Image ' + imageNumber;
66
+ const baseTitle = title ? String(title).replace(/<[^>]*>/g, '').trim() : '';
67
+ const altText = alt
68
+ ? alt + ' - ' + imageName
69
+ : baseTitle
70
+ ? baseTitle + ' - ' + imageName
71
+ : imageName;
72
+
61
73
  return (
62
74
  <swiper-slide>
63
75
  <a
@@ -65,11 +77,13 @@ const imagePaths = Object.keys(images).filter((imagePath) => {
65
77
  data-pswp-width={optimizedImage.attributes.width}
66
78
  data-pswp-height={optimizedImage.attributes.height}
67
79
  target="_blank"
80
+ title={altText}
68
81
  class="group overflow-hidden rounded-md border-primary cursor-zoom-in block aspect-square"
69
82
  >
70
83
  <AstroImage
71
84
  src={image}
72
- alt={'altText'}
85
+ alt={altText}
86
+ title={altText}
73
87
  widths={[400, 900]}
74
88
  width={400}
75
89
  sizes="(max-width: 900px) 400px, 900px"
@@ -7,6 +7,7 @@ export type WidgetSwiperPhotoSliderProps = {
7
7
  isAfterContent?: boolean;
8
8
  withNavigation?: boolean;
9
9
  imagesFolder: string;
10
+ alt?: string;
10
11
  classes?: {
11
12
  container?: string;
12
13
  headline?: Record<string, string>;
@@ -61,8 +61,8 @@ if (
61
61
  type="button"
62
62
  class={cn('zoom-bg block w-full h-full cursor-zoom-in')}
63
63
  tabindex="0"
64
- style={`background-image: url('${image.src}')`}
65
64
  data-yt-zoom-button
65
+ data-yt-zoom-src={image.src}
66
66
  >
67
67
  <span>
68
68
  {!image ? (
@@ -170,6 +170,7 @@ if (
170
170
  span.classList.remove('zoom-wrap');
171
171
  zoomButton.classList.remove('cursor-zoom-out', 'bg-zoomed');
172
172
  zoomButton.classList.add('cursor-zoom-in');
173
+ zoomButton.style.backgroundImage = 'none';
173
174
  zoomButton.style.backgroundSize = '100%';
174
175
  zoomButton.style.backgroundPosition = '0% 0%';
175
176
 
@@ -194,6 +195,11 @@ if (
194
195
  zoomed = !zoomed;
195
196
 
196
197
  if (zoomed) {
198
+ // Set background-image only when zooming in
199
+ const zoomSrc = zoomButton.dataset.ytZoomSrc;
200
+ if (zoomSrc) {
201
+ zoomButton.style.backgroundImage = `url('${zoomSrc}')`;
202
+ }
197
203
  image.classList.add('hidden');
198
204
  span.classList.add('zoom-wrap');
199
205
  zoomButton.classList.add('cursor-zoom-out', 'bg-zoomed');
@@ -64,6 +64,7 @@
64
64
  if (zoomButtonRef) {
65
65
  zoomButtonRef.classList.remove('cursor-zoom-out', 'bg-zoomed');
66
66
  zoomButtonRef.classList.add('cursor-zoom-in');
67
+ zoomButtonRef.style.backgroundImage = 'none';
67
68
  zoomButtonRef.style.backgroundSize = '100%';
68
69
  zoomButtonRef.style.backgroundPosition = '0% 0%';
69
70
  }
@@ -87,6 +88,11 @@
87
88
  if (imageRef) imageRef.classList.add('hidden');
88
89
  if (spanRef) spanRef.classList.add('zoom-wrap');
89
90
  if (zoomButtonRef) {
91
+ // Set background-image only when zooming in
92
+ const zoomSrc = zoomButtonRef.dataset.ytZoomSrc;
93
+ if (zoomSrc) {
94
+ zoomButtonRef.style.backgroundImage = `url('${zoomSrc}')`;
95
+ }
90
96
  zoomButtonRef.classList.add('cursor-zoom-out', 'bg-zoomed');
91
97
  zoomButtonRef.classList.remove('cursor-zoom-in');
92
98
  zoomButtonRef.style.backgroundSize = '200%';
@@ -139,8 +145,8 @@
139
145
  type="button"
140
146
  class={cn('zoom-bg block w-full h-full cursor-zoom-in')}
141
147
  tabindex="0"
142
- style={`background-image: url('${src}')`}
143
148
  data-yt-zoom-button
149
+ data-yt-zoom-src={src}
144
150
  >
145
151
  <span bind:this={spanRef}>
146
152
  <img
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yatoday/astro-ui",
3
3
  "type": "module",
4
- "version": "0.17.2",
4
+ "version": "0.17.5",
5
5
  "scripts": {
6
6
  "prepare": "husky",
7
7
  "pre-commit": "lint-staged",
package/styles/styles.css CHANGED
@@ -1055,6 +1055,10 @@
1055
1055
  border-style: var(--tw-border-style);
1056
1056
  border-width: 2px;
1057
1057
  }
1058
+ .border-4 {
1059
+ border-style: var(--tw-border-style);
1060
+ border-width: 4px;
1061
+ }
1058
1062
  .border-t {
1059
1063
  border-top-style: var(--tw-border-style);
1060
1064
  border-top-width: 1px;
@@ -1,28 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { themeVariants } from '../theme-variants';
3
-
4
- describe('Button Theme Variants', () => {
5
- it('has the correct default variant', () => {
6
- const classes = themeVariants({ variant: 'default' });
7
- expect(classes).toContain('bg-secondary');
8
- expect(classes).toContain('text-secondary-foreground');
9
- });
10
-
11
- it('has the correct primary variant', () => {
12
- const classes = themeVariants({ variant: 'primary' });
13
- expect(classes).toContain('bg-primary');
14
- expect(classes).toContain('text-primary-foreground');
15
- });
16
-
17
- it('has the correct size classes for default size', () => {
18
- const classes = themeVariants({ size: 'default' });
19
- expect(classes).toContain('px-6');
20
- expect(classes).toContain('h-10');
21
- });
22
-
23
- it('has the correct size classes for small size', () => {
24
- const classes = themeVariants({ size: 'sm' });
25
- expect(classes).toContain('h-8');
26
- expect(classes).toContain('px-4');
27
- });
28
- });