negodesign 0.0.28 → 0.0.29

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.
Files changed (53) hide show
  1. package/dist/components/core/banner/BannerNotification.svelte +94 -0
  2. package/dist/components/core/banner/BannerNotification.svelte.d.ts +4 -0
  3. package/dist/components/core/banner/CardAdBanner.svelte +74 -0
  4. package/dist/components/core/banner/CardAdBanner.svelte.d.ts +4 -0
  5. package/dist/components/core/banner/types.d.ts +42 -0
  6. package/dist/components/core/banner/types.js +1 -0
  7. package/dist/components/core/card/highlight/01/CardHighlight01.svelte +5 -1
  8. package/dist/components/core/card/highlight/02/CardHighlight02.svelte +5 -3
  9. package/dist/components/core/card/highlight/data.js +1 -4
  10. package/dist/components/core/card/media/01/CardMedia01.svelte +124 -98
  11. package/dist/components/core/card/media/02/CardMedia02.svelte +125 -99
  12. package/dist/components/core/card/media/CardMedia.svelte +10 -9
  13. package/dist/components/core/card/media/CardMedia.svelte.d.ts +1 -0
  14. package/dist/components/core/card/profile/01/CardProfile01.svelte +104 -86
  15. package/dist/components/core/card/profile/02/CardProfile02.svelte +103 -87
  16. package/dist/components/core/card/profile/CardProfile.svelte +8 -7
  17. package/dist/components/core/card/profile/CardProfile.svelte.d.ts +1 -0
  18. package/dist/components/core/card/promotion/CardPromotion.svelte +6 -4
  19. package/dist/components/core/card/shared/CardButton.svelte +30 -21
  20. package/dist/components/core/card/shared/CardButton.svelte.d.ts +1 -0
  21. package/dist/components/core/card/shared/CardDescription.svelte +30 -30
  22. package/dist/components/core/card/shared/CardStarRating.svelte +18 -14
  23. package/dist/components/core/card/shared/CardStarRating.svelte.d.ts +1 -0
  24. package/dist/components/core/card/shared/CardStartPrice.svelte +1 -1
  25. package/dist/components/core/card/shared/CardThumbnailVideo.svelte +207 -148
  26. package/dist/components/core/card/shared/CardThumbnailVideo.svelte.d.ts +1 -0
  27. package/dist/components/core/card/types.d.ts +2 -0
  28. package/dist/components/core/carousel/grid/media/types.d.ts +10 -0
  29. package/dist/components/core/carousel/grid/media/types.js +1 -0
  30. package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +48 -0
  31. package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte.d.ts +4 -0
  32. package/dist/components/core/carousel/grid/profile/types.d.ts +10 -0
  33. package/dist/components/core/carousel/grid/profile/types.js +1 -0
  34. package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +51 -0
  35. package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte.d.ts +4 -0
  36. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte +38 -0
  37. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte.d.ts +10 -0
  38. package/dist/components/core/carousel/hero/CarouselHero.svelte +21 -19
  39. package/dist/components/core/carousel/hero/CarouselHero.svelte.d.ts +1 -0
  40. package/dist/components/core/carousel/highlights/types.d.ts +4 -4
  41. package/dist/components/core/carousel/highlights/ui/CarouselHighlights.svelte +4 -22
  42. package/dist/components/core/carousel/promotion/types.d.ts +5 -5
  43. package/dist/components/core/carousel/promotion/ui/CarouselPromotion.svelte +4 -21
  44. package/dist/components/core/carousel/shared/ui/CarouselHeader.svelte +6 -1
  45. package/dist/components/core/carousel/types.d.ts +1 -0
  46. package/dist/components/core/panel/CarouselSlot.svelte +3 -14
  47. package/dist/components/core/panel/CarouselSlot.svelte.d.ts +2 -12
  48. package/dist/components/core/panel/type.d.ts +11 -0
  49. package/dist/components/core/panel/type.js +1 -0
  50. package/dist/globals.css +351 -43
  51. package/dist/index.d.ts +15 -5
  52. package/dist/index.js +15 -5
  53. package/package.json +1 -1
@@ -1,170 +1,229 @@
1
1
  <!-- CardMedia.svelte -->
2
2
  <script lang="ts">
3
- /**
4
- * Image thumbnail with hover-to-play video, lazy loading,
5
- * duration badge, video indicator, and optional maximize overlay.
6
- * @component
7
- */
8
- import { HugeiconsIcon } from "@hugeicons/svelte";
9
- import { Maximize01Icon, Cancel01Icon, VideoReplayIcon } from "@hugeicons/core-free-icons";
10
-
11
- type Props = {
12
- imageUrl?: string;
13
- videoUrl?: string;
14
- class?: string;
15
- isImageButtonMaximized?: boolean;
16
- isVideoButtonMaximized?: boolean;
17
- };
18
-
19
- let {
20
- imageUrl,
21
- videoUrl,
22
- class: className = "w-64 h-64",
23
- isImageButtonMaximized = false,
24
- isVideoButtonMaximized = false,
25
- }: Props = $props();
26
-
27
- let containerEl: HTMLDivElement | undefined = $state();
28
- let videoEl: HTMLVideoElement | undefined = $state();
29
-
30
- let isHovering = $state(false);
31
- let isVisible = $state(false);
32
- let isMaximized = $state(false);
33
- let duration = $state("");
34
-
35
- let showVideo = $derived(!imageUrl || isHovering);
36
- let showMaximize = $derived((imageUrl && isImageButtonMaximized) || (videoUrl && isVideoButtonMaximized));
37
- let showVideoBadge = $derived(!!imageUrl && !!videoUrl && !isHovering);
38
-
39
- function onLoadedMetadata() {
40
- if (!videoEl) return;
41
- const m = Math.floor(videoEl.duration / 60);
42
- const s = Math.floor(videoEl.duration % 60);
43
- duration = `${m}:${s.toString().padStart(2, "0")}`;
3
+ /**
4
+ * Image thumbnail with hover-to-play video, lazy loading,
5
+ * duration badge, video indicator, and optional maximize overlay.
6
+ * @component
7
+ */
8
+ import { HugeiconsIcon } from "@hugeicons/svelte";
9
+ import {
10
+ Maximize01Icon,
11
+ Cancel01Icon,
12
+ VideoReplayIcon,
13
+ } from "@hugeicons/core-free-icons";
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import ImgPlaceholder from "../../../../assets/placeholder-image.png";
16
+
17
+ type Props = {
18
+ imageUrl?: string;
19
+ videoUrl?: string;
20
+ class?: string;
21
+ isImageButtonMaximized?: boolean;
22
+ isVideoButtonMaximized?: boolean;
23
+ isLoading?: boolean;
24
+ };
25
+
26
+ let {
27
+ imageUrl,
28
+ videoUrl,
29
+ class: className = "w-64 h-64",
30
+ isImageButtonMaximized = false,
31
+ isVideoButtonMaximized = false,
32
+ isLoading = false,
33
+ }: Props = $props();
34
+
35
+ let containerEl: HTMLDivElement | undefined = $state();
36
+ let videoEl: HTMLVideoElement | undefined = $state();
37
+
38
+ let isHovering = $state(false);
39
+ let isVisible = $state(false);
40
+ let isMaximized = $state(false);
41
+ let duration = $state("");
42
+
43
+ let showVideo = $derived(!imageUrl || isHovering);
44
+ let showMaximize = $derived(
45
+ (imageUrl && isImageButtonMaximized) ||
46
+ (videoUrl && isVideoButtonMaximized),
47
+ );
48
+ let showVideoBadge = $derived(!!imageUrl && !!videoUrl && !isHovering);
49
+
50
+ function onLoadedMetadata() {
51
+ if (!videoEl) return;
52
+ const m = Math.floor(videoEl.duration / 60);
53
+ const s = Math.floor(videoEl.duration % 60);
54
+ duration = `${m}:${s.toString().padStart(2, "0")}`;
55
+ }
56
+
57
+ function toggleHover(state: boolean) {
58
+ if (!videoUrl) return;
59
+ isHovering = state;
60
+ if (state) videoEl?.play();
61
+ else {
62
+ videoEl?.pause();
63
+ if (imageUrl && videoEl) videoEl.currentTime = 0;
44
64
  }
45
-
46
- function toggleHover(state: boolean) {
47
- if (!videoUrl) return;
48
- isHovering = state;
49
- if (state) videoEl?.play();
50
- else {
51
- videoEl?.pause();
52
- if (imageUrl && videoEl) videoEl.currentTime = 0;
65
+ }
66
+
67
+ $effect(() => {
68
+ if (!containerEl) return;
69
+ const observer = new IntersectionObserver(
70
+ ([entry]) => {
71
+ if (entry.isIntersecting) {
72
+ isVisible = true;
73
+ observer.disconnect();
53
74
  }
54
- }
55
-
56
- $effect(() => {
57
- if (!containerEl) return;
58
- const observer = new IntersectionObserver(
59
- ([entry]) => {
60
- if (entry.isIntersecting) {
61
- isVisible = true;
62
- observer.disconnect();
63
- }
64
- },
65
- { rootMargin: "100px" }
66
- );
67
- observer.observe(containerEl);
68
- return () => observer.disconnect();
69
- });
75
+ },
76
+ { rootMargin: "100px" },
77
+ );
78
+ observer.observe(containerEl);
79
+ return () => observer.disconnect();
80
+ });
70
81
  </script>
71
82
 
72
- <svelte:window onkeydown={isMaximized ? (e) => e.key === "Escape" && (isMaximized = false) : undefined} />
83
+ <svelte:window
84
+ onkeydown={isMaximized
85
+ ? (e) => e.key === "Escape" && (isMaximized = false)
86
+ : undefined}
87
+ />
73
88
 
74
89
  {#snippet maximizeButton()}
75
- <button
76
- type="button"
77
- class="absolute top-2 right-2 z-10 rounded-full bg-black/50 p-1.5 text-white transition-colors hover:bg-black/70"
78
- onclick={(e) => { e.stopPropagation(); isMaximized = true; videoEl?.pause(); }}
79
- aria-label="Maximizar"
80
- >
81
- <HugeiconsIcon icon={Maximize01Icon} size={16} color="currentColor" strokeWidth={1.5} />
82
- </button>
90
+ <button
91
+ type="button"
92
+ class="absolute top-2 right-2 z-10 rounded-full bg-black/50 p-1.5 text-white transition-colors hover:bg-black/70"
93
+ onclick={(e) => {
94
+ e.stopPropagation();
95
+ isMaximized = true;
96
+ videoEl?.pause();
97
+ }}
98
+ aria-label="Maximizar"
99
+ >
100
+ <HugeiconsIcon
101
+ icon={Maximize01Icon}
102
+ size={16}
103
+ color="currentColor"
104
+ strokeWidth={1.5}
105
+ />
106
+ </button>
83
107
  {/snippet}
84
108
 
85
- {#if imageUrl || videoUrl}
109
+ {#if isLoading}
110
+ <Skeleton class="w-full h-12.5 md:h-50 bg-gray-500/90" />
111
+ {:else}
112
+ {#if imageUrl || videoUrl}
86
113
  <div
87
- bind:this={containerEl}
88
- class="relative w-full {className}"
89
- role="button"
90
- tabindex="0"
91
- onmouseenter={() => toggleHover(true)}
92
- onmouseleave={() => toggleHover(false)}
93
- onfocus={() => toggleHover(true)}
94
- onblur={() => toggleHover(false)}
95
- onkeydown={(e) => (e.key === "Enter" || e.key === " ") && (e.preventDefault(), toggleHover(!isHovering))}
114
+ bind:this={containerEl}
115
+ class="relative w-full {className}"
116
+ role="button"
117
+ tabindex="0"
118
+ onmouseenter={() => toggleHover(true)}
119
+ onmouseleave={() => toggleHover(false)}
120
+ onfocus={() => toggleHover(true)}
121
+ onblur={() => toggleHover(false)}
122
+ onkeydown={(e) =>
123
+ (e.key === "Enter" || e.key === " ") &&
124
+ (e.preventDefault(), toggleHover(!isHovering))}
96
125
  >
97
- {#if imageUrl}
98
- <img
99
- src={isVisible ? imageUrl : undefined}
100
- loading="lazy"
101
- decoding="async"
102
- alt={imageUrl}
103
- class="absolute inset-0 w-full {className} rounded-md object-cover transition-opacity duration-500"
104
- class:opacity-0={videoUrl && showVideo}
126
+ {#if imageUrl}
127
+ <img
128
+ src={isVisible ? imageUrl : undefined}
129
+ loading="lazy"
130
+ decoding="async"
131
+ alt={imageUrl}
132
+ class="absolute inset-0 w-full {className} rounded-md object-cover transition-opacity duration-500"
133
+ class:opacity-0={videoUrl && showVideo}
134
+ onerror={(e) => ((e.target as HTMLImageElement).src = ImgPlaceholder)}
135
+ />
136
+ {/if}
137
+
138
+ {#if videoUrl && isVisible}
139
+ <video
140
+ bind:this={videoEl}
141
+ src={videoUrl}
142
+ preload="metadata"
143
+ onloadedmetadata={onLoadedMetadata}
144
+ class="absolute inset-0 w-full {className} rounded-md object-cover transition-opacity duration-500"
145
+ class:opacity-0={imageUrl && !showVideo}
146
+ muted
147
+ loop
148
+ playsinline
149
+ ></video>
150
+ {/if}
151
+
152
+ {#if showVideoBadge}
153
+ <div
154
+ class="pointer-events-none absolute inset-0 flex items-center justify-center transition-opacity duration-300"
155
+ >
156
+ <div
157
+ class="flex h-11 w-11 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm"
158
+ >
159
+ <HugeiconsIcon
160
+ icon={VideoReplayIcon}
161
+ size={22}
162
+ color="white"
163
+ strokeWidth={1.5}
105
164
  />
165
+ </div>
166
+ </div>
167
+ {#if duration}
168
+ <span
169
+ class="absolute bottom-2 right-2 z-10 rounded bg-black/60 px-1.5 py-0.5 text-xs font-medium text-white"
170
+ >
171
+ {duration}
172
+ </span>
106
173
  {/if}
174
+ {/if}
107
175
 
108
- {#if videoUrl && isVisible}
109
- <video
110
- bind:this={videoEl}
111
- src={videoUrl}
112
- preload="metadata"
113
- onloadedmetadata={onLoadedMetadata}
114
- class="absolute inset-0 w-full {className} rounded-md object-cover transition-opacity duration-500"
115
- class:opacity-0={imageUrl && !showVideo}
116
- muted
117
- loop
118
- playsinline
119
- ></video>
120
- {/if}
121
-
122
- {#if showVideoBadge}
123
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center transition-opacity duration-300">
124
- <div class="flex h-11 w-11 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm">
125
- <HugeiconsIcon icon={VideoReplayIcon} size={22} color="white" strokeWidth={1.5} />
126
- </div>
127
- </div>
128
- {#if duration}
129
- <span class="absolute bottom-2 right-2 z-10 rounded bg-black/60 px-1.5 py-0.5 text-xs font-medium text-white">
130
- {duration}
131
- </span>
132
- {/if}
133
- {/if}
134
-
135
- {#if showMaximize}
136
- {@render maximizeButton()}
137
- {/if}
176
+ {#if showMaximize}
177
+ {@render maximizeButton()}
178
+ {/if}
138
179
  </div>
139
- {/if}
180
+ {/if}
140
181
 
141
- {#if isMaximized}
182
+ {#if isMaximized}
142
183
  <div class="fixed inset-0 z-50">
143
- <button
184
+ <button
185
+ type="button"
186
+ class="absolute inset-0 h-full w-full cursor-default bg-black/80"
187
+ onclick={() => (isMaximized = false)}
188
+ aria-label="Fechar modal"
189
+ ></button>
190
+
191
+ <div
192
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
193
+ >
194
+ <div class="pointer-events-auto relative max-h-[90vh] max-w-4xl">
195
+ <button
144
196
  type="button"
145
- class="absolute inset-0 h-full w-full cursor-default bg-black/80"
197
+ class="absolute top-3 right-3 rounded-full bg-red-900/50 p-2 text-white hover:bg-black/70"
146
198
  onclick={() => (isMaximized = false)}
147
- aria-label="Fechar modal"
148
- ></button>
149
-
150
- <div class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4">
151
- <div class="pointer-events-auto relative max-h-[90vh] max-w-4xl">
152
- <button
153
- type="button"
154
- class="absolute top-3 right-3 rounded-full bg-red-900/50 p-2 text-white hover:bg-black/70"
155
- onclick={() => (isMaximized = false)}
156
- aria-label="Fechar"
157
- >
158
- <HugeiconsIcon icon={Cancel01Icon} size={20} color="currentColor" strokeWidth={1.5} />
159
- </button>
160
-
161
- {#if videoUrl && showVideo}
162
- <!-- svelte-ignore a11y_media_has_caption -->
163
- <video src={videoUrl} class="max-h-[90vh] max-w-full rounded-md" controls autoplay playsinline></video>
164
- {:else if imageUrl}
165
- <img src={imageUrl} alt={imageUrl} class="max-h-[90vh] max-w-full rounded-md object-contain" />
166
- {/if}
167
- </div>
199
+ aria-label="Fechar"
200
+ >
201
+ <HugeiconsIcon
202
+ icon={Cancel01Icon}
203
+ size={20}
204
+ color="currentColor"
205
+ strokeWidth={1.5}
206
+ />
207
+ </button>
208
+
209
+ {#if videoUrl && showVideo}
210
+ <!-- svelte-ignore a11y_media_has_caption -->
211
+ <video
212
+ src={videoUrl}
213
+ class="max-h-[90vh] max-w-full rounded-md"
214
+ controls
215
+ autoplay
216
+ playsinline
217
+ ></video>
218
+ {:else if imageUrl}
219
+ <img
220
+ src={imageUrl}
221
+ alt={imageUrl}
222
+ class="max-h-[90vh] max-w-full rounded-md object-contain"
223
+ />
224
+ {/if}
168
225
  </div>
226
+ </div>
169
227
  </div>
170
- {/if}
228
+ {/if}
229
+ {/if}
@@ -4,6 +4,7 @@ type Props = {
4
4
  class?: string;
5
5
  isImageButtonMaximized?: boolean;
6
6
  isVideoButtonMaximized?: boolean;
7
+ isLoading?: boolean;
7
8
  };
8
9
  declare const CardThumbnailVideo: import("svelte").Component<Props, {}, "">;
9
10
  type CardThumbnailVideo = ReturnType<typeof CardThumbnailVideo>;
@@ -14,6 +14,7 @@ export interface CardMediaProps {
14
14
  className?: string;
15
15
  tags?: CardTagsProps[];
16
16
  isFavorite?: boolean;
17
+ isLoading?: boolean;
17
18
  startNumber?: number;
18
19
  isTagBorderBottom?: boolean;
19
20
  buttonProfileClass?: string;
@@ -35,6 +36,7 @@ export interface CardProfileProps {
35
36
  tags?: CardTagsProps[];
36
37
  isFavorite?: boolean;
37
38
  startNumber?: number;
39
+ isLoading?: boolean;
38
40
  isTagBorderBottom?: boolean;
39
41
  buttonProfileClass?: string;
40
42
  buttonDetailsClass?: string;
@@ -0,0 +1,10 @@
1
+ import type { CardMediaProps } from "../../../card/types";
2
+ import type { CarouselSlotProps } from "../../../panel/type";
3
+ import type { CarouselHeaderProps } from "../../types";
4
+ export interface CarouselMediaProps {
5
+ headerProps?: CarouselHeaderProps;
6
+ slotProps?: CarouselSlotProps;
7
+ variant?: 1 | 2;
8
+ items: CardMediaProps[];
9
+ isLoading?: boolean;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import CarouselGridSlot from "../../shared/ui/CarouselGridSlot.svelte";
3
+ import * as Carousel from "../../../../../ui/carousel/index.js";
4
+ import type { CarouselMediaProps } from "../types";
5
+ import CarouselHeader from "../../../shared/ui/CarouselHeader.svelte";
6
+ import { useDevice } from "../../../../../../hooks/responsive.svelte";
7
+ import CardMedia from "../../../../card/media/CardMedia.svelte";
8
+
9
+ const {
10
+ headerProps,
11
+ slotProps,
12
+ items,
13
+ variant,
14
+ isLoading = false,
15
+ }: CarouselMediaProps = $props();
16
+
17
+ const responsive = useDevice();
18
+ </script>
19
+
20
+ <CarouselHeader {...headerProps}>
21
+ <CarouselGridSlot {slotProps} totalItems={items.length}>
22
+ {#if isLoading}
23
+ {#each Array.from( { length: responsive.isMobile ? 5 : 9 }, ) as _, i (`loading-${i}`)}
24
+ {#if responsive.isMobile}
25
+ <Carousel.Item class="pl-2 basis-auto">
26
+ <CardMedia id={i} {variant} isLoading />
27
+ </Carousel.Item>
28
+ {:else}
29
+ <div>
30
+ <CardMedia id={i} {variant} isLoading />
31
+ </div>
32
+ {/if}
33
+ {/each}
34
+ {:else}
35
+ {#each items as item, i (`media-${item.id ?? i}`)}
36
+ {#if responsive.isMobile}
37
+ <Carousel.Item class="pl-2 basis-auto">
38
+ <CardMedia {...item} {variant} />
39
+ </Carousel.Item>
40
+ {:else}
41
+ <div>
42
+ <CardMedia {...item} {variant} />
43
+ </div>
44
+ {/if}
45
+ {/each}
46
+ {/if}
47
+ </CarouselGridSlot>
48
+ </CarouselHeader>
@@ -0,0 +1,4 @@
1
+ import type { CarouselMediaProps } from "../types";
2
+ declare const CarouselGridMedia: import("svelte").Component<CarouselMediaProps, {}, "">;
3
+ type CarouselGridMedia = ReturnType<typeof CarouselGridMedia>;
4
+ export default CarouselGridMedia;
@@ -0,0 +1,10 @@
1
+ import type { CardProfileProps } from "../../../card/types";
2
+ import type { CarouselSlotProps } from "../../../panel/type";
3
+ import type { CarouselHeaderProps } from "../../types";
4
+ export interface CarouselProfileProps {
5
+ headerProps?: CarouselHeaderProps;
6
+ slotProps?: CarouselSlotProps;
7
+ variant?: 1 | 2;
8
+ items: CardProfileProps[];
9
+ isLoading?: boolean;
10
+ }
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import CarouselGridSlot from "../../shared/ui/CarouselGridSlot.svelte";
3
+ import * as Carousel from "../../../../../ui/carousel/index.js";
4
+ import CarouselHeader from "../../../shared/ui/CarouselHeader.svelte";
5
+ import { useDevice } from "../../../../../../hooks/responsive.svelte";
6
+ import type { CarouselProfileProps } from "../types";
7
+ import CardProfile from "../../../../card/profile/CardProfile.svelte";
8
+
9
+ const {
10
+ headerProps,
11
+ slotProps,
12
+ items,
13
+ variant,
14
+ isLoading = false,
15
+ }: CarouselProfileProps = $props();
16
+
17
+ const responsive = useDevice();
18
+ </script>
19
+
20
+ <CarouselHeader {...headerProps}>
21
+ <CarouselGridSlot
22
+ {slotProps}
23
+ totalItems={items.length}
24
+ >
25
+ {#if isLoading}
26
+ {#each Array.from({ length: responsive.isMobile ? 5 : 9 }) as _, i (`loading-${i}`)}
27
+ {#if responsive.isMobile}
28
+ <Carousel.Item class="pl-2 basis-auto">
29
+ <CardProfile id={i} {variant} isLoading />
30
+ </Carousel.Item>
31
+ {:else}
32
+ <div>
33
+ <CardProfile id={i} {variant} isLoading />
34
+ </div>
35
+ {/if}
36
+ {/each}
37
+ {:else}
38
+ {#each items as item, i (`profile-${item.id ?? i}`)}
39
+ {#if responsive.isMobile}
40
+ <Carousel.Item class="pl-2 basis-auto">
41
+ <CardProfile {...item} {variant} />
42
+ </Carousel.Item>
43
+ {:else}
44
+ <div>
45
+ <CardProfile {...item} {variant} />
46
+ </div>
47
+ {/if}
48
+ {/each}
49
+ {/if}
50
+ </CarouselGridSlot>
51
+ </CarouselHeader>
@@ -0,0 +1,4 @@
1
+ import type { CarouselProfileProps } from "../types";
2
+ declare const CarouselGridProfile: import("svelte").Component<CarouselProfileProps, {}, "">;
3
+ type CarouselGridProfile = ReturnType<typeof CarouselGridProfile>;
4
+ export default CarouselGridProfile;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import CarouselSlot from "../../../../panel/CarouselSlot.svelte";
4
+ import type { CarouselSlotProps } from "../../../../panel/type";
5
+ import { useDevice } from "../../../../../../hooks/responsive.svelte";
6
+ import Autoplay from "embla-carousel-autoplay";
7
+
8
+ type Props = {
9
+ slotProps?: CarouselSlotProps;
10
+ totalItems: number;
11
+ children: Snippet;
12
+ };
13
+
14
+ const {
15
+ slotProps,
16
+ totalItems,
17
+ children,
18
+ }: Props = $props();
19
+
20
+ const responsive = useDevice();
21
+
22
+ const colsClass = $derived(
23
+ totalItems <= 4 ? "grid-cols-2" : "grid-cols-3"
24
+ );
25
+ </script>
26
+
27
+ {#if responsive.isMobile}
28
+ <CarouselSlot
29
+ {...slotProps}
30
+ plugins={[Autoplay({ delay: 4000, stopOnInteraction: true })]}
31
+ >
32
+ {@render children()}
33
+ </CarouselSlot>
34
+ {:else}
35
+ <div class="grid {colsClass} gap-4 p-2">
36
+ {@render children()}
37
+ </div>
38
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { CarouselSlotProps } from "../../../../panel/type";
3
+ type Props = {
4
+ slotProps?: CarouselSlotProps;
5
+ totalItems: number;
6
+ children: Snippet;
7
+ };
8
+ declare const CarouselGridSlot: import("svelte").Component<Props, {}, "">;
9
+ type CarouselGridSlot = ReturnType<typeof CarouselGridSlot>;
10
+ export default CarouselGridSlot;