@yatoday/astro-ui 0.6.13 → 0.7.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.
package/astro.d.ts CHANGED
@@ -16,6 +16,7 @@ import type { DarkModeProps as YtDarkModeProps } from './components/DarkMode/typ
16
16
  import type { HeadlineProps as YtHeadlineProps } from './components/Headline/types'
17
17
  import type { HeroSectionProps as YtHeroSectionProps } from './components/HeroSection/types'
18
18
  import type { ImageProps as YtImageProps } from './components/Image/types'
19
+ import type { ImageGalleryIkeaProps as YtImageGalleryIkeaProps } from './components/ImageGalleryIkea/types'
19
20
  import type { ItemGrid0Props as YtItemGrid0Props } from './components/ItemGrid0/types'
20
21
  import type { LayoutProps as YtLayoutProps } from './components/Layout/types'
21
22
  import type { MetadataProps as YtMetadataProps } from './components/Metadata/types'
@@ -64,6 +65,7 @@ declare module '@yatoday/astro-ui/astro' {
64
65
  export function Headline(_props: YtHeadlineProps): any
65
66
  export function HeroSection(_props: YtHeroSectionProps): any
66
67
  export function Image(_props: YtImageProps): any
68
+ export function ImageGalleryIkea(_props: YtImageGalleryIkeaProps): any
67
69
  export function ItemGrid0(_props: YtItemGrid0Props): any
68
70
  export function Layout(_props: YtLayoutProps): any
69
71
  export function Metadata(_props: YtMetadataProps): any
@@ -111,6 +113,7 @@ declare module '@yatoday/astro-ui/astro' {
111
113
  export type HeadlineProps = YtHeadlineProps
112
114
  export type HeroSectionProps = YtHeroSectionProps
113
115
  export type ImageProps = YtImageProps
116
+ export type ImageGalleryIkeaProps = YtImageGalleryIkeaProps
114
117
  export type ItemGrid0Props = YtItemGrid0Props
115
118
  export type LayoutProps = YtLayoutProps
116
119
  export type MetadataProps = YtMetadataProps
package/astro.js CHANGED
@@ -16,6 +16,7 @@ import DarkModeComponent from './components/DarkMode/DarkMode.astro'
16
16
  import HeadlineComponent from './components/Headline/Headline.astro'
17
17
  import HeroSectionComponent from './components/HeroSection/HeroSection.astro'
18
18
  import ImageComponent from './components/Image/Image.astro'
19
+ import ImageGalleryIkeaComponent from './components/ImageGalleryIkea/ImageGalleryIkea.astro'
19
20
  import ItemGrid0Component from './components/ItemGrid0/ItemGrid0.astro'
20
21
  import LayoutComponent from './components/Layout/Layout.astro'
21
22
  import MetadataComponent from './components/Metadata/Metadata.astro'
@@ -63,6 +64,7 @@ export const DarkMode = DarkModeComponent
63
64
  export const Headline = HeadlineComponent
64
65
  export const HeroSection = HeroSectionComponent
65
66
  export const Image = ImageComponent
67
+ export const ImageGalleryIkea = ImageGalleryIkeaComponent
66
68
  export const ItemGrid0 = ItemGrid0Component
67
69
  export const Layout = LayoutComponent
68
70
  export const Metadata = MetadataComponent
@@ -9,9 +9,9 @@ import { cn } from '../../utils';
9
9
 
10
10
  const {
11
11
  title = await Astro.slots.render('title'),
12
- description,
13
- icon,
12
+ description = await Astro.slots.render('description'),
14
13
  image = await Astro.slots.render('image'),
14
+ icon,
15
15
  data = [],
16
16
  links = [],
17
17
  callToAction = await Astro.slots.render('actions'),
@@ -32,11 +32,13 @@ const {
32
32
  action: actionClass = '',
33
33
  } = classes;
34
34
 
35
- const img = {
36
- src: typeof image === 'string' ? image : image?.src,
37
- alt: typeof image === 'string' ? title : image?.alt,
38
- aspectRatio: typeof image === 'string' ? '4:3' : image?.aspectRatio || '4:3',
39
- };
35
+ const urlForImage = Array.isArray(callToAction)
36
+ ? typeof callToAction[0] === 'string'
37
+ ? callToAction[0]
38
+ : (callToAction[0] as { href?: string })?.href
39
+ : typeof callToAction === 'string'
40
+ ? callToAction
41
+ : (callToAction as { href?: string })?.href;
40
42
  ---
41
43
 
42
44
  <Card0
@@ -50,19 +52,43 @@ const img = {
50
52
  {
51
53
  image && (
52
54
  <div class={twMerge('w-full overflow-hidden -mt-6 h-60 bg-gray-400 dark:bg-zinc-700', imageClass)}>
53
- <Image
54
- src={img.src || String(image)}
55
- class={twMerge('w-full h-full')}
56
- widths={[400, 900]}
57
- width={400}
58
- height={400}
59
- sizes="(max-width: 900px) 400px, 900px"
60
- alt={img.alt || title}
61
- aspectRatio={img.aspectRatio}
62
- layout="cover"
63
- loading="lazy"
64
- decoding="async"
65
- />
55
+ {urlForImage ? (
56
+ <a href={urlForImage} class="group">
57
+ {typeof image === 'string' ? (
58
+ <Fragment set:html={image} />
59
+ ) : (
60
+ <Image
61
+ class={twMerge('w-full h-full hover:scale-105 transition duration-300')}
62
+ widths={[400, 900]}
63
+ width={400}
64
+ height={400}
65
+ sizes="(max-width: 900px) 400px, 900px"
66
+ layout="cover"
67
+ loading="lazy"
68
+ decoding="async"
69
+ {...image}
70
+ />
71
+ )}
72
+ </a>
73
+ ) : (
74
+ <Fragment>
75
+ {typeof image === 'string' ? (
76
+ <Fragment set:html={image} />
77
+ ) : (
78
+ <Image
79
+ class={twMerge('w-full h-full')}
80
+ widths={[400, 900]}
81
+ width={400}
82
+ height={400}
83
+ sizes="(max-width: 900px) 400px, 900px"
84
+ layout="cover"
85
+ loading="lazy"
86
+ decoding="async"
87
+ {...image}
88
+ />
89
+ )}
90
+ </Fragment>
91
+ )}
66
92
  </div>
67
93
  )
68
94
  }
@@ -83,7 +109,7 @@ const img = {
83
109
  {title}
84
110
  </WrapperHeaderTag>
85
111
 
86
- <div class={twMerge('mt-2', descriptionClass)}>
112
+ <div class={twMerge('mt-1', descriptionClass)}>
87
113
  <slot />
88
114
  {description}
89
115
  </div>
@@ -8,7 +8,7 @@ import { cn } from '../../utils';
8
8
 
9
9
  const {
10
10
  title = await Astro.slots.render('title'),
11
- description,
11
+ description = await Astro.slots.render('description'),
12
12
  icon,
13
13
  image = await Astro.slots.render('image'),
14
14
  url = '',
@@ -29,12 +29,6 @@ const {
29
29
  image: imageClass = '',
30
30
  badge: badgeClass = 'top-2 left-2',
31
31
  } = classes;
32
-
33
- const img = {
34
- src: typeof image === 'string' ? image : image?.src,
35
- alt: typeof image === 'string' ? title : image?.alt,
36
- aspectRatio: typeof image === 'string' ? '4:3' : image?.aspectRatio || '4:3',
37
- };
38
32
  ---
39
33
 
40
34
  <Card0
@@ -50,19 +44,25 @@ const img = {
50
44
  {
51
45
  image && (
52
46
  <div class={twMerge('w-full overflow-hidden -mt-6 h-40 bg-gray-400 dark:bg-zinc-700', imageClass)}>
53
- <Image
54
- src={img.src || String(image)}
55
- class={cn('w-full md:h-full', imageClass, (url || callToAction?.href) && 'group-hover:scale-105 transition duration-300')}
56
- widths={[400, 900]}
57
- width={400}
58
- height={400}
59
- sizes="(max-width: 900px) 400px, 900px"
60
- alt={img.alt || title}
61
- aspectRatio={img.aspectRatio}
62
- layout="cover"
63
- loading="lazy"
64
- decoding="async"
65
- />
47
+ {typeof image === 'string' ? (
48
+ <Fragment set:html={image} />
49
+ ) : (
50
+ <Image
51
+ class={cn(
52
+ 'w-full md:h-full',
53
+ imageClass,
54
+ (url || callToAction?.href) && 'group-hover:scale-105 transition duration-300'
55
+ )}
56
+ widths={[400, 900]}
57
+ width={400}
58
+ height={400}
59
+ sizes="(max-width: 900px) 400px, 900px"
60
+ layout="cover"
61
+ loading="lazy"
62
+ decoding="async"
63
+ {...image}
64
+ />
65
+ )}
66
66
  </div>
67
67
  )
68
68
  }
@@ -6,12 +6,13 @@ const {
6
6
  title = await Astro.slots.render('title'),
7
7
  subtitle = await Astro.slots.render('subtitle'),
8
8
  tagline,
9
+ position = 'center',
9
10
  classes = {},
10
11
  as = 'h2',
11
12
  } = Astro.props;
12
13
 
13
14
  const {
14
- container: containerClass = 'max-w-3xl md:mx-auto ',
15
+ container: containerClass = '',
15
16
  title: titleClass = 'text-foreground',
16
17
  subtitle: subtitleClass = '',
17
18
  tagline: taglineClass = '',
@@ -22,24 +23,34 @@ const WrapperTag = as;
22
23
 
23
24
  {
24
25
  (title || subtitle || tagline) && (
25
- <div class={cn('mb-8 md:mb-12 text-center', containerClass)}>
26
+ <div
27
+ class={cn(
28
+ 'mb-8 md:mb-12',
29
+ position === 'center' && 'text-center max-w-3xl md:mx-auto',
30
+ position === 'left' && 'text-left max-w-3xl',
31
+ position === 'right' && 'text-right',
32
+ containerClass
33
+ )}
34
+ >
26
35
  {tagline && (
27
36
  <p
28
- class={cn('text-base text-muted-foreground font-bold tracking-wide uppercase', taglineClass)}
37
+ class={cn('text-sm md:text-base text-muted-foreground font-bold tracking-wide uppercase', taglineClass)}
29
38
  set:html={tagline}
30
39
  />
31
40
  )}
32
41
  {title && (
33
42
  <WrapperTag
34
43
  class={cn(
35
- 'font-bold leading-tighter tracking-tighter font-heading text-heading text-3xl md:text-4xl',
44
+ 'font-bold leading-tighter tracking-tighter font-heading text-heading text-2xl md:text-4xl',
36
45
  titleClass
37
46
  )}
38
47
  set:html={title}
39
48
  />
40
49
  )}
41
50
 
42
- {subtitle && <p class={cn('mt-4 text-muted-foreground text-xl', subtitleClass)} set:html={subtitle} />}
51
+ {subtitle && (
52
+ <p class={cn('mt-1 md:mt-4 text-muted-foreground text-lg/6 md:text-xl', subtitleClass)} set:html={subtitle} />
53
+ )}
43
54
  </div>
44
55
  )
45
56
  }
@@ -6,4 +6,5 @@ export type HeadlineProps = {
6
6
  tagline?: string;
7
7
  classes?: Record<string, string>;
8
8
  ['as']?: HTMLTag;
9
+ position?: 'center' | 'left' | 'right';
9
10
  };
@@ -4,29 +4,32 @@ import type { HeroSectionProps as Props } from './types';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import Image from '../Image/Image.astro';
6
6
  import Button from '../Button/Button.astro';
7
+ import { cn } from '../../utils';
7
8
 
8
9
  const {
9
10
  asHeader = 'h3',
10
11
  classes = {},
11
- image,
12
+ image = await Astro.slots.render('image'),
12
13
  title = await Astro.slots.render('title'),
13
14
  description = await Astro.slots.render('description'),
14
15
  callToAction,
15
16
  } = Astro.props;
16
17
 
17
- const img = {
18
- src: typeof image === 'string' ? image : image?.src,
19
- alt: typeof image === 'string' ? title : image?.alt,
20
- aspectRatio: typeof image === 'string' ? '16:9' : image?.aspectRatio || '16:9',
21
- };
18
+ const urlForImage = Array.isArray(callToAction)
19
+ ? typeof callToAction[0] === 'string'
20
+ ? callToAction[0]
21
+ : (callToAction[0] as { href?: string })?.href
22
+ : typeof callToAction === 'string'
23
+ ? callToAction
24
+ : (callToAction as { href?: string })?.href;
22
25
 
23
26
  const {
24
- container: containerClass = 'bg-zinc-700 text-zinc-200 h-96',
27
+ container: containerClass = 'h-96',
25
28
  image: imageClass = '',
26
29
  content: contentClass = '',
27
30
  title: titleClass = '',
28
31
  description: descriptionClass = '',
29
- action: actionClass = 'w-full',
32
+ action: actionClass = '',
30
33
  } = classes;
31
34
 
32
35
  const WrapperHeaderTag = asHeader;
@@ -34,33 +37,48 @@ const WrapperHeaderTag = asHeader;
34
37
 
35
38
  <div
36
39
  class={twMerge(
37
- 'swiper-slide overflow-hidden dark:bg-card dark:text-card-foreground',
40
+ 'swiper-slide overflow-hidden bg-zinc-700 text-zinc-200 dark:bg-card dark:text-card-foreground',
38
41
  containerClass
39
42
  )}
40
43
  >
41
44
  <div class="slide flex flex-col-reverse justify-between md:flex-row h-full">
42
45
  <div class="w-full flex items-center justify-center">
43
- <div class={twMerge("p-6 md:px-12 lg:px-20 xl:px-24 w-full h-full md:h-auto", contentClass)}>
46
+ <div class={twMerge('p-6 md:px-12 lg:px-20 xl:px-24 w-full h-full md:h-auto', contentClass)}>
44
47
  <slot />
45
48
  {
46
49
  title && description && (
47
50
  <div class="flex flex-col h-full justify-between">
48
51
  <div class="mb-6">
49
52
  <WrapperHeaderTag
50
- class={twMerge(
51
- 'text-base md:text-lg xl:text-2xl font-medium md:font-semibold mb-2',
52
- titleClass
53
- )}
53
+ class={twMerge('text-base md:text-lg xl:text-2xl font-medium md:font-semibold mb-2', titleClass)}
54
54
  >
55
55
  {title}
56
56
  </WrapperHeaderTag>
57
57
  <p class={twMerge('text-sm md:text-base xl:text-lg', descriptionClass)}>{description}</p>
58
58
  </div>
59
+
59
60
  {callToAction && (
60
- <Button
61
- class={twMerge('transition hover:translate-y-0.5 w-full', actionClass)}
62
- {...callToAction}
63
- />
61
+ <div class={cn('w-full flex items-center gap-2', actionClass)}>
62
+ {Array.isArray(callToAction) ? (
63
+ callToAction.map((action) => (
64
+ <Fragment>
65
+ {typeof action === 'string' ? (
66
+ <Fragment set:html={action} />
67
+ ) : (
68
+ <Button {...(action || {})} class="w-full sm:mb-0" />
69
+ )}
70
+ </Fragment>
71
+ ))
72
+ ) : (
73
+ <Fragment>
74
+ {typeof callToAction === 'string' ? (
75
+ <Fragment set:html={callToAction} />
76
+ ) : (
77
+ <Button {...(callToAction as Object)} class="w-full sm:mb-0" />
78
+ )}
79
+ </Fragment>
80
+ )}
81
+ </div>
64
82
  )}
65
83
  </div>
66
84
  )
@@ -69,36 +87,44 @@ const WrapperHeaderTag = asHeader;
69
87
  </div>
70
88
  <div class="w-full md:h-auto">
71
89
  {
72
- img && (
90
+ image && (
73
91
  <Fragment>
74
- {callToAction?.href ? (
75
- <a href={callToAction?.href}>
76
- <Image
77
- src={img.src}
78
- class={twMerge('w-full md:h-full bg-gray-400 dark:bg-slate-700', imageClass)}
79
- widths={[400, 900]}
80
- width={400}
81
- sizes="(max-width: 900px) 400px, 900px"
82
- alt={img.alt}
83
- aspectRatio={img.aspectRatio}
84
- layout="cover"
85
- loading="lazy"
86
- decoding="async"
87
- />
92
+ {urlForImage ? (
93
+ <a href={urlForImage} class="group">
94
+ {typeof image === 'string' ? (
95
+ <Fragment set:html={image} />
96
+ ) : (
97
+ <Image
98
+ class={twMerge('w-full md:h-full bg-gray-400 dark:bg-slate-700', imageClass)}
99
+ widths={[400, 900]}
100
+ width={400}
101
+ height={400}
102
+ sizes="(max-width: 900px) 400px, 900px"
103
+ layout="cover"
104
+ loading="lazy"
105
+ decoding="async"
106
+ {...image}
107
+ />
108
+ )}
88
109
  </a>
89
110
  ) : (
90
- <Image
91
- src={img.src}
92
- class={twMerge('w-full md:h-full bg-gray-400 dark:bg-slate-700', imageClass)}
93
- widths={[400, 900]}
94
- width={400}
95
- sizes="(max-width: 900px) 400px, 900px"
96
- alt={img.alt}
97
- aspectRatio={img.aspectRatio}
98
- layout="cover"
99
- loading="lazy"
100
- decoding="async"
101
- />
111
+ <Fragment>
112
+ {typeof image === 'string' ? (
113
+ <Fragment set:html={image} />
114
+ ) : (
115
+ <Image
116
+ class={twMerge('w-full h-full')}
117
+ widths={[400, 900]}
118
+ width={400}
119
+ height={400}
120
+ sizes="(max-width: 900px) 400px, 900px"
121
+ layout="cover"
122
+ loading="lazy"
123
+ decoding="async"
124
+ {...image}
125
+ />
126
+ )}
127
+ </Fragment>
102
128
  )}
103
129
  </Fragment>
104
130
  )
@@ -6,6 +6,6 @@ export type HeroSectionProps = {
6
6
  title?: string;
7
7
  description?: string;
8
8
  image: Image | string;
9
- callToAction?: ToAction;
9
+ callToAction?: string | ToAction | Array<string | ToAction>;
10
10
  classes?: Record<string, string>;
11
11
  };
@@ -0,0 +1,216 @@
1
+ ---
2
+ import type { ImageGalleryIkeaProps as Props } from './types';
3
+ import { cn } from '../../utils';
4
+ import { Icon } from 'astro-icon/components';
5
+ import ZoomedImage from '../ZoomedImage/ZoomedImage.astro';
6
+ import Image from '../Image/Image.astro';
7
+
8
+ const {
9
+ id = (Math.random() + 1).toString(36).substring(7),
10
+ withNavigation = true,
11
+ classes = {},
12
+ images = [],
13
+ height = '',
14
+ ...rest
15
+ } = Astro.props;
16
+
17
+ const {
18
+ container: containerClass = '',
19
+ swiper: swiperClass = '',
20
+ swiperThumb: swiperThumbClass = '',
21
+ } = classes;
22
+ ---
23
+
24
+ <style>
25
+ .swiper-thumb .swiper-slide-thumb-active {
26
+ opacity: 1;
27
+ border: 1px solid #666;
28
+ }
29
+ </style>
30
+
31
+ <div class={cn("@container relative", containerClass)} data-image-gallery-ikea={id}>
32
+ <div class="absolute left-0 top-0 z-20 hidden md:block group w-20">
33
+ <!-- Thumb Slider -->
34
+ <button
35
+ disabled="disabled"
36
+ type="button"
37
+ id={`btn-prev-${id}`}
38
+ class="absolute top-0 left-1/2 -translate-x-1/2 transform -translate-y-1/3 btn-shadow opacity-0 group-hover:not-disabled:opacity-90 hover:not-disabled:opacity-100 hidden md:flex items-center justify-center cursor-pointer z-10 size-8 bg-black text-white dark:bg-white dark:text-black rounded-full"
39
+ >
40
+ <Icon name="tabler:chevron-up" class="size-7" />
41
+ </button>
42
+ <button
43
+ type="button"
44
+ id={`btn-next-${id}`}
45
+ class="absolute bottom-0 left-1/2 -translate-x-1/2 transform translate-y-1/3 btn-shadow opacity-0 group-hover:not-disabled:opacity-90 hover:not-disabled:opacity-100 hidden md:flex items-center justify-center cursor-pointer z-10 size-8 bg-black text-white dark:bg-white dark:text-black rounded-full"
46
+ >
47
+ <Icon name="tabler:chevron-down" class="size-7" />
48
+ </button>
49
+
50
+ <swiper-container
51
+ id={`swiper-thumb-${id}`}
52
+ data-id={id}
53
+ direction="vertical"
54
+ class={cn(`swiper-thumb border-box ${swiperThumbClass}`, height)}
55
+ space-between="10"
56
+ slides-per-view="auto"
57
+ free-mode="true"
58
+ watch-slides-progress="true"
59
+ navigation={withNavigation
60
+ ? JSON.stringify({
61
+ nextEl: `#btn-next-${id}`,
62
+ prevEl: `#btn-prev-${id}`,
63
+ })
64
+ : false}
65
+ })}
66
+ >
67
+ {
68
+ images &&
69
+ images.map((image) => (
70
+ <swiper-slide class="cursor-pointer border border-input aspect-square size-20">
71
+ <Image
72
+ widths={[200, 400]}
73
+ width={200}
74
+ height={200}
75
+ sizes="(max-width: 400px) 200px, 400px"
76
+ layout="constrained"
77
+ loading="lazy"
78
+ decoding="async"
79
+ {...image}
80
+ />
81
+ </swiper-slide>
82
+ ))
83
+ }
84
+ </swiper-container>
85
+ </div>
86
+
87
+ <!-- Main slider -->
88
+ <div class="md:pl-24">
89
+ <div class="relative group">
90
+ <swiper-container
91
+ id={`swiper-main-${id}`}
92
+ init="false"
93
+ class={cn('', swiperClass, height)}
94
+ thumbs-swiper={`#swiper-thumb-${id}`}
95
+ speed="250"
96
+ space-between="10"
97
+ centered-slides="true"
98
+ slides-per-view="1.2"
99
+ navigation={withNavigation
100
+ ? JSON.stringify({
101
+ nextEl: `#btn-main-next-${id}`,
102
+ prevEl: `#btn-main-prev-${id}`,
103
+ })
104
+ : false}
105
+ breakpoints={JSON.stringify({
106
+ // 640: sm
107
+ // 768: sm
108
+ // 1024: sm
109
+ // 1280 xl
110
+ // 1536 2xl
111
+ 640: {
112
+ speed: 0,
113
+ centeredSlides: false,
114
+ slidesPerView: 1,
115
+ },
116
+ })}
117
+ {...rest}
118
+ >
119
+ {
120
+ images &&
121
+ images.map((image, index) => (
122
+ <swiper-slide>
123
+ <ZoomedImage {...image} />
124
+ </swiper-slide>
125
+ ))
126
+ }
127
+ </swiper-container>
128
+
129
+ {
130
+ withNavigation && (
131
+ <Fragment>
132
+ <button
133
+ type="button"
134
+ id={`btn-main-prev-${id}`}
135
+ class="btn-shadow opacity-0 group-hover:not-disabled:opacity-90 hover:not-disabled:opacity-100 hidden md:flex items-center justify-center cursor-pointer absolute left-5 top-1/2 -translate-y-1/2 z-10 size-12 bg-black text-white dark:bg-white dark:text-black rounded-full transition-opacity"
136
+ >
137
+ <Icon name="tabler:chevron-left" class="size-7" />
138
+ </button>
139
+ <button
140
+ type="button"
141
+ id={`btn-main-next-${id}`}
142
+ class="btn-shadow opacity-0 group-hover:not-disabled:opacity-90 hover:not-disabled:opacity-100 hidden md:flex items-center justify-center cursor-pointer absolute right-5 top-1/2 -translate-y-1/2 z-10 size-12 bg-black text-white dark:bg-white dark:text-black rounded-full transition-opacity"
143
+ >
144
+ <Icon name="tabler:chevron-right" class="size-7" />
145
+ </button>
146
+ </Fragment>
147
+ )
148
+ }
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <script>
154
+ import { on, get } from '../../utils';
155
+
156
+ import { register } from 'swiper/element/bundle';
157
+
158
+ register();
159
+
160
+ const init = () => {
161
+ const matches = document.querySelectorAll('div[data-image-gallery-ikea]');
162
+ if (!matches.length) return;
163
+
164
+ matches.forEach((elem) => {
165
+ const id = (elem as HTMLElement).dataset.imageGalleryIkea || '0';
166
+
167
+ // Find the Swiper containers
168
+ const mainSwiperEl = document.getElementById(`swiper-main-${id}`);
169
+ const thumbSwiperEl = document.getElementById(`swiper-thumb-${id}`);
170
+ if (!mainSwiperEl || !thumbSwiperEl) return;
171
+
172
+ mainSwiperEl.addEventListener('swiperafterinit', () => {
173
+ (thumbSwiperEl as any)?.swiper.slides.forEach((slide: HTMLElement, index: number) => {
174
+ slide.addEventListener('mouseenter', () => {
175
+ (mainSwiperEl as any)?.swiper.slideTo(index);
176
+ });
177
+ });
178
+ });
179
+
180
+ // Thumb Slider navigation buttons
181
+ const btnThumbPrev = get(`#btn-prev-${id}`) as HTMLButtonElement;
182
+ const btnThumbNext = get(`#btn-next-${id}`) as HTMLButtonElement;
183
+ thumbSwiperEl.addEventListener('swiperreachend', () => {
184
+ btnThumbNext.disabled = true;
185
+ });
186
+ thumbSwiperEl.addEventListener('swiperreachbeginning', () => {
187
+ btnThumbPrev.disabled = true;
188
+ });
189
+ thumbSwiperEl.addEventListener('swiperfromedge', () => {
190
+ btnThumbPrev.disabled = false;
191
+ btnThumbNext.disabled = false;
192
+ });
193
+
194
+ // Main Slider navigation buttons
195
+ const btnPrev = get(`#btn-main-prev-${id}`) as HTMLButtonElement;
196
+ const btnNext = get(`#btn-main-next-${id}`) as HTMLButtonElement;
197
+ mainSwiperEl.addEventListener('swiperreachend', () => {
198
+ btnNext.disabled = true;
199
+ });
200
+ mainSwiperEl.addEventListener('swiperreachbeginning', () => {
201
+ btnPrev.disabled = true;
202
+ });
203
+ mainSwiperEl.addEventListener('swiperfromedge', () => {
204
+ btnPrev.disabled = false;
205
+ btnNext.disabled = false;
206
+ });
207
+
208
+ Object.assign(mainSwiperEl, {});
209
+
210
+ mainSwiperEl?.initialize();
211
+ });
212
+ };
213
+
214
+ init();
215
+ on(document, 'astro:after-swap', init);
216
+ </script>
@@ -0,0 +1,3 @@
1
+ <script lang="ts">
2
+ import type { ImageGalleryIkeaProps } from './types'
3
+ </script>
@@ -0,0 +1,9 @@
1
+ import type { Image } from '../../types';
2
+
3
+ export type ImageGalleryIkeaProps = {
4
+ id?: string;
5
+ height?: string;
6
+ withNavigation?: boolean;
7
+ classes?: Record<string, string>;
8
+ images?: Image[];
9
+ };
@@ -15,8 +15,8 @@ const {
15
15
  <swiper-container
16
16
  data-id={id}
17
17
  class={cn('opacity-100', classes?.swiper)}
18
- centered-slides="true"
19
18
  space-between="10"
19
+ centered-slides="true"
20
20
  slides-per-view="1.2"
21
21
  navigation={withNavigation
22
22
  ? JSON.stringify({
@@ -75,7 +75,6 @@ const {
75
75
 
76
76
  <script>
77
77
  import { addClasses, removeClasses } from '../../utils';
78
-
79
78
  import { register } from 'swiper/element/bundle';
80
79
 
81
80
  register();
@@ -85,29 +84,31 @@ const {
85
84
  matches.forEach((elem) => {
86
85
  const id = elem.dataset.id;
87
86
 
88
- elem.addEventListener(
89
- 'swiperreachend',
90
- () => {
91
- removeClasses(`#btn-next-${id}`, 'opacity-100');
92
- addClasses(`#btn-next-${id}`, 'opacity-0');
93
- },
94
- false
95
- );
96
- elem.addEventListener(
97
- 'swiperreachbeginning',
98
- () => {
99
- addClasses(`#btn-prev-${id}`, 'opacity-0');
100
- },
101
- false
102
- );
103
- elem.addEventListener(
104
- 'swiperfromedge',
105
- () => {
106
- removeClasses(`#btn-prev-${id}`, 'opacity-0');
107
- addClasses(`#btn-next-${id}`, 'opacity-100');
108
- },
109
- false
110
- );
87
+ if (id) {
88
+ elem.addEventListener(
89
+ 'swiperreachend',
90
+ () => {
91
+ removeClasses(`#btn-next-${id}`, 'opacity-100');
92
+ addClasses(`#btn-next-${id}`, 'opacity-0');
93
+ },
94
+ false
95
+ );
96
+ elem.addEventListener(
97
+ 'swiperreachbeginning',
98
+ () => {
99
+ addClasses(`#btn-prev-${id}`, 'opacity-0');
100
+ },
101
+ false
102
+ );
103
+ elem.addEventListener(
104
+ 'swiperfromedge',
105
+ () => {
106
+ removeClasses(`#btn-prev-${id}`, 'opacity-0');
107
+ addClasses(`#btn-next-${id}`, 'opacity-100');
108
+ },
109
+ false
110
+ );
111
+ }
111
112
  });
112
113
  }
113
114
  </script>
@@ -15,7 +15,7 @@ const {
15
15
  tagline = await Astro.slots.render('tagline'),
16
16
  items = [],
17
17
  columns = 4,
18
- image,
18
+ image = await Astro.slots.render('image'),
19
19
 
20
20
  isBeforeContent,
21
21
  isAfterContent,
@@ -26,12 +26,6 @@ const {
26
26
  } = Astro.props;
27
27
 
28
28
  const Component = via;
29
-
30
- const img = {
31
- src: typeof image === 'string' ? image : image?.src,
32
- alt: typeof image === 'string' ? title : image?.alt,
33
- aspectRatio: typeof image === 'string' ? '16:9' : image?.aspectRatio || '16:9',
34
- };
35
29
  ---
36
30
 
37
31
  <WidgetWrapper
@@ -53,15 +47,18 @@ const img = {
53
47
  classes?.image
54
48
  )}
55
49
  >
56
- <Image
57
- class="w-full h-80 object-cover mx-auto bg-gray-500 shadow-lg"
58
- width="auto"
59
- height={320}
60
- widths={[400, 768]}
61
- layout="fullWidth"
62
- src={img.src || String(image)}
63
- alt={img.alt || title || 'Demo'}
64
- />
50
+ {typeof image === 'string' ? (
51
+ <Fragment set:html={image} />
52
+ ) : (
53
+ <Image
54
+ class="w-full h-80 object-cover mx-auto bg-gray-500 shadow-lg"
55
+ width="auto"
56
+ height={320}
57
+ widths={[400, 768]}
58
+ layout="fullWidth"
59
+ {...image}
60
+ />
61
+ )}
65
62
  </div>
66
63
  </div>
67
64
  )
@@ -12,7 +12,7 @@ const {
12
12
  subtitle = await Astro.slots.render('subtitle'),
13
13
  tagline = await Astro.slots.render('tagline'),
14
14
  position = 'center',
15
- image,
15
+ image = await Astro.slots.render('image'),
16
16
  actions = [],
17
17
  links = [],
18
18
  stats = [],
@@ -24,14 +24,6 @@ const {
24
24
  } = Astro.props;
25
25
 
26
26
  const { container: containerClass = '', bg: bgClass = '' } = classes;
27
-
28
- const img = image
29
- ? {
30
- src: typeof image === 'string' ? image : image?.src,
31
- alt: typeof image === 'string' ? title : image?.alt,
32
- aspectRatio: typeof image === 'string' ? '16:9' : image?.aspectRatio || '16:9',
33
- }
34
- : undefined;
35
27
  ---
36
28
 
37
29
  <WidgetWrapper
@@ -43,19 +35,24 @@ const img = image
43
35
  <div class="relative isolate overflow-hidden h-full">
44
36
  <div class={cn(' absolute top-0 left-0 w-full h-full z-20', bgClass)}></div>
45
37
  {
46
- img && (
47
- <Image
48
- src={img.src}
49
- class="absolute inset-0 z-10 size-full object-cover object-right md:object-center"
50
- alt={img.alt}
51
- aspectRatio={img.aspectRatio}
52
- widths={[400, 768, 1024, 2040]}
53
- sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
54
- loading="eager"
55
- width={1024}
56
- height={576}
57
- layout="fullWidth"
58
- />
38
+ image && (
39
+ <Fragment>
40
+ {typeof image === 'string' ? (
41
+ <Fragment set:html={image} />
42
+ ) : (
43
+ <Image
44
+ class="absolute inset-0 z-10 size-full object-cover object-right md:object-center"
45
+ widths={[400, 900]}
46
+ widths={[400, 768, 1024, 2040]}
47
+ sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
48
+ loading="eager"
49
+ width={1024}
50
+ height={576}
51
+ layout="fullWidth"
52
+ {...image}
53
+ />
54
+ )}
55
+ </Fragment>
59
56
  )
60
57
  }
61
58
  </div>
@@ -20,7 +20,7 @@ type ImageType = {
20
20
  attributes: HTMLAttributes<'img'>;
21
21
  };
22
22
 
23
- const _image = await findImage(props.src);
23
+ const _image = await findImage(src);
24
24
 
25
25
  let image: ImageType | undefined;
26
26
 
@@ -127,6 +127,9 @@ if (
127
127
 
128
128
  if (zoomed) {
129
129
  zoomButton.style.backgroundPosition = `${mousePosition.percentX}% ${mousePosition.percentY}%`;
130
+
131
+ // Add document click listener to detect outside clicks
132
+ document.addEventListener('click', handleOutsideClick);
130
133
  }
131
134
  };
132
135
 
package/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export type Image = {
36
36
  src: string;
37
37
  alt?: string;
38
38
  aspectRatio?: string;
39
+ class?: string;
39
40
  };
40
41
 
41
42
  export type Testimonial = {
@@ -66,6 +67,6 @@ declare module '@yatoday/astro-ui' {
66
67
  // URLUtils.ts
67
68
  export const trimSlash = (s: number) => string;
68
69
  export const createPath = (...params: string[]) => string;
69
- export const cleanSlug = (text: string[]) => string;
70
+ export const cleanSlug = (text: string) => string;
70
71
  export const getCanonical = (path: string) => string | URL;
71
72
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yatoday/astro-ui",
3
3
  "type": "module",
4
- "version": "0.6.13",
4
+ "version": "0.7.2",
5
5
  "scripts": {
6
6
  "prepare": "husky",
7
7
  "pre-commit": "lint-staged",
package/styles/styles.css CHANGED
@@ -303,6 +303,9 @@
303
303
  .-right-5 {
304
304
  right: calc(var(--spacing) * -5);
305
305
  }
306
+ .right-5 {
307
+ right: calc(var(--spacing) * 5);
308
+ }
306
309
  .bottom-0 {
307
310
  bottom: calc(var(--spacing) * 0);
308
311
  }
@@ -318,6 +321,9 @@
318
321
  .left-2 {
319
322
  left: calc(var(--spacing) * 2);
320
323
  }
324
+ .left-5 {
325
+ left: calc(var(--spacing) * 5);
326
+ }
321
327
  .isolate {
322
328
  isolation: isolate;
323
329
  }
@@ -390,6 +396,9 @@
390
396
  .-mt-12 {
391
397
  margin-top: calc(var(--spacing) * -12);
392
398
  }
399
+ .mt-1 {
400
+ margin-top: calc(var(--spacing) * 1);
401
+ }
393
402
  .mt-2 {
394
403
  margin-top: calc(var(--spacing) * 2);
395
404
  }
@@ -510,6 +519,10 @@
510
519
  width: calc(var(--spacing) * 7);
511
520
  height: calc(var(--spacing) * 7);
512
521
  }
522
+ .size-8 {
523
+ width: calc(var(--spacing) * 8);
524
+ height: calc(var(--spacing) * 8);
525
+ }
513
526
  .size-10 {
514
527
  width: calc(var(--spacing) * 10);
515
528
  height: calc(var(--spacing) * 10);
@@ -518,6 +531,10 @@
518
531
  width: calc(var(--spacing) * 12);
519
532
  height: calc(var(--spacing) * 12);
520
533
  }
534
+ .size-20 {
535
+ width: calc(var(--spacing) * 20);
536
+ height: calc(var(--spacing) * 20);
537
+ }
521
538
  .size-72 {
522
539
  width: calc(var(--spacing) * 72);
523
540
  height: calc(var(--spacing) * 72);
@@ -581,6 +598,9 @@
581
598
  .h-\[28rem\] {
582
599
  height: 28rem;
583
600
  }
601
+ .h-\[500px\] {
602
+ height: 500px;
603
+ }
584
604
  .h-\[900px\] {
585
605
  height: 900px;
586
606
  }
@@ -626,6 +646,9 @@
626
646
  .w-16 {
627
647
  width: calc(var(--spacing) * 16);
628
648
  }
649
+ .w-20 {
650
+ width: calc(var(--spacing) * 20);
651
+ }
629
652
  .w-24 {
630
653
  width: calc(var(--spacing) * 24);
631
654
  }
@@ -671,6 +694,9 @@
671
694
  .max-w-60 {
672
695
  max-width: calc(var(--spacing) * 60);
673
696
  }
697
+ .max-w-\[600px\] {
698
+ max-width: 600px;
699
+ }
674
700
  .max-w-none\! {
675
701
  max-width: none !important;
676
702
  }
@@ -711,6 +737,14 @@
711
737
  --tw-translate-y: calc(calc(1/2 * 100%) * -1);
712
738
  translate: var(--tw-translate-x) var(--tw-translate-y);
713
739
  }
740
+ .-translate-y-1\/3 {
741
+ --tw-translate-y: calc(calc(1/3 * 100%) * -1);
742
+ translate: var(--tw-translate-x) var(--tw-translate-y);
743
+ }
744
+ .translate-y-1\/3 {
745
+ --tw-translate-y: calc(1/3 * 100%);
746
+ translate: var(--tw-translate-x) var(--tw-translate-y);
747
+ }
714
748
  .scale-50 {
715
749
  --tw-scale-x: 50%;
716
750
  --tw-scale-y: 50%;
@@ -1205,6 +1239,10 @@
1205
1239
  font-size: var(--text-lg);
1206
1240
  line-height: calc(var(--spacing) * 5);
1207
1241
  }
1242
+ .text-lg\/6 {
1243
+ font-size: var(--text-lg);
1244
+ line-height: calc(var(--spacing) * 6);
1245
+ }
1208
1246
  .text-sm {
1209
1247
  font-size: var(--text-sm);
1210
1248
  line-height: var(--tw-leading, var(--text-sm--line-height));
@@ -1441,6 +1479,15 @@
1441
1479
  }
1442
1480
  }
1443
1481
  }
1482
+ .group-hover\:not-disabled\:opacity-90 {
1483
+ &:is(:where(.group):hover *) {
1484
+ @media (hover: hover) {
1485
+ &:not(*:disabled) {
1486
+ opacity: 90%;
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1444
1491
  .group-active\:scale-75 {
1445
1492
  &:is(:where(.group):active *) {
1446
1493
  --tw-scale-x: 75%;
@@ -1517,14 +1564,6 @@
1517
1564
  background-color: var(--color-slate-50);
1518
1565
  }
1519
1566
  }
1520
- .hover\:translate-y-0\.5 {
1521
- &:hover {
1522
- @media (hover: hover) {
1523
- --tw-translate-y: calc(var(--spacing) * 0.5);
1524
- translate: var(--tw-translate-x) var(--tw-translate-y);
1525
- }
1526
- }
1527
- }
1528
1567
  .hover\:scale-105 {
1529
1568
  &:hover {
1530
1569
  @media (hover: hover) {
@@ -1577,6 +1616,15 @@
1577
1616
  }
1578
1617
  }
1579
1618
  }
1619
+ .hover\:not-disabled\:opacity-100 {
1620
+ &:hover {
1621
+ @media (hover: hover) {
1622
+ &:not(*:disabled) {
1623
+ opacity: 100%;
1624
+ }
1625
+ }
1626
+ }
1627
+ }
1580
1628
  .focus\:ring-4 {
1581
1629
  &:focus {
1582
1630
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
@@ -1853,6 +1901,11 @@
1853
1901
  margin-top: calc(var(--spacing) * 0);
1854
1902
  }
1855
1903
  }
1904
+ .md\:mt-4 {
1905
+ @media (width >= 48rem) {
1906
+ margin-top: calc(var(--spacing) * 4);
1907
+ }
1908
+ }
1856
1909
  .md\:mt-12 {
1857
1910
  @media (width >= 48rem) {
1858
1911
  margin-top: calc(var(--spacing) * 12);
@@ -2120,6 +2173,11 @@
2120
2173
  padding-left: calc(var(--spacing) * 0);
2121
2174
  }
2122
2175
  }
2176
+ .md\:pl-24 {
2177
+ @media (width >= 48rem) {
2178
+ padding-left: calc(var(--spacing) * 24);
2179
+ }
2180
+ }
2123
2181
  .md\:text-left {
2124
2182
  @media (width >= 48rem) {
2125
2183
  text-align: left;
@@ -2536,6 +2594,11 @@
2536
2594
  background-color: var(--color-zinc-800);
2537
2595
  }
2538
2596
  }
2597
+ .dark\:text-black {
2598
+ &:where(.dark, .dark *) {
2599
+ color: var(--color-black);
2600
+ }
2601
+ }
2539
2602
  .dark\:text-gray-400 {
2540
2603
  &:where(.dark, .dark *) {
2541
2604
  color: var(--color-gray-400);
@@ -22,6 +22,8 @@
22
22
  --yt-color-border: hsl(0 0% 89.8%);
23
23
  --yt-color-input: hsl(0 0% 89.8%);
24
24
  --yt-color-ring: hsl(0 0% 3.9%);
25
+
26
+ color-scheme: light;
25
27
  }
26
28
 
27
29
  .dark {
package/svelte.d.ts CHANGED
@@ -17,6 +17,7 @@ import type { SvelteDarkModeProps as YtSvelteDarkModeProps } from './components/
17
17
  import type { SvelteHeadlineProps as YtSvelteHeadlineProps } from './components/Headline/types'
18
18
  import type { SvelteHeroSectionProps as YtSvelteHeroSectionProps } from './components/HeroSection/types'
19
19
  import type { SvelteImageProps as YtSvelteImageProps } from './components/Image/types'
20
+ import type { SvelteImageGalleryIkeaProps as YtSvelteImageGalleryIkeaProps } from './components/ImageGalleryIkea/types'
20
21
  import type { SvelteItemGrid0Props as YtSvelteItemGrid0Props } from './components/ItemGrid0/types'
21
22
  import type { SvelteLayoutProps as YtSvelteLayoutProps } from './components/Layout/types'
22
23
  import type { SvelteMetadataProps as YtSvelteMetadataProps } from './components/Metadata/types'
@@ -65,6 +66,7 @@ declare module '@yatoday/astro-ui/svelte' {
65
66
  export const Headline: Component<YtSvelteHeadlineProps>
66
67
  export const HeroSection: Component<YtSvelteHeroSectionProps>
67
68
  export const Image: Component<YtSvelteImageProps>
69
+ export const ImageGalleryIkea: Component<YtSvelteImageGalleryIkeaProps>
68
70
  export const ItemGrid0: Component<YtSvelteItemGrid0Props>
69
71
  export const Layout: Component<YtSvelteLayoutProps>
70
72
  export const Metadata: Component<YtSvelteMetadataProps>
@@ -112,6 +114,7 @@ declare module '@yatoday/astro-ui/svelte' {
112
114
  export type HeadlineProps = YtSvelteHeadlineProps
113
115
  export type HeroSectionProps = YtSvelteHeroSectionProps
114
116
  export type ImageProps = YtSvelteImageProps
117
+ export type ImageGalleryIkeaProps = YtSvelteImageGalleryIkeaProps
115
118
  export type ItemGrid0Props = YtSvelteItemGrid0Props
116
119
  export type LayoutProps = YtSvelteLayoutProps
117
120
  export type MetadataProps = YtSvelteMetadataProps
package/svelte.js CHANGED
@@ -16,6 +16,7 @@ import DarkModeComponent from './components/DarkMode/DarkMode.svelte'
16
16
  import HeadlineComponent from './components/Headline/Headline.svelte'
17
17
  import HeroSectionComponent from './components/HeroSection/HeroSection.svelte'
18
18
  import ImageComponent from './components/Image/Image.svelte'
19
+ import ImageGalleryIkeaComponent from './components/ImageGalleryIkea/ImageGalleryIkea.svelte'
19
20
  import ItemGrid0Component from './components/ItemGrid0/ItemGrid0.svelte'
20
21
  import LayoutComponent from './components/Layout/Layout.svelte'
21
22
  import MetadataComponent from './components/Metadata/Metadata.svelte'
@@ -63,6 +64,7 @@ export const DarkMode = DarkModeComponent
63
64
  export const Headline = HeadlineComponent
64
65
  export const HeroSection = HeroSectionComponent
65
66
  export const Image = ImageComponent
67
+ export const ImageGalleryIkea = ImageGalleryIkeaComponent
66
68
  export const ItemGrid0 = ItemGrid0Component
67
69
  export const Layout = LayoutComponent
68
70
  export const Metadata = MetadataComponent
package/utils/URLUtils.ts CHANGED
@@ -39,7 +39,7 @@ export const createPath = (...params: string[]): string => {
39
39
  export const cleanSlug = (text: string = ''): string =>
40
40
  trimSlash(text)
41
41
  .split('/')
42
- .map((a: string) => slugify(a))
42
+ .map((a: string) => slugify(a).toLowerCase())
43
43
  .join('/');
44
44
 
45
45
  /**
package/utils/slugify.ts CHANGED
@@ -28,8 +28,7 @@ function replace(string: string, options?: string | Options): string {
28
28
  throw new Error('slugify: string argument expected');
29
29
  }
30
30
 
31
- const resolvedOptions: Options =
32
- typeof options === 'string' ? { replacement: options } : options || {};
31
+ const resolvedOptions: Options = typeof options === 'string' ? { replacement: options } : options || {};
33
32
 
34
33
  const locale: LocaleMap = locales[resolvedOptions.locale || ''] || {};
35
34
  const replacement: string = resolvedOptions.replacement === undefined ? '-' : resolvedOptions.replacement;