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.
- package/dist/components/core/banner/BannerNotification.svelte +94 -0
- package/dist/components/core/banner/BannerNotification.svelte.d.ts +4 -0
- package/dist/components/core/banner/CardAdBanner.svelte +74 -0
- package/dist/components/core/banner/CardAdBanner.svelte.d.ts +4 -0
- package/dist/components/core/banner/types.d.ts +42 -0
- package/dist/components/core/banner/types.js +1 -0
- package/dist/components/core/card/highlight/01/CardHighlight01.svelte +5 -1
- package/dist/components/core/card/highlight/02/CardHighlight02.svelte +5 -3
- package/dist/components/core/card/highlight/data.js +1 -4
- package/dist/components/core/card/media/01/CardMedia01.svelte +124 -98
- package/dist/components/core/card/media/02/CardMedia02.svelte +125 -99
- package/dist/components/core/card/media/CardMedia.svelte +10 -9
- package/dist/components/core/card/media/CardMedia.svelte.d.ts +1 -0
- package/dist/components/core/card/profile/01/CardProfile01.svelte +104 -86
- package/dist/components/core/card/profile/02/CardProfile02.svelte +103 -87
- package/dist/components/core/card/profile/CardProfile.svelte +8 -7
- package/dist/components/core/card/profile/CardProfile.svelte.d.ts +1 -0
- package/dist/components/core/card/promotion/CardPromotion.svelte +6 -4
- package/dist/components/core/card/shared/CardButton.svelte +30 -21
- package/dist/components/core/card/shared/CardButton.svelte.d.ts +1 -0
- package/dist/components/core/card/shared/CardDescription.svelte +30 -30
- package/dist/components/core/card/shared/CardStarRating.svelte +18 -14
- package/dist/components/core/card/shared/CardStarRating.svelte.d.ts +1 -0
- package/dist/components/core/card/shared/CardStartPrice.svelte +1 -1
- package/dist/components/core/card/shared/CardThumbnailVideo.svelte +207 -148
- package/dist/components/core/card/shared/CardThumbnailVideo.svelte.d.ts +1 -0
- package/dist/components/core/card/types.d.ts +2 -0
- package/dist/components/core/carousel/grid/media/types.d.ts +10 -0
- package/dist/components/core/carousel/grid/media/types.js +1 -0
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +48 -0
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte.d.ts +4 -0
- package/dist/components/core/carousel/grid/profile/types.d.ts +10 -0
- package/dist/components/core/carousel/grid/profile/types.js +1 -0
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +51 -0
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte.d.ts +4 -0
- package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte +38 -0
- package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte.d.ts +10 -0
- package/dist/components/core/carousel/hero/CarouselHero.svelte +21 -19
- package/dist/components/core/carousel/hero/CarouselHero.svelte.d.ts +1 -0
- package/dist/components/core/carousel/highlights/types.d.ts +4 -4
- package/dist/components/core/carousel/highlights/ui/CarouselHighlights.svelte +4 -22
- package/dist/components/core/carousel/promotion/types.d.ts +5 -5
- package/dist/components/core/carousel/promotion/ui/CarouselPromotion.svelte +4 -21
- package/dist/components/core/carousel/shared/ui/CarouselHeader.svelte +6 -1
- package/dist/components/core/carousel/types.d.ts +1 -0
- package/dist/components/core/panel/CarouselSlot.svelte +3 -14
- package/dist/components/core/panel/CarouselSlot.svelte.d.ts +2 -12
- package/dist/components/core/panel/type.d.ts +11 -0
- package/dist/components/core/panel/type.js +1 -0
- package/dist/globals.css +351 -43
- package/dist/index.d.ts +15 -5
- package/dist/index.js +15 -5
- package/package.json +1 -1
|
@@ -1,170 +1,229 @@
|
|
|
1
1
|
<!-- CardMedia.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
83
|
+
<svelte:window
|
|
84
|
+
onkeydown={isMaximized
|
|
85
|
+
? (e) => e.key === "Escape" && (isMaximized = false)
|
|
86
|
+
: undefined}
|
|
87
|
+
/>
|
|
73
88
|
|
|
74
89
|
{#snippet maximizeButton()}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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,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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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;
|