@yatoday/astro-ui 0.9.0 → 0.10.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
@@ -12,6 +12,7 @@ import type { Card3Props as YtCard3Props } from './components/Card3/types'
12
12
  import type { Card4Props as YtCard4Props } from './components/Card4/types'
13
13
  import type { Card5Props as YtCard5Props } from './components/Card5/types'
14
14
  import type { Card6Props as YtCard6Props } from './components/Card6/types'
15
+ import type { Card7Props as YtCard7Props } from './components/Card7/types'
15
16
  import type { ConditionalWrapperProps as YtConditionalWrapperProps } from './components/ConditionalWrapper/types'
16
17
  import type { CopyToClipboardProps as YtCopyToClipboardProps } from './components/CopyToClipboard/types'
17
18
  import type { DarkModeProps as YtDarkModeProps } from './components/DarkMode/types'
@@ -23,6 +24,7 @@ import type { ItemGrid0Props as YtItemGrid0Props } from './components/ItemGrid0/
23
24
  import type { ItemGrid1Props as YtItemGrid1Props } from './components/ItemGrid1/types'
24
25
  import type { LayoutProps as YtLayoutProps } from './components/Layout/types'
25
26
  import type { MetadataProps as YtMetadataProps } from './components/Metadata/types'
27
+ import type { PointMapProps as YtPointMapProps } from './components/PointMap/types'
26
28
  import type { QuantitySwitchProps as YtQuantitySwitchProps } from './components/QuantitySwitch/types'
27
29
  import type { SiteVerificationProps as YtSiteVerificationProps } from './components/SiteVerification/types'
28
30
  import type { Stats0Props as YtStats0Props } from './components/Stats0/types'
@@ -64,6 +66,7 @@ declare module '@yatoday/astro-ui/astro' {
64
66
  export function Card4(_props: YtCard4Props): any
65
67
  export function Card5(_props: YtCard5Props): any
66
68
  export function Card6(_props: YtCard6Props): any
69
+ export function Card7(_props: YtCard7Props): any
67
70
  export function ConditionalWrapper(_props: YtConditionalWrapperProps): any
68
71
  export function CopyToClipboard(_props: YtCopyToClipboardProps): any
69
72
  export function DarkMode(_props: YtDarkModeProps): any
@@ -75,6 +78,7 @@ declare module '@yatoday/astro-ui/astro' {
75
78
  export function ItemGrid1(_props: YtItemGrid1Props): any
76
79
  export function Layout(_props: YtLayoutProps): any
77
80
  export function Metadata(_props: YtMetadataProps): any
81
+ export function PointMap(_props: YtPointMapProps): any
78
82
  export function QuantitySwitch(_props: YtQuantitySwitchProps): any
79
83
  export function SiteVerification(_props: YtSiteVerificationProps): any
80
84
  export function Stats0(_props: YtStats0Props): any
@@ -115,6 +119,7 @@ declare module '@yatoday/astro-ui/astro' {
115
119
  export type Card4Props = YtCard4Props
116
120
  export type Card5Props = YtCard5Props
117
121
  export type Card6Props = YtCard6Props
122
+ export type Card7Props = YtCard7Props
118
123
  export type ConditionalWrapperProps = YtConditionalWrapperProps
119
124
  export type CopyToClipboardProps = YtCopyToClipboardProps
120
125
  export type DarkModeProps = YtDarkModeProps
@@ -126,6 +131,7 @@ declare module '@yatoday/astro-ui/astro' {
126
131
  export type ItemGrid1Props = YtItemGrid1Props
127
132
  export type LayoutProps = YtLayoutProps
128
133
  export type MetadataProps = YtMetadataProps
134
+ export type PointMapProps = YtPointMapProps
129
135
  export type QuantitySwitchProps = YtQuantitySwitchProps
130
136
  export type SiteVerificationProps = YtSiteVerificationProps
131
137
  export type Stats0Props = YtStats0Props
package/astro.js CHANGED
@@ -12,6 +12,7 @@ import Card3Component from './components/Card3/Card3.astro'
12
12
  import Card4Component from './components/Card4/Card4.astro'
13
13
  import Card5Component from './components/Card5/Card5.astro'
14
14
  import Card6Component from './components/Card6/Card6.astro'
15
+ import Card7Component from './components/Card7/Card7.astro'
15
16
  import ConditionalWrapperComponent from './components/ConditionalWrapper/ConditionalWrapper.astro'
16
17
  import CopyToClipboardComponent from './components/CopyToClipboard/CopyToClipboard.astro'
17
18
  import DarkModeComponent from './components/DarkMode/DarkMode.astro'
@@ -23,6 +24,7 @@ import ItemGrid0Component from './components/ItemGrid0/ItemGrid0.astro'
23
24
  import ItemGrid1Component from './components/ItemGrid1/ItemGrid1.astro'
24
25
  import LayoutComponent from './components/Layout/Layout.astro'
25
26
  import MetadataComponent from './components/Metadata/Metadata.astro'
27
+ import PointMapComponent from './components/PointMap/PointMap.astro'
26
28
  import QuantitySwitchComponent from './components/QuantitySwitch/QuantitySwitch.astro'
27
29
  import SiteVerificationComponent from './components/SiteVerification/SiteVerification.astro'
28
30
  import Stats0Component from './components/Stats0/Stats0.astro'
@@ -63,6 +65,7 @@ export const Card3 = Card3Component
63
65
  export const Card4 = Card4Component
64
66
  export const Card5 = Card5Component
65
67
  export const Card6 = Card6Component
68
+ export const Card7 = Card7Component
66
69
  export const ConditionalWrapper = ConditionalWrapperComponent
67
70
  export const CopyToClipboard = CopyToClipboardComponent
68
71
  export const DarkMode = DarkModeComponent
@@ -74,6 +77,7 @@ export const ItemGrid0 = ItemGrid0Component
74
77
  export const ItemGrid1 = ItemGrid1Component
75
78
  export const Layout = LayoutComponent
76
79
  export const Metadata = MetadataComponent
80
+ export const PointMap = PointMapComponent
77
81
  export const QuantitySwitch = QuantitySwitchComponent
78
82
  export const SiteVerification = SiteVerificationComponent
79
83
  export const Stats0 = Stats0Component
@@ -0,0 +1,53 @@
1
+ ---
2
+ import type {Card7Props as Props} from './types';
3
+ import Card0 from '../Card0/Card0.astro';
4
+ import {Icon} from 'astro-icon/components';
5
+ import {cn} from '../../utils';
6
+
7
+ const {
8
+ title = await Astro.slots.render('title'),
9
+ description = await Astro.slots.render('description'),
10
+ callToAction,
11
+ as = 'article',
12
+ asHeader = 'h3',
13
+ icon = await Astro.slots.render('icon'),
14
+ classes = {},
15
+ } = Astro.props;
16
+
17
+ const WrapperHeaderTag = asHeader;
18
+ const DefaultIcon = 'tabler:chevron-right';
19
+
20
+ const {
21
+ container: containerClass = '',
22
+ title: titleClass = '',
23
+ description: descriptionClass = '',
24
+ icon: iconClass = '',
25
+ } = classes;
26
+ ---
27
+
28
+ <Card0
29
+ as={as}
30
+ classes={{
31
+ container: cn('flex flex-row p-0', containerClass),
32
+ }}
33
+ >
34
+ <div class="flex items-center relative">
35
+ <div class={cn("flex flex-col gap-3 justify-between h-full p-4 ", callToAction && 'border-r dark:border-white/10 border-black/10')}>
36
+ {callToAction ? (
37
+ <a href={callToAction.href} class="flex items-center">
38
+ <WrapperHeaderTag class={cn('text-lg md:text-xl font-bold', titleClass)}>{title}</WrapperHeaderTag>
39
+ <span class="absolute inset-0" aria-hidden="true"></span>
40
+ </a>
41
+ ) : (
42
+ <WrapperHeaderTag class={cn('text-lg md:text-xl font-bold', titleClass)}>{title}</WrapperHeaderTag>
43
+ )}
44
+ {description && (
45
+ <p class={cn('text-muted-foreground text-sm/5 md:text-base', descriptionClass)} set:html={description}/>
46
+ )}
47
+
48
+ <slot/>
49
+ </div>
50
+
51
+ {callToAction && <Icon name={icon ?? DefaultIcon} class={cn('flex-none size-7 mx-1', iconClass)}/>}
52
+ </div>
53
+ </Card0>
@@ -0,0 +1,3 @@
1
+ <script lang="ts">
2
+ import type { Card7Props } from './types'
3
+ </script>
@@ -0,0 +1,11 @@
1
+ import type { HTMLTag } from 'astro/types';
2
+ import type { Card0Props } from '../Card0/types';
3
+ import type { ToAction } from '../../types';
4
+
5
+ export type Card7Props = {
6
+ asHeader?: HTMLTag;
7
+ title?: string;
8
+ description?: string;
9
+ icon?: string;
10
+ callToAction?: ToAction;
11
+ } & Card0Props;
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  import type { HeroSectionProps as Props } from './types';
3
3
 
4
- import { twMerge } from 'tailwind-merge';
5
4
  import Image from '../Image/Image.astro';
6
5
  import Button from '../Button/Button.astro';
7
6
  import { cn } from '../../utils';
@@ -25,8 +24,10 @@ const urlForImage = Array.isArray(callToAction)
25
24
 
26
25
  const {
27
26
  container: containerClass = 'h-96',
27
+ start: startClass = 'w-full',
28
+ end: endClass = 'w-full',
28
29
  image: imageClass = '',
29
- content: contentClass = '',
30
+ content: contentClass = 'md:px-12 lg:px-20 xl:px-24',
30
31
  title: titleClass = '',
31
32
  description: descriptionClass = '',
32
33
  action: actionClass = '',
@@ -36,25 +37,24 @@ const WrapperHeaderTag = asHeader;
36
37
  ---
37
38
 
38
39
  <div
39
- class={twMerge(
40
- 'swiper-slide overflow-hidden bg-zinc-700 text-zinc-200 dark:bg-card dark:text-card-foreground',
40
+ class={cn(
41
+ 'overflow-hidden bg-zinc-700 text-zinc-200 dark:bg-card dark:text-card-foreground',
41
42
  containerClass
42
43
  )}
43
44
  >
44
- <div class="slide flex flex-col-reverse justify-between md:flex-row h-full">
45
- <div class="w-full flex items-center justify-center">
46
- <div class={twMerge('p-6 md:px-12 lg:px-20 xl:px-24 w-full h-full md:h-auto', contentClass)}>
47
- <slot />
45
+ <div class="flex flex-col-reverse justify-between md:flex-row h-full">
46
+ <div class={cn("flex items-center justify-center", startClass)}>
47
+ <div class={cn('p-6 w-full h-full md:h-auto', contentClass)}>
48
48
  {
49
49
  title && description && (
50
- <div class="flex flex-col h-full justify-between">
50
+ <div class="flex flex-col h-full">
51
51
  <div class="mb-6">
52
52
  <WrapperHeaderTag
53
- class={twMerge('text-base md:text-lg xl:text-2xl font-medium md:font-semibold mb-2', titleClass)}
53
+ class={cn('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
- <p class={twMerge('text-sm md:text-base xl:text-lg', descriptionClass)}>{description}</p>
57
+ <p class={cn('text-sm md:text-base xl:text-lg', descriptionClass)}>{description}</p>
58
58
  </div>
59
59
 
60
60
  {callToAction && (
@@ -83,9 +83,11 @@ const WrapperHeaderTag = asHeader;
83
83
  </div>
84
84
  )
85
85
  }
86
+
87
+ <slot />
86
88
  </div>
87
89
  </div>
88
- <div class="w-full md:h-auto">
90
+ <div class={cn("md:h-auto", endClass)}>
89
91
  {
90
92
  image && (
91
93
  <Fragment>
@@ -95,7 +97,7 @@ const WrapperHeaderTag = asHeader;
95
97
  <Fragment set:html={image} />
96
98
  ) : (
97
99
  <Image
98
- class={twMerge('w-full md:h-full bg-gray-400 dark:bg-slate-700', imageClass)}
100
+ class={cn('w-full md:h-full bg-gray-400 dark:bg-slate-700', imageClass)}
99
101
  widths={[400, 900]}
100
102
  width={400}
101
103
  height={400}
@@ -113,7 +115,7 @@ const WrapperHeaderTag = asHeader;
113
115
  <Fragment set:html={image} />
114
116
  ) : (
115
117
  <Image
116
- class={twMerge('w-full h-full')}
118
+ class={cn('w-full h-full')}
117
119
  widths={[400, 900]}
118
120
  width={400}
119
121
  height={400}
@@ -0,0 +1,326 @@
1
+ ---
2
+ import type {PointMapProps as Props} from './types';
3
+ import {cn, id} from "../../utils";
4
+ import Card7 from '../Card7/Card7.astro';
5
+
6
+ const {
7
+ via = Card7,
8
+ items = [],
9
+ pointsDisplayMode = 'always', // default to always visible
10
+ classes = {}
11
+ } = Astro.props;
12
+
13
+ const mapId = `map-${id()}`;
14
+
15
+ const {
16
+ container: containerClass = '',
17
+ popup: popupClass = '',
18
+ } = classes;
19
+
20
+ const Component = via;
21
+ ---
22
+
23
+ <div id={mapId} class={cn(`map-picker relative h-full w-full points-${pointsDisplayMode}`, containerClass)}>
24
+ {items.map((point, i) => (
25
+ <>
26
+ <!-- Hotspot point -->
27
+ <button
28
+ class="hotspot-point cursor-pointer"
29
+ style={`left: ${point.x}%; top: ${point.y}%; width: 24px; height: 24px; transform: translate(-50%, -50%);`}
30
+ data-index={i}
31
+ data-map-id={mapId}
32
+ aria-label={point.title}
33
+ {...(point.isPopupOpen ? { 'data-initially-open': '' } : {})}
34
+ >
35
+ </button>
36
+
37
+ <!-- Popup content (hidden by default) -->
38
+ <div
39
+ class={cn("popup-content absolute rounded-md shadow-lg z-10 max-w-xs hidden", popupClass)}
40
+ style={`left: ${point.x}%; top: ${point.y}%; transform: translate(${point.x > 50 ? '-100%' : '0'}, ${point.y > 50 ? '-100%' : '0'});`}
41
+ data-popup={i}
42
+ data-map-id={mapId}
43
+ >
44
+ <Component
45
+ {...point}
46
+ classes={Object.assign({container: 'border-transparent dark:border-input'}, classes?.card || {}, point?.classes || {}) as Record<string, string>}
47
+ />
48
+ </div>
49
+ </>
50
+ ))}
51
+
52
+ <div class="w-full h-full">
53
+ <slot/>
54
+ </div>
55
+ </div>
56
+
57
+ <style>
58
+ .map-picker {
59
+ max-width: 100%;
60
+ display: block;
61
+ }
62
+
63
+ .popup-content {
64
+ width: 250px;
65
+ margin: 10px;
66
+ transition: all 200ms ease-in-out;
67
+ }
68
+
69
+ /* Points display modes */
70
+ .points-hover .hotspot-point {
71
+ opacity: 0;
72
+ visibility: hidden;
73
+ transition: opacity 250ms ease-in-out, visibility 250ms ease-in-out;
74
+ }
75
+
76
+ .points-hover:hover .hotspot-point,
77
+ .points-hover .hotspot-point[data-initially-open] {
78
+ opacity: 1;
79
+ visibility: visible;
80
+ }
81
+
82
+ .hotspot-point {
83
+ box-sizing: border-box;
84
+ transition: border-color 250ms ease-in-out, opacity 250ms ease-in-out, visibility 250ms ease-in-out;
85
+ transform: translateX(-1rem) translateY(-1rem) scale(0.999);
86
+ padding: 0;
87
+ width: 2rem;
88
+ height: 2rem;
89
+ border: 2px solid rgba(223, 223, 223, 0.75);
90
+ background-clip: padding-box;
91
+ position: absolute;
92
+ background-color: rgba(17, 17, 17, 0.75);
93
+ border-radius: 64px;
94
+ line-height: .5;
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ }
99
+
100
+ .hotspot-point::before {
101
+ content: "";
102
+ position: absolute;
103
+ top: -0.5rem;
104
+ left: -0.5rem;
105
+ right: -0.5rem;
106
+ bottom: -0.5rem;
107
+ display: block;
108
+ }
109
+
110
+ .hotspot-point::after {
111
+ content: "";
112
+ position: relative;
113
+ display: block;
114
+ background: rgb(255, 255, 255);
115
+ box-shadow: 0 1px 4px rgba(17, 17, 17, 0.55);
116
+ width: .75rem;
117
+ height: .75rem;
118
+ border-radius: 64px;
119
+ transition: transform 250ms ease-in-out;
120
+ }
121
+
122
+ .hotspot-point:active, .hotspot-point:hover {
123
+ background-color: rgba(17, 17, 17, 0.9);
124
+ }
125
+
126
+ .hotspot-point:hover::after {
127
+ transform: scale(0.667);
128
+ }
129
+ </style>
130
+
131
+ <script>
132
+ interface PointCoordinates {
133
+ x: number;
134
+ y: number;
135
+ }
136
+
137
+ document.addEventListener('DOMContentLoaded', () => {
138
+ const POPUP_DATA_ATTRIBUTE = 'data-popup';
139
+ const MAP_ID_ATTRIBUTE = 'data-map-id';
140
+
141
+ // Initialize each map component separately
142
+ document.querySelectorAll('.map-picker').forEach(mapContainer => {
143
+ const mapId = mapContainer.id;
144
+ if (!mapId) return;
145
+
146
+ // Get all hotspot points and popups for this specific map
147
+ const hotspotPoints = document.querySelectorAll(`.hotspot-point[${MAP_ID_ATTRIBUTE}="${mapId}"]`);
148
+ const popups = document.querySelectorAll(`.popup-content[${MAP_ID_ATTRIBUTE}="${mapId}"]`) as NodeListOf<HTMLElement>;
149
+
150
+ if (!hotspotPoints.length || !popups.length) return;
151
+
152
+ // Check for initially open popups
153
+ hotspotPoints.forEach((point, index) => {
154
+ // Get the data attribute to check if the popup should be initially open
155
+ const isInitiallyOpen = point.hasAttribute('data-initially-open');
156
+ if (isInitiallyOpen) {
157
+ showPopup(index);
158
+ }
159
+ });
160
+
161
+ // Show a popup when hovering over a point
162
+ function showPopup(index: number) {
163
+ popups.forEach((popup: HTMLElement, i) => {
164
+ if (i === index) {
165
+ popup.classList.remove('hidden');
166
+
167
+ // Position correction for popups that don't fit within the container
168
+ // This is especially important for mobile devices with smaller screens
169
+ // where popups might extend beyond the visible area
170
+ const containerRect = mapContainer.getBoundingClientRect();
171
+ const popupRect = popup.getBoundingClientRect();
172
+
173
+ // Calculate offsets if popup extends beyond container
174
+ let offsetX = 0;
175
+ let offsetY = 0;
176
+
177
+ // Check horizontal overflow
178
+ if (popupRect.right > containerRect.right) {
179
+ offsetX = containerRect.right - popupRect.right - 10; // 10px padding
180
+ } else if (popupRect.left < containerRect.left) {
181
+ offsetX = containerRect.left - popupRect.left + 10; // 10px padding
182
+ }
183
+
184
+ // Check vertical overflow
185
+ if (popupRect.bottom > containerRect.bottom) {
186
+ offsetY = containerRect.bottom - popupRect.bottom - 10; // 10px padding
187
+ } else if (popupRect.top < containerRect.top) {
188
+ offsetY = containerRect.top - popupRect.top + 10; // 10px padding
189
+ }
190
+
191
+ // Apply position adjustment if needed
192
+ if (offsetX !== 0 || offsetY !== 0) {
193
+ // Get the point data to preserve the original positioning logic
194
+ const popupIndex = getPopupIndex(popup);
195
+ if (popupIndex === null) {
196
+ return;
197
+ }
198
+
199
+ const point = document.querySelector(`.hotspot-point[data-index="${popupIndex}"][${MAP_ID_ATTRIBUTE}="${mapId}"]`) as HTMLElement;
200
+ if (!point) return;
201
+ const coordinates = getPointCoordinates(point);
202
+ const translateX = coordinates.x > 50 ? '-100%' : '0';
203
+ const translateY = coordinates.y > 50 ? '-100%' : '0';
204
+
205
+ // Apply the original transform with additional pixel offsets for correction
206
+ popup.style.transform = `translate(${translateX}, ${translateY}) translate(${offsetX}px, ${offsetY}px)`;
207
+ }
208
+ } else {
209
+ popup.classList.add('hidden');
210
+ }
211
+ });
212
+ }
213
+
214
+ function getPointCoordinates(pointElement: HTMLElement): PointCoordinates {
215
+ return {
216
+ x: parseFloat(pointElement.style.left) || 0,
217
+ y: parseFloat(pointElement.style.top) || 0
218
+ };
219
+ }
220
+
221
+ function getPopupIndex(element: HTMLElement): number | null {
222
+ const rawIndex = element?.getAttribute(POPUP_DATA_ATTRIBUTE);
223
+ if (!rawIndex) {
224
+ return null;
225
+ }
226
+
227
+ const parsedIndex = parseInt(rawIndex, 10);
228
+ return isNaN(parsedIndex) ? null : parsedIndex;
229
+ }
230
+
231
+ function getPointIndex(element: Element): number | null {
232
+ const dataIndex = element.getAttribute('data-index');
233
+ if (!dataIndex) {
234
+ return null;
235
+ }
236
+ const index = parseInt(dataIndex, 10);
237
+ if (isNaN(index)) {
238
+ return null;
239
+ }
240
+ return index;
241
+ }
242
+
243
+ // Hide popup when the mouse leaves
244
+ function hidePopup() {
245
+ popups.forEach((popup: HTMLElement) => {
246
+ const index = getPopupIndex(popup);
247
+ if (index === null) return;
248
+
249
+ const point = document.querySelector(`.hotspot-point[data-index="${index}"][${MAP_ID_ATTRIBUTE}="${mapId}"]`);
250
+ if (!point) return;
251
+
252
+ // Only hide popups that aren't initially open
253
+ if (!point.hasAttribute('data-initially-open')) {
254
+ popup.classList.add('hidden');
255
+ }
256
+ });
257
+ }
258
+
259
+ // Hide all popups (including initially open ones)
260
+ function hideAllPopups() {
261
+ popups.forEach(popup => {
262
+ popup.classList.add('hidden');
263
+ });
264
+ }
265
+
266
+ // Add event listeners to hotspot points
267
+ hotspotPoints.forEach(point => {
268
+ const index = getPointIndex(point);
269
+ if (index === null) return;
270
+
271
+ point.addEventListener('mouseenter', () => {
272
+ // Hide all popups (including initially open ones) when hovering a new point
273
+ hideAllPopups();
274
+ showPopup(index);
275
+ });
276
+
277
+ point.addEventListener('mouseleave', () => {
278
+ hidePopup();
279
+ });
280
+
281
+ // Add click event to the toggle popup
282
+ point.addEventListener('click', (e) => {
283
+ e.preventDefault();
284
+ // Hide all popups first
285
+ hideAllPopups();
286
+ showPopup(index);
287
+ });
288
+ });
289
+
290
+ // Add an event listener to the container for mouseleave
291
+ mapContainer.addEventListener('mouseleave', () => {
292
+ // Hide all popups first
293
+ hideAllPopups();
294
+
295
+ // Show popups for points that should be initially open
296
+ hotspotPoints.forEach((point, index) => {
297
+ if (point.hasAttribute('data-initially-open')) {
298
+ showPopup(index);
299
+ }
300
+ });
301
+ });
302
+
303
+ // Add event listeners to popups to keep them visible when hovered
304
+ popups.forEach((popup: HTMLElement) => {
305
+ const index = getPopupIndex(popup);
306
+ if (index === null) return;
307
+
308
+ popup.addEventListener('mouseenter', () => {
309
+ showPopup(index);
310
+ });
311
+
312
+ popup.addEventListener('mouseleave', () => {
313
+ hidePopup(); // This will preserve initially open popups
314
+ });
315
+
316
+ // Add click event to close the popup
317
+ popup.addEventListener('click', (e) => {
318
+ // If the click is on the popup itself (not a child element), close it
319
+ if (e.target === popup) {
320
+ hideAllPopups();
321
+ }
322
+ });
323
+ });
324
+ });
325
+ });
326
+ </script>
@@ -0,0 +1,3 @@
1
+ <script lang="ts">
2
+ import type { PointMapProps } from './types'
3
+ </script>
@@ -0,0 +1,8 @@
1
+ import type {HotspotPoint} from "../../types";
2
+
3
+ export type PointMapProps = {
4
+ via?: any;
5
+ items: HotspotPoint[];
6
+ pointsDisplayMode?: 'always' | 'hover' | undefined; // controls visibility of hotspot points
7
+ classes?: Record<string, string>;
8
+ }
@@ -6,6 +6,7 @@ import ItemGrid0 from '../ItemGrid0/ItemGrid0.astro';
6
6
  import Card4 from '../Card4/Card4.astro';
7
7
  import Button from '../Button/Button.astro';
8
8
  import Image from '../Image/Image.astro';
9
+ import PointMap from '../PointMap/PointMap.astro';
9
10
  import { cn } from '../../utils';
10
11
 
11
12
  const {
@@ -15,6 +16,8 @@ const {
15
16
  content = await Astro.slots.render('content'),
16
17
  callToAction,
17
18
  items = [],
19
+ points = [],
20
+ pointsDisplayMode = 'always',
18
21
  columns,
19
22
  image = await Astro.slots.render('image'),
20
23
  isReversed = false,
@@ -31,7 +34,7 @@ const {
31
34
  <WidgetWrapper
32
35
  id={id}
33
36
  isDark={isDark}
34
- containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
37
+ containerClass={`${isAfterContent ? 'pt-4 md:pt-6 lg:pt-8' : ''} ${classes?.container ?? ''}`}
35
38
  bg={bg}
36
39
  >
37
40
  <Headline
@@ -43,7 +46,8 @@ const {
43
46
  />
44
47
 
45
48
  <div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}>
46
- <div class="md:basis-1/2 self-center">
49
+ <div class={cn("md:basis-1/2 self-center", classes?.start ?? '')}>
50
+ <slot />
47
51
  {content && <div class={cn('mb-12 text-lg', classes?.content ?? '')} set:html={content} />}
48
52
 
49
53
  {
@@ -62,9 +66,9 @@ const {
62
66
  icon={defaultIcon}
63
67
  {...item}
64
68
  classes={{
65
- container: `gap-0 border-0 py-0`,
69
+ container: `gap-0 border-0 py-0 justify-start`,
66
70
  title: 'text-lg font-medium leading-6 dark:text-white ml-2 rtl:ml-0 rtl:mr-2',
67
- description: 'text-muted dark:text-slate-400 ml-2 rtl:ml-0 rtl:mr-2',
71
+ description: 'ml-2 rtl:ml-0 rtl:mr-2',
68
72
  icon: 'flex h-7 w-7 items-center justify-center rounded-full bg-green-600 dark:bg-green-700 text-gray-50 p-1',
69
73
  ...((classes?.items as object) ?? {}),
70
74
  }}
@@ -73,24 +77,26 @@ const {
73
77
  }
74
78
  </ItemGrid0>
75
79
  </div>
76
- <div aria-hidden="true" class="mt-10 md:mt-0 md:basis-1/2">
80
+ <div aria-hidden="true" class={cn("mt-10 md:mt-0 md:basis-1/2", classes?.end ?? '')}>
77
81
  {
78
82
  image && (
79
- <div class={cn('relative m-auto max-w-4xl', classes?.image ?? '')}>
80
- {typeof image === 'string' ? (
81
- <Fragment set:html={image} />
82
- ) : (
83
- <Image
84
- class="mx-auto w-full rounded-lg shadow-lg"
85
- width={500}
86
- height={500}
87
- widths={[400, 768]}
88
- sizes="(max-width: 768px) 100vw, 432px"
89
- layout="responsive"
90
- {...image}
91
- />
92
- )}
93
- </div>
83
+ <div class={cn('relative m-auto max-w-4xl', classes?.image ?? '')}>
84
+ <PointMap items={points} pointsDisplayMode={pointsDisplayMode} classes={{...((classes?.points as object) ?? {})}}>
85
+ {typeof image === 'string' ? (
86
+ <Fragment set:html={image} />
87
+ ) : (
88
+ <Image
89
+ class="mx-auto w-full rounded-lg shadow-lg"
90
+ width={500}
91
+ height={500}
92
+ widths={[400, 768]}
93
+ sizes="(max-width: 768px) 100vw, 432px"
94
+ layout="responsive"
95
+ {...image}
96
+ />
97
+ )}
98
+ </PointMap>
99
+ </div>
94
100
  )
95
101
  }
96
102
  </div>
@@ -1,4 +1,4 @@
1
- import type { Item, ToAction } from '../../types';
1
+ import type {Item, HotspotPoint, ToAction} from '../../types';
2
2
  import type { WidgetWrapperProps } from '../WidgetWrapper/types';
3
3
  import type { HeadlineProps } from '../Headline/types.ts';
4
4
 
@@ -7,6 +7,8 @@ export type WidgetContentProps = {
7
7
  defaultIcon?: string;
8
8
  image?: string | unknown;
9
9
  items?: Array<Item>;
10
+ points?: Array<HotspotPoint>;
11
+ pointsDisplayMode?: 'always' | 'hover' | undefined; // controls visibility of hotspot points
10
12
  columns?: number;
11
13
  isReversed?: boolean;
12
14
  isAfterContent?: boolean;
@@ -9,6 +9,7 @@ import { cn } from '../../utils';
9
9
 
10
10
  const {
11
11
  via = Card1,
12
+ viaGrid = ItemGrid0,
12
13
 
13
14
  title = await Astro.slots.render('title'),
14
15
  subtitle = await Astro.slots.render('subtitle'),
@@ -26,6 +27,7 @@ const {
26
27
  } = Astro.props;
27
28
 
28
29
  const Component = via;
30
+ const ComponentGrid = viaGrid;
29
31
  ---
30
32
 
31
33
  <WidgetWrapper
@@ -43,7 +45,7 @@ const Component = via;
43
45
  classes={classes?.headline as Record<string, string>}
44
46
  position={position}
45
47
  />
46
-
48
+
47
49
  {
48
50
  image && (
49
51
  <div aria-hidden="true" class={cn('aspect-w-16 aspect-h-7 mb-6')}>
@@ -70,7 +72,9 @@ const Component = via;
70
72
  )
71
73
  }
72
74
 
73
- <ItemGrid0 columns={columns} class={classes?.grid && ''}>
74
- {items && items.map((item) => <Component {...item} classes={classes?.card as Record<string, string>} />)}
75
- </ItemGrid0>
75
+ <slot />
76
+
77
+ <ComponentGrid columns={columns} class={classes?.grid && ''}>
78
+ {items && items.map((item) => <Component {...item} classes={Object.assign({}, classes?.card || {}, item?.classes || {}) as Record<string, string>}/>)}
79
+ </ComponentGrid>
76
80
  </WidgetWrapper>
@@ -3,6 +3,7 @@ import type { Image } from '../../types';
3
3
 
4
4
  export type WidgetFeaturesCardProps = {
5
5
  via?: any;
6
+ viaGrid?: any;
6
7
  items?: any[];
7
8
  columns?: number;
8
9
  image?: Image | string;
@@ -5,6 +5,7 @@ import WidgetWrapper from '../WidgetWrapper/WidgetWrapper.astro';
5
5
  import SwiperSlider from '../SwiperSlider/SwiperSlider.astro';
6
6
  import { Image as AstroImage, getImage } from 'astro:assets';
7
7
  import { fetchLocalImages } from '../../utils/images';
8
+ import Button from "../Button/Button.astro";
8
9
 
9
10
  const {
10
11
  title = await Astro.slots.render('title'),
@@ -13,7 +14,8 @@ const {
13
14
  imagesFolder,
14
15
  position = 'center',
15
16
  isAfterContent = false,
16
-
17
+ callToAction,
18
+
17
19
  id = (Math.random() + 1).toString(36).substring(7),
18
20
  withNavigation = true,
19
21
  isDark = false,
@@ -77,6 +79,14 @@ const imagePaths = Object.keys(images).filter((imagePath) => {
77
79
  </SwiperSlider>
78
80
  )
79
81
  }
82
+
83
+ {
84
+ callToAction && (
85
+ <div class="flex justify-center mx-auto w-fit mt-8 md:mt-12 font-medium">
86
+ <Button {...callToAction} />
87
+ </div>
88
+ )
89
+ }
80
90
  </WidgetWrapper>
81
91
 
82
92
  <script>
@@ -1,10 +1,12 @@
1
1
  import type { WidgetWrapperProps } from '../WidgetWrapper/types';
2
2
  import type { HeadlineProps } from '../Headline/types';
3
+ import type {ToAction} from "../../types";
3
4
 
4
5
  export type WidgetSwiperPhotoSliderProps = {
5
6
  isAfterContent?: boolean;
6
7
  withNavigation?: boolean;
7
8
  imagesFolder: string;
8
9
  classes?: Record<string, string>;
10
+ callToAction?: ToAction;
9
11
  } & WidgetWrapperProps &
10
12
  Omit<HeadlineProps, 'classes'>;
@@ -21,8 +21,8 @@ const WrapperTag = as;
21
21
  <div
22
22
  class:list={[
23
23
  cn(
24
- 'relative mx-auto max-w-7xl 2xl:max-w-[96rem] px-4 md:px-6 text-default',
25
- isAfterContent ? 'py-6 md:py-8' : 'py-12 md:py-16 lg:py-20',
24
+ 'relative mx-auto max-w-7xl 2xl:max-w-[96rem] px-4 md:px-6 lg:px-10 text-default',
25
+ isAfterContent ? 'pt-6 md:pt-8 pb-12 md:pb-16 lg:pb-20' : 'py-12 md:py-16 lg:py-20',
26
26
  containerClass
27
27
  ),
28
28
  { dark: isDark },
package/index.d.ts CHANGED
@@ -22,6 +22,16 @@ export type NameValue = {
22
22
  value?: string;
23
23
  };
24
24
 
25
+ export type HotspotPoint = {
26
+ x: number; // percentage from the left (0-100)
27
+ y: number; // percentage from top (0-100)
28
+ title: string;
29
+ description: string;
30
+ isPopupOpen?: boolean; // whether the popup is open on initialization
31
+ classes?: Record<string, string>;
32
+ callToAction?: CallToAction;
33
+ }
34
+
25
35
  export type Item = {
26
36
  title?: string;
27
37
  description?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yatoday/astro-ui",
3
3
  "type": "module",
4
- "version": "0.9.0",
4
+ "version": "0.10.2",
5
5
  "scripts": {
6
6
  "prepare": "husky",
7
7
  "pre-commit": "lint-staged",
@@ -32,14 +32,14 @@
32
32
  "unpic": "^3.22.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "astro": "^5.4.2"
35
+ "astro": "^5.10.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@astrojs/check": "0.9.4",
39
39
  "@astrojs/mdx": "^4.1.0",
40
40
  "@astrojs/node": "^9.1.2",
41
41
  "@astrojs/partytown": "^2.1.4",
42
- "@astrojs/svelte": "^7.0.5",
42
+ "@astrojs/svelte": "^7.1.0",
43
43
  "@eslint/js": "9.18.0",
44
44
  "@iconify-json/tabler": "^1.2.10",
45
45
  "@tailwindcss/cli": "^4.0.13",
package/styles/styles.css CHANGED
@@ -387,6 +387,9 @@
387
387
  .m-auto {
388
388
  margin: auto;
389
389
  }
390
+ .mx-1 {
391
+ margin-inline: calc(var(--spacing) * 1);
392
+ }
390
393
  .mx-auto {
391
394
  margin-inline: auto;
392
395
  }
@@ -981,6 +984,10 @@
981
984
  border-top-style: var(--tw-border-style);
982
985
  border-top-width: 1px;
983
986
  }
987
+ .border-r {
988
+ border-right-style: var(--tw-border-style);
989
+ border-right-width: 1px;
990
+ }
984
991
  .border-l-2 {
985
992
  border-left-style: var(--tw-border-style);
986
993
  border-left-width: 2px;
@@ -989,6 +996,9 @@
989
996
  --tw-border-style: none;
990
997
  border-style: none;
991
998
  }
999
+ .border-black\/10 {
1000
+ border-color: color-mix(in oklab, var(--color-black) 10%, transparent);
1001
+ }
992
1002
  .border-gray-300 {
993
1003
  border-color: var(--color-gray-300);
994
1004
  }
@@ -1210,6 +1220,9 @@
1210
1220
  .pt-4 {
1211
1221
  padding-top: calc(var(--spacing) * 4);
1212
1222
  }
1223
+ .pt-6 {
1224
+ padding-top: calc(var(--spacing) * 6);
1225
+ }
1213
1226
  .pt-8 {
1214
1227
  padding-top: calc(var(--spacing) * 8);
1215
1228
  }
@@ -1225,6 +1238,9 @@
1225
1238
  .pb-8 {
1226
1239
  padding-bottom: calc(var(--spacing) * 8);
1227
1240
  }
1241
+ .pb-12 {
1242
+ padding-bottom: calc(var(--spacing) * 12);
1243
+ }
1228
1244
  .pb-\[56\%\] {
1229
1245
  padding-bottom: 56%;
1230
1246
  }
@@ -2244,6 +2260,16 @@
2244
2260
  padding-top: calc(var(--spacing) * 0);
2245
2261
  }
2246
2262
  }
2263
+ .md\:pt-6 {
2264
+ @media (width >= 48rem) {
2265
+ padding-top: calc(var(--spacing) * 6);
2266
+ }
2267
+ }
2268
+ .md\:pt-8 {
2269
+ @media (width >= 48rem) {
2270
+ padding-top: calc(var(--spacing) * 8);
2271
+ }
2272
+ }
2247
2273
  .md\:pt-\[76px\] {
2248
2274
  @media (width >= 48rem) {
2249
2275
  padding-top: 76px;
@@ -2254,6 +2280,11 @@
2254
2280
  padding-bottom: calc(var(--spacing) * 8);
2255
2281
  }
2256
2282
  }
2283
+ .md\:pb-16 {
2284
+ @media (width >= 48rem) {
2285
+ padding-bottom: calc(var(--spacing) * 16);
2286
+ }
2287
+ }
2257
2288
  .md\:pl-0 {
2258
2289
  @media (width >= 48rem) {
2259
2290
  padding-left: calc(var(--spacing) * 0);
@@ -2388,6 +2419,11 @@
2388
2419
  column-gap: calc(var(--spacing) * 24);
2389
2420
  }
2390
2421
  }
2422
+ .lg\:px-10 {
2423
+ @media (width >= 64rem) {
2424
+ padding-inline: calc(var(--spacing) * 10);
2425
+ }
2426
+ }
2391
2427
  .lg\:px-20 {
2392
2428
  @media (width >= 64rem) {
2393
2429
  padding-inline: calc(var(--spacing) * 20);
@@ -2418,11 +2454,21 @@
2418
2454
  padding-top: calc(var(--spacing) * 0);
2419
2455
  }
2420
2456
  }
2457
+ .lg\:pt-8 {
2458
+ @media (width >= 64rem) {
2459
+ padding-top: calc(var(--spacing) * 8);
2460
+ }
2461
+ }
2421
2462
  .lg\:pb-12 {
2422
2463
  @media (width >= 64rem) {
2423
2464
  padding-bottom: calc(var(--spacing) * 12);
2424
2465
  }
2425
2466
  }
2467
+ .lg\:pb-20 {
2468
+ @media (width >= 64rem) {
2469
+ padding-bottom: calc(var(--spacing) * 20);
2470
+ }
2471
+ }
2426
2472
  .lg\:text-4xl {
2427
2473
  @media (width >= 64rem) {
2428
2474
  font-size: var(--text-4xl);
@@ -2664,6 +2710,11 @@
2664
2710
  border-color: var(--color-gray-700);
2665
2711
  }
2666
2712
  }
2713
+ .dark\:border-white\/10 {
2714
+ &:where(.dark, .dark *) {
2715
+ border-color: color-mix(in oklab, var(--color-white) 10%, transparent);
2716
+ }
2717
+ }
2667
2718
  .dark\:border-zinc-700 {
2668
2719
  &:where(.dark, .dark *) {
2669
2720
  border-color: var(--color-zinc-700);
@@ -2734,11 +2785,6 @@
2734
2785
  color: var(--color-slate-200);
2735
2786
  }
2736
2787
  }
2737
- .dark\:text-slate-400 {
2738
- &:where(.dark, .dark *) {
2739
- color: var(--color-slate-400);
2740
- }
2741
- }
2742
2788
  .dark\:text-white {
2743
2789
  &:where(.dark, .dark *) {
2744
2790
  color: var(--color-white);
package/svelte.d.ts CHANGED
@@ -13,6 +13,7 @@ import type { SvelteCard3Props as YtSvelteCard3Props } from './components/Card3/
13
13
  import type { SvelteCard4Props as YtSvelteCard4Props } from './components/Card4/types'
14
14
  import type { SvelteCard5Props as YtSvelteCard5Props } from './components/Card5/types'
15
15
  import type { SvelteCard6Props as YtSvelteCard6Props } from './components/Card6/types'
16
+ import type { SvelteCard7Props as YtSvelteCard7Props } from './components/Card7/types'
16
17
  import type { SvelteConditionalWrapperProps as YtSvelteConditionalWrapperProps } from './components/ConditionalWrapper/types'
17
18
  import type { SvelteCopyToClipboardProps as YtSvelteCopyToClipboardProps } from './components/CopyToClipboard/types'
18
19
  import type { SvelteDarkModeProps as YtSvelteDarkModeProps } from './components/DarkMode/types'
@@ -24,6 +25,7 @@ import type { SvelteItemGrid0Props as YtSvelteItemGrid0Props } from './component
24
25
  import type { SvelteItemGrid1Props as YtSvelteItemGrid1Props } from './components/ItemGrid1/types'
25
26
  import type { SvelteLayoutProps as YtSvelteLayoutProps } from './components/Layout/types'
26
27
  import type { SvelteMetadataProps as YtSvelteMetadataProps } from './components/Metadata/types'
28
+ import type { SveltePointMapProps as YtSveltePointMapProps } from './components/PointMap/types'
27
29
  import type { SvelteQuantitySwitchProps as YtSvelteQuantitySwitchProps } from './components/QuantitySwitch/types'
28
30
  import type { SvelteSiteVerificationProps as YtSvelteSiteVerificationProps } from './components/SiteVerification/types'
29
31
  import type { SvelteStats0Props as YtSvelteStats0Props } from './components/Stats0/types'
@@ -65,6 +67,7 @@ declare module '@yatoday/astro-ui/svelte' {
65
67
  export const Card4: Component<YtSvelteCard4Props>
66
68
  export const Card5: Component<YtSvelteCard5Props>
67
69
  export const Card6: Component<YtSvelteCard6Props>
70
+ export const Card7: Component<YtSvelteCard7Props>
68
71
  export const ConditionalWrapper: Component<YtSvelteConditionalWrapperProps>
69
72
  export const CopyToClipboard: Component<YtSvelteCopyToClipboardProps>
70
73
  export const DarkMode: Component<YtSvelteDarkModeProps>
@@ -76,6 +79,7 @@ declare module '@yatoday/astro-ui/svelte' {
76
79
  export const ItemGrid1: Component<YtSvelteItemGrid1Props>
77
80
  export const Layout: Component<YtSvelteLayoutProps>
78
81
  export const Metadata: Component<YtSvelteMetadataProps>
82
+ export const PointMap: Component<YtSveltePointMapProps>
79
83
  export const QuantitySwitch: Component<YtSvelteQuantitySwitchProps>
80
84
  export const SiteVerification: Component<YtSvelteSiteVerificationProps>
81
85
  export const Stats0: Component<YtSvelteStats0Props>
@@ -116,6 +120,7 @@ declare module '@yatoday/astro-ui/svelte' {
116
120
  export type Card4Props = YtSvelteCard4Props
117
121
  export type Card5Props = YtSvelteCard5Props
118
122
  export type Card6Props = YtSvelteCard6Props
123
+ export type Card7Props = YtSvelteCard7Props
119
124
  export type ConditionalWrapperProps = YtSvelteConditionalWrapperProps
120
125
  export type CopyToClipboardProps = YtSvelteCopyToClipboardProps
121
126
  export type DarkModeProps = YtSvelteDarkModeProps
@@ -127,6 +132,7 @@ declare module '@yatoday/astro-ui/svelte' {
127
132
  export type ItemGrid1Props = YtSvelteItemGrid1Props
128
133
  export type LayoutProps = YtSvelteLayoutProps
129
134
  export type MetadataProps = YtSvelteMetadataProps
135
+ export type PointMapProps = YtSveltePointMapProps
130
136
  export type QuantitySwitchProps = YtSvelteQuantitySwitchProps
131
137
  export type SiteVerificationProps = YtSvelteSiteVerificationProps
132
138
  export type Stats0Props = YtSvelteStats0Props
package/svelte.js CHANGED
@@ -12,6 +12,7 @@ import Card3Component from './components/Card3/Card3.svelte'
12
12
  import Card4Component from './components/Card4/Card4.svelte'
13
13
  import Card5Component from './components/Card5/Card5.svelte'
14
14
  import Card6Component from './components/Card6/Card6.svelte'
15
+ import Card7Component from './components/Card7/Card7.svelte'
15
16
  import ConditionalWrapperComponent from './components/ConditionalWrapper/ConditionalWrapper.svelte'
16
17
  import CopyToClipboardComponent from './components/CopyToClipboard/CopyToClipboard.svelte'
17
18
  import DarkModeComponent from './components/DarkMode/DarkMode.svelte'
@@ -23,6 +24,7 @@ import ItemGrid0Component from './components/ItemGrid0/ItemGrid0.svelte'
23
24
  import ItemGrid1Component from './components/ItemGrid1/ItemGrid1.svelte'
24
25
  import LayoutComponent from './components/Layout/Layout.svelte'
25
26
  import MetadataComponent from './components/Metadata/Metadata.svelte'
27
+ import PointMapComponent from './components/PointMap/PointMap.svelte'
26
28
  import QuantitySwitchComponent from './components/QuantitySwitch/QuantitySwitch.svelte'
27
29
  import SiteVerificationComponent from './components/SiteVerification/SiteVerification.svelte'
28
30
  import Stats0Component from './components/Stats0/Stats0.svelte'
@@ -63,6 +65,7 @@ export const Card3 = Card3Component
63
65
  export const Card4 = Card4Component
64
66
  export const Card5 = Card5Component
65
67
  export const Card6 = Card6Component
68
+ export const Card7 = Card7Component
66
69
  export const ConditionalWrapper = ConditionalWrapperComponent
67
70
  export const CopyToClipboard = CopyToClipboardComponent
68
71
  export const DarkMode = DarkModeComponent
@@ -74,6 +77,7 @@ export const ItemGrid0 = ItemGrid0Component
74
77
  export const ItemGrid1 = ItemGrid1Component
75
78
  export const Layout = LayoutComponent
76
79
  export const Metadata = MetadataComponent
80
+ export const PointMap = PointMapComponent
77
81
  export const QuantitySwitch = QuantitySwitchComponent
78
82
  export const SiteVerification = SiteVerificationComponent
79
83
  export const Stats0 = Stats0Component