negodesign 0.0.25 → 0.0.26

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 (36) hide show
  1. package/dist/assets/start.png +0 -0
  2. package/dist/components/core/card/highlight/01/CardHighlight01.svelte +91 -0
  3. package/dist/components/core/card/highlight/01/CardHighlight01.svelte.d.ts +8 -0
  4. package/dist/components/core/card/highlight/02/CardHighlight02.svelte +93 -0
  5. package/dist/components/core/card/highlight/02/CardHighlight02.svelte.d.ts +8 -0
  6. package/dist/components/core/card/highlight/CardHighlight.svelte +13 -58
  7. package/dist/components/core/card/highlight/CardHighlight.svelte.d.ts +5 -2
  8. package/dist/components/core/card/highlight/data.js +32 -1
  9. package/dist/components/core/card/media/01/CardMedia01.svelte +8 -11
  10. package/dist/components/core/card/media/02/CardMedia02.svelte +4 -7
  11. package/dist/components/core/card/profile/01/CardProfile01.svelte +7 -10
  12. package/dist/components/core/card/profile/02/CardProfile02.svelte +2 -1
  13. package/dist/components/core/card/promotion/CardPromotion.svelte +49 -39
  14. package/dist/components/core/card/promotion/data.js +31 -0
  15. package/dist/components/core/card/shared/CardDescription.svelte +23 -8
  16. package/dist/components/core/card/shared/CardDescription.svelte.d.ts +3 -0
  17. package/dist/components/core/card/shared/CardStartPrice.svelte +34 -0
  18. package/dist/components/core/card/shared/CardStartPrice.svelte.d.ts +8 -0
  19. package/dist/components/core/card/shared/CardTags.svelte +32 -15
  20. package/dist/components/core/card/shared/CardTags.svelte.d.ts +2 -1
  21. package/dist/components/core/card/types.d.ts +13 -1
  22. package/dist/components/core/carousel/badge/ui/CarouselBadge.svelte +8 -5
  23. package/dist/components/core/carousel/highlights/types.d.ts +3 -0
  24. package/dist/components/core/carousel/highlights/ui/CarouselHighlights.svelte +27 -5
  25. package/dist/components/core/carousel/promotion/types.d.ts +2 -0
  26. package/dist/components/core/carousel/promotion/ui/CarouselPromotion.svelte +28 -5
  27. package/dist/components/core/carousel/shared/ui/CarouselHeader.svelte +10 -5
  28. package/dist/components/core/carousel/shared/ui/CarouselHeader.svelte.d.ts +6 -1
  29. package/dist/components/core/carousel/types.d.ts +1 -2
  30. package/dist/components/core/nav/ui/nav-menu.svelte +3 -3
  31. package/dist/components/core/panel/CarouselSlot.svelte +37 -12
  32. package/dist/components/core/panel/CarouselSlot.svelte.d.ts +5 -2
  33. package/dist/globals.css +139 -17
  34. package/dist/hooks/responsive.svelte.d.ts +1 -1
  35. package/dist/hooks/responsive.svelte.js +1 -1
  36. package/package.json +3 -3
@@ -1,8 +1,10 @@
1
+ import { GlobalIcon, MapPin, NecklaceIcon } from "@hugeicons/core-free-icons";
1
2
  const items = [
2
3
  {
3
4
  id: 1,
4
5
  imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
5
6
  title: 'Hotel Baía Azul',
7
+ content: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
6
8
  isFavorite: true,
7
9
  newPrice: 1000,
8
10
  oldPrice: 2000,
@@ -10,22 +12,34 @@ const items = [
10
12
  onClickBuy: () => alert('Comprar'),
11
13
  onClickShop: (id) => alert(`Shop ${id}`),
12
14
  isCart: true,
15
+ tags: [
16
+ { icon: GlobalIcon, text: 'Luanda', },
17
+ { icon: MapPin, text: 'Kilamba', },
18
+ { icon: NecklaceIcon, text: 'Rua 19', },
19
+ ]
13
20
  },
14
21
  {
15
22
  id: 2,
16
23
  imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
17
24
  title: 'Miramar Suites',
25
+ content: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
18
26
  newPrice: 1000,
19
27
  oldPrice: 2000,
20
28
  buttonBuyText: 'Comprar',
21
29
  onClickBuy: (id) => alert(`Buy ${id}`),
22
30
  onClickShop: (id) => alert(`Shop ${id}`),
23
31
  isCart: true,
32
+ tags: [
33
+ { icon: GlobalIcon, text: 'Luanda', },
34
+ { icon: MapPin, text: 'Kilamba', },
35
+ { icon: NecklaceIcon, text: 'Rua 19', },
36
+ ]
24
37
  },
25
38
  {
26
39
  id: 3,
27
40
  imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
28
41
  title: 'Palm Resort & Spa',
42
+ content: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
29
43
  isFavorite: true,
30
44
  newPrice: 1000,
31
45
  oldPrice: 2000,
@@ -33,22 +47,34 @@ const items = [
33
47
  onClickBuy: (id) => alert(`Buy ${id}`),
34
48
  onClickShop: (id) => alert(`Shop ${id}`),
35
49
  isCart: true,
50
+ tags: [
51
+ { icon: GlobalIcon, text: 'Luanda', },
52
+ { icon: MapPin, text: 'Kilamba', },
53
+ { icon: NecklaceIcon, text: 'Rua 19', },
54
+ ]
36
55
  },
37
56
  {
38
57
  id: 4,
39
58
  imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
40
59
  title: 'Hotel Central Park',
60
+ content: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
41
61
  newPrice: 1000,
42
62
  oldPrice: 2000,
43
63
  buttonBuyText: 'Comprar',
44
64
  onClickBuy: (id) => alert(`Buy ${id}`),
45
65
  onClickShop: (id) => alert(`Shop ${id}`),
46
66
  isCart: true,
67
+ tags: [
68
+ { icon: GlobalIcon, text: 'Luanda', },
69
+ { icon: MapPin, text: 'Kilamba', },
70
+ { icon: NecklaceIcon, text: 'Rua 19', },
71
+ ]
47
72
  },
48
73
  {
49
74
  id: 5,
50
75
  imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
51
76
  title: 'Villa Serena Boutique',
77
+ content: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
52
78
  isFavorite: true,
53
79
  newPrice: 1000,
54
80
  oldPrice: 2000,
@@ -56,6 +82,11 @@ const items = [
56
82
  onClickBuy: (id) => alert(`Buy ${id}`),
57
83
  onClickShop: (id) => alert(`Shop ${id}`),
58
84
  isCart: true,
85
+ tags: [
86
+ { icon: GlobalIcon, text: 'Luanda', },
87
+ { icon: MapPin, text: 'Kilamba', },
88
+ { icon: NecklaceIcon, text: 'Rua 19', },
89
+ ]
59
90
  },
60
91
  ];
61
92
  export default items;
@@ -3,21 +3,36 @@
3
3
  import { HugeiconsIcon } from "@hugeicons/svelte";
4
4
  import TruncatableText from "./TruncatableText.svelte";
5
5
  import { t } from "../../../../i18n";
6
-
6
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
7
+
7
8
  type CardDescriptionProps = {
8
9
  content?: string;
10
+ isLoading?: boolean;
11
+ isDescriptionIcon?: boolean;
12
+ isDescriptionLabel?: boolean;
9
13
  };
10
14
 
11
- let { content }: CardDescriptionProps = $props();
15
+ let {
16
+ content,
17
+ isLoading = false,
18
+ isDescriptionIcon = true,
19
+ isDescriptionLabel = true,
20
+ }: CardDescriptionProps = $props();
12
21
  </script>
13
22
 
14
- {#if content}
15
- <div class="mb-1 text-[14px] flex items-center gap-1">
16
- <HugeiconsIcon icon={Message01Icon} class="h-4 w-4" />
17
- <div>{$t("label.description")}</div>
18
- </div>
23
+ {#if isLoading}
24
+ <Skeleton class="w-full h-5" />
25
+ {:else if content}
26
+ {#if isDescriptionLabel}
27
+ <div class="mb-1 text-[14px] md:text-[15px] flex items-center gap-1">
28
+ {#if isDescriptionIcon}
29
+ <HugeiconsIcon icon={Message01Icon} class="h-4 w-4" />
30
+ {/if}
31
+ <div>{$t("label.description")}</div>
32
+ </div>
33
+ {/if}
19
34
  <TruncatableText
20
35
  text={content}
21
- class="text-justify text-[13px] text-gray-800 dark:text-gray-50 mt-3 my-4"
36
+ class="text-justify text-[13px] md:text-[14px] text-gray-800 dark:text-gray-50 mt-3 my-4"
22
37
  />
23
38
  {/if}
@@ -1,5 +1,8 @@
1
1
  type CardDescriptionProps = {
2
2
  content?: string;
3
+ isLoading?: boolean;
4
+ isDescriptionIcon?: boolean;
5
+ isDescriptionLabel?: boolean;
3
6
  };
4
7
  declare const CardDescription: import("svelte").Component<CardDescriptionProps, {}, "">;
5
8
  type CardDescription = ReturnType<typeof CardDescription>;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import StartBg from "../../../../assets/start.png";
3
+
4
+ type Props = {
5
+ newPrice?: string | number;
6
+ oldPrice?: string | number;
7
+ type?: "start" | "normal";
8
+ };
9
+
10
+ let { newPrice, oldPrice, type = "normal" }: Props = $props();
11
+ </script>
12
+
13
+ <div class="relative min-w-25 md:min-w-32">
14
+ {#if type == "start"}
15
+ <img
16
+ src={StartBg}
17
+ alt=""
18
+ class="shadow rounded-tl-lg rounded-bl-lg md:w-20 md:h-20 lg:w-32 lg:h-24 absolute"
19
+ />
20
+ {/if}
21
+ {#if newPrice && oldPrice}
22
+ <div class="z-20 flex justify-center items-center">
23
+ <div class="font-extrabold md:text-3xl lg:text-4xl">{newPrice}</div>
24
+ <div class="mx-2">/</div>
25
+ <div
26
+ class="font-semibold line-through text-red-600 text-[10px] md:text-xl"
27
+ >
28
+ {oldPrice}
29
+ </div>
30
+ </div>
31
+ {:else}
32
+ <div class="z-20 font-bold">{newPrice}</div>
33
+ {/if}
34
+ </div>
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ newPrice?: string | number;
3
+ oldPrice?: string | number;
4
+ type?: "start" | "normal";
5
+ };
6
+ declare const CardStartPrice: import("svelte").Component<Props, {}, "">;
7
+ type CardStartPrice = ReturnType<typeof CardStartPrice>;
8
+ export default CardStartPrice;
@@ -1,12 +1,14 @@
1
1
  <script lang="ts">
2
2
  import { HugeiconsIcon } from "@hugeicons/svelte";
3
3
  import type { CardTagsProps } from "../types";
4
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
4
5
 
5
6
  type Props = {
6
7
  className?: string;
7
- tags: CardTagsProps[];
8
+ tags?: CardTagsProps[];
8
9
  isTagBorderBottom?: boolean;
9
10
  isWrap?: boolean;
11
+ isLoading?: boolean;
10
12
  };
11
13
 
12
14
  const {
@@ -14,9 +16,10 @@
14
16
  className,
15
17
  isTagBorderBottom,
16
18
  isWrap = false,
19
+ isLoading = false,
17
20
  }: Props = $props();
18
21
 
19
- let isJustify = $derived(tags.length == 2 || tags.length == 3);
22
+ let isJustify = $derived(tags?.length == 2 || tags?.length == 3);
20
23
  </script>
21
24
 
22
25
  {#snippet createBadge(tag: CardTagsProps)}
@@ -35,22 +38,36 @@
35
38
  </div>
36
39
  {/snippet}
37
40
 
38
- {#if tags && tags.length > 0}
39
- {#if isWrap}
40
- <div class="flex flex-wrap gap-2 md:gap-5 {className}">
41
+ {#snippet skeletonTag()}
42
+ <div class="my-2 flex gap-5">
43
+ {#each Array.from({ length: 3 }) as it, i (i)}
44
+ <Skeleton class="h-5 w-25 bg-black/50" />
45
+ {/each}
46
+ </div>
47
+ {/snippet}
48
+
49
+ {#if isWrap}
50
+ <div class="flex flex-wrap gap-2 md:gap-5 {className}">
51
+ {#if isLoading}
52
+ {@render skeletonTag()}
53
+ {:else if tags && tags.length > 0}
41
54
  {#each tags as tag, i (i)}
42
- {@render createBadge(tag)}
55
+ {@render createBadge(tag)}
43
56
  {/each}
44
- </div>
45
- {:else}
46
- <div class="flex overflow-x-auto no-scrollbar w-full
57
+ {/if}
58
+ </div>
59
+ {:else}
60
+ <div
61
+ class="flex overflow-x-auto no-scrollbar w-full
47
62
  {isJustify ? 'justify-between' : 'gap-4 md:gap-5 lg:gap-10'}
48
- {className}">
63
+ {className}"
64
+ >
65
+ {#if isLoading}
66
+ {@render skeletonTag()}
67
+ {:else if tags && tags.length > 0}
49
68
  {#each tags as tag, i (i)}
50
- {@render createBadge(tag)}
69
+ {@render createBadge(tag)}
51
70
  {/each}
52
- </div>
53
- {/if}
54
- {:else}
55
- <div class="w-full h-5 {className}"></div>
71
+ {/if}
72
+ </div>
56
73
  {/if}
@@ -1,9 +1,10 @@
1
1
  import type { CardTagsProps } from "../types";
2
2
  type Props = {
3
3
  className?: string;
4
- tags: CardTagsProps[];
4
+ tags?: CardTagsProps[];
5
5
  isTagBorderBottom?: boolean;
6
6
  isWrap?: boolean;
7
+ isLoading?: boolean;
7
8
  };
8
9
  declare const CardTags: import("svelte").Component<Props, {}, "">;
9
10
  type CardTags = ReturnType<typeof CardTags>;
@@ -47,13 +47,17 @@ export interface CardPromotionProps {
47
47
  logo?: string;
48
48
  imageUrl?: string;
49
49
  title?: string;
50
+ content?: string;
50
51
  className?: string;
51
52
  isCart?: boolean;
52
- isFavorite?: boolean;
53
53
  isLoading?: boolean;
54
+ isFavorite?: boolean;
55
+ tags?: CardTagsProps[];
54
56
  oldPrice?: string | number;
55
57
  newPrice?: string | number;
56
58
  buttonBuyText?: string;
59
+ isDescriptionIcon?: boolean;
60
+ isDescriptionLabel?: boolean;
57
61
  onClickBuy?: (id: string | number) => void;
58
62
  onClickShop?: (id: string | number) => void;
59
63
  onClickFavorite?: (id: string | number) => void;
@@ -65,4 +69,12 @@ export interface CardHighlightProps {
65
69
  title?: string;
66
70
  className?: string;
67
71
  isFavorite?: boolean;
72
+ isLoading?: boolean;
73
+ content?: string;
74
+ buttonText?: string;
75
+ isDescriptionIcon?: boolean;
76
+ isDescriptionLabel?: boolean;
77
+ tags?: CardTagsProps[];
78
+ onClickFavorite?: (id: string | number) => void;
79
+ onClickBtn?: (id: string | number) => void;
68
80
  }
@@ -4,6 +4,7 @@
4
4
  import { HugeiconsIcon } from "@hugeicons/svelte";
5
5
  import CarouselSlot from "../../../panel/CarouselSlot.svelte";
6
6
  import Skeleton from "../../../../ui/skeleton/skeleton.svelte";
7
+ import { useDevice } from "../../../../../hooks/responsive.svelte";
7
8
 
8
9
  const {
9
10
  items,
@@ -22,6 +23,7 @@
22
23
  }: CarouselBadgeProps = $props();
23
24
 
24
25
  const DEFAULT_IMG_OR_ICON_CLASS = "size-3";
26
+ const responsive = useDevice();
25
27
  </script>
26
28
 
27
29
  <CarouselSlot
@@ -30,11 +32,12 @@
30
32
  {buttonPreviousAndNextClass}
31
33
  >
32
34
  {#if isLoading}
33
- {#each Array.from({ length: 10 }) as it (it)}
34
- <Carousel.Item>
35
- <Skeleton class="h-4 w-37.5" />
36
- </Carousel.Item>
37
- {/each}
35
+ <div class="flex items-center justify-between w-full gap-1 md:gap-2">
36
+ {#each Array.from({ length: responsive.isMobile ? 2 : 10 }) as it, i (i)}
37
+ <Skeleton class="h-4 w-25 bg-black/20" />
38
+ <div class="src-only">{it}</div>
39
+ {/each}
40
+ </div>
38
41
  {:else}
39
42
  {#each items as item, i (i)}
40
43
  <Carousel.Item
@@ -2,8 +2,11 @@ import type { CardHighlightProps } from "../../card/types";
2
2
  import type { CarouselHeaderProps } from "../types";
3
3
  export interface CarouselHighlightsProps extends CarouselHeaderProps {
4
4
  items: CardHighlightProps[];
5
+ varient?: 1 | 2;
5
6
  isBorderBottom?: boolean;
6
7
  isLoading?: boolean;
8
+ isDescriptionIcon?: boolean;
9
+ isDescriptionLabel?: boolean;
7
10
  isButtonPreviousAndNext?: boolean;
8
11
  buttonPreviousAndNextClass?: string;
9
12
  }
@@ -2,7 +2,10 @@
2
2
  import CardHighlight from "../../../card/highlight/CardHighlight.svelte";
3
3
  import CarouselHeader from "../../shared/ui/CarouselHeader.svelte";
4
4
  import CarouselSlot from "../../../panel/CarouselSlot.svelte";
5
+ import * as Carousel from "../../../../ui/carousel/index.js";
5
6
  import type { CarouselHighlightsProps } from "../types";
7
+ import { useDevice } from "../../../../../hooks/responsive.svelte";
8
+ import Autoplay from "embla-carousel-autoplay";
6
9
 
7
10
  const {
8
11
  title,
@@ -14,8 +17,13 @@
14
17
  buttonPreviousAndNextClass = "",
15
18
  containerClass = "",
16
19
  isLoading = false,
20
+ varient = 1,
21
+ isDescriptionIcon,
22
+ isDescriptionLabel,
17
23
  onButtonViewAll,
18
24
  }: CarouselHighlightsProps = $props();
25
+
26
+ const responsive = useDevice();
19
27
  </script>
20
28
 
21
29
  <CarouselHeader
@@ -31,11 +39,25 @@
31
39
  {isButtonPreviousAndNext}
32
40
  {buttonPreviousAndNextClass}
33
41
  containerClass="w-full"
42
+ plugins={[Autoplay({ delay: 4000, stopOnInteraction: true })]}
34
43
  >
35
- {#each items as item (item.id)}
36
- <div class="mx-2">
37
- <CardHighlight {...item} {isLoading} />
38
- </div>
39
- {/each}
44
+ {#if isLoading}
45
+ {#each Array.from( { length: responsive.isMobile ? 5 : 10 }, ) as _, i (`loading-${i}`)}
46
+ <Carousel.Item class="pl-2 md:basis-1/2 lg:basis-auto">
47
+ <CardHighlight id={i} {isLoading} {varient} />
48
+ </Carousel.Item>
49
+ {/each}
50
+ {:else}
51
+ {#each items as item (item.id)}
52
+ <Carousel.Item class="pl-2 md:basis-1/2 lg:basis-auto">
53
+ <CardHighlight
54
+ {...item}
55
+ {varient}
56
+ {isDescriptionIcon}
57
+ {isDescriptionLabel}
58
+ />
59
+ </Carousel.Item>
60
+ {/each}
61
+ {/if}
40
62
  </CarouselSlot>
41
63
  </CarouselHeader>
@@ -6,4 +6,6 @@ export interface CarouselPromotionProps extends CarouselHeaderProps {
6
6
  isLoading?: boolean;
7
7
  isButtonPreviousAndNext?: boolean;
8
8
  buttonPreviousAndNextClass?: string;
9
+ isDescriptionIcon?: boolean;
10
+ isDescriptionLabel?: boolean;
9
11
  }
@@ -1,8 +1,11 @@
1
1
  <script lang="ts">
2
2
  import CardPromotion from "../../../card/promotion/CardPromotion.svelte";
3
3
  import CarouselSlot from "../../../panel/CarouselSlot.svelte";
4
+ import * as Carousel from "../../../../ui/carousel/index.js";
4
5
  import type { CarouselPromotionProps } from "../types";
5
6
  import CarouselHeader from "../../shared/ui/CarouselHeader.svelte";
7
+ import { useDevice } from "../../../../../hooks/responsive.svelte";
8
+ import Autoplay from "embla-carousel-autoplay";
6
9
 
7
10
  const {
8
11
  title,
@@ -14,8 +17,13 @@
14
17
  isLoading = false,
15
18
  buttonPreviousAndNextClass = "",
16
19
  containerClass = "",
20
+ positionButtonPreviousAndNext,
21
+ isDescriptionIcon,
22
+ isDescriptionLabel,
17
23
  onButtonViewAll,
18
24
  }: CarouselPromotionProps = $props();
25
+
26
+ const responsive = useDevice();
19
27
  </script>
20
28
 
21
29
  <CarouselHeader
@@ -30,12 +38,27 @@
30
38
  isBorderBottom={false}
31
39
  {isButtonPreviousAndNext}
32
40
  {buttonPreviousAndNextClass}
41
+ {positionButtonPreviousAndNext}
33
42
  containerClass="w-full"
43
+ plugins={[Autoplay({ delay: 4000, stopOnInteraction: true })]}
34
44
  >
35
- {#each items as item (item.id)}
36
- <div class="mx-2">
37
- <CardPromotion {...item} {isLoading} />
38
- </div>
39
- {/each}
45
+ {#if isLoading}
46
+ {#each Array.from( { length: responsive.isMobile ? 5 : 10 }, ) as _, i (`loading-${i}`)}
47
+ <Carousel.Item class="pl-2 md:basis-1/2 lg:basis-1/4">
48
+ <CardPromotion
49
+ id={i}
50
+ {isLoading}
51
+ {isDescriptionIcon}
52
+ {isDescriptionLabel}
53
+ />
54
+ </Carousel.Item>
55
+ {/each}
56
+ {:else}
57
+ {#each items as item (item.id)}
58
+ <Carousel.Item class="pl-2 md:basis-1/2 lg:basis-1/4">
59
+ <CardPromotion {...item} {isDescriptionIcon} {isDescriptionLabel} />
60
+ </Carousel.Item>
61
+ {/each}
62
+ {/if}
40
63
  </CarouselSlot>
41
64
  </CarouselHeader>
@@ -4,6 +4,11 @@
4
4
  import { HugeiconsIcon } from "@hugeicons/svelte";
5
5
  import { ArrowRight02Icon } from "@hugeicons/core-free-icons";
6
6
  import type { CarouselHeaderProps } from "../../types";
7
+ import type { Snippet } from "svelte";
8
+
9
+ type Props = {
10
+ children: Snippet;
11
+ };
7
12
 
8
13
  const {
9
14
  title,
@@ -13,19 +18,19 @@
13
18
  containerClass = "",
14
19
  onButtonViewAll,
15
20
  children,
16
- }: CarouselHeaderProps = $props();
21
+ }: CarouselHeaderProps & Props = $props();
17
22
  </script>
18
23
 
19
24
  <div class="w-full flex justify-center">
20
- <div class="w-[95%] p-2 rounded-xl {containerClass}">
21
- <div class="ml-10 flex flex-col gap-1">
25
+ <div class="w-[95%] p-2 rounded-xl border {containerClass}">
26
+ <div class="flex flex-col gap-1">
22
27
  <div class="flex justify-between">
23
28
  <div>
24
29
  {#if title}
25
30
  <h1 class={`text-xl ${titleClass}`}>{title}</h1>
26
31
  {/if}
27
32
  {#if description}
28
- <div class={`mt-3 w-11/12 ${descriptionClass}`}>
33
+ <div class={`mt-3 ${descriptionClass}`}>
29
34
  {description}
30
35
  </div>
31
36
  {/if}
@@ -45,6 +50,6 @@
45
50
  {/if}
46
51
  </div>
47
52
  </div>
53
+ <div>{@render children()}</div>
48
54
  </div>
49
- <div>{@render children()}</div>
50
55
  </div>
@@ -1,4 +1,9 @@
1
1
  import type { CarouselHeaderProps } from "../../types";
2
- declare const CarouselHeader: import("svelte").Component<CarouselHeaderProps, {}, "">;
2
+ import type { Snippet } from "svelte";
3
+ type Props = {
4
+ children: Snippet;
5
+ };
6
+ type $$ComponentProps = CarouselHeaderProps & Props;
7
+ declare const CarouselHeader: import("svelte").Component<$$ComponentProps, {}, "">;
3
8
  type CarouselHeader = ReturnType<typeof CarouselHeader>;
4
9
  export default CarouselHeader;
@@ -1,6 +1,5 @@
1
1
  import type { IconSvgElement } from "@hugeicons/svelte";
2
2
  import type { NavMenuProps } from "../nav/data/nav-menu";
3
- import type { Snippet } from "svelte";
4
3
  export interface ItemCarousel {
5
4
  value: string;
6
5
  label: string;
@@ -17,7 +16,7 @@ export interface CarouselHeaderProps {
17
16
  descriptionClass?: string;
18
17
  containerClass?: string;
19
18
  onButtonViewAll?: () => void;
20
- children: Snippet;
19
+ positionButtonPreviousAndNext?: "center" | "top_right";
21
20
  }
22
21
  export interface CarouselBadgeProps {
23
22
  items: ItemCarousel[];
@@ -6,7 +6,7 @@
6
6
  import LanguageSwitcher from "../../../ui/language-switcher/language-switcher.svelte";
7
7
  import { t } from "../../../../i18n";
8
8
  import { Login03Icon, User03Icon } from "@hugeicons/core-free-icons";
9
- import { useIsMobile } from "../../../../hooks/responsive.svelte";
9
+ import { useDevice } from "../../../../hooks/responsive.svelte";
10
10
  import NavMenuDrawer from "./mobile/nav-menu-drawer.svelte";
11
11
  import MenuLinks from "./menu-links.svelte";
12
12
 
@@ -25,7 +25,7 @@
25
25
  onclickButtonRegister,
26
26
  }: NavMenuProps = $props();
27
27
 
28
- const responsive = useIsMobile();
28
+ const responsive = useDevice();
29
29
  </script>
30
30
 
31
31
  {#snippet actionButtons()}
@@ -36,7 +36,7 @@
36
36
  {#if isLanguageSwitcher}
37
37
  <LanguageSwitcher />
38
38
  {/if}
39
- {#each (actions ?? []) as item (item.label)}
39
+ {#each actions ?? [] as item (item.label)}
40
40
  <Button
41
41
  variant={item.type === "LINK" ? "ghost" : "outline"}
42
42
  class={item.className}
@@ -1,38 +1,63 @@
1
1
  <script lang="ts">
2
2
  import * as Carousel from "../../ui/carousel/index.js";
3
3
  import type { Snippet } from "svelte";
4
+ import type { CarouselPlugins } from "../../ui/carousel/context.js";
4
5
 
5
6
  type Props = {
6
- isBorderBottom?: boolean;
7
- isButtonPreviousAndNext?: boolean;
7
+ positionButtonPreviousAndNext?: "center" | "top_right";
8
8
  buttonPreviousAndNextClass?: string;
9
+ isButtonPreviousAndNext?: boolean;
10
+ isBorderBottom?: boolean;
9
11
  containerClass?: string;
12
+ plugins?: CarouselPlugins;
10
13
  children: Snippet;
11
14
  };
12
15
 
13
16
  const {
14
- isBorderBottom = true,
17
+ positionButtonPreviousAndNext = "center",
15
18
  isButtonPreviousAndNext = true,
16
19
  buttonPreviousAndNextClass = "",
20
+ isBorderBottom = true,
17
21
  containerClass = "",
22
+ plugins = [],
18
23
  children,
19
24
  }: Props = $props();
25
+
26
+ const styleTopCenter = "bg-blue-700! text-white! cursor-pointer!";
27
+ const styleCenter =
28
+ "absolute top-1/2 -translate-y-1/2 mt-0.5 mr-0 z-50 bg-blue-700! text-white! cursor-pointer!";
20
29
  </script>
21
30
 
22
31
  <div
23
32
  class={`relative py-3 px-2 ${isBorderBottom ? "border-b" : ""} ${containerClass}`}
24
33
  >
25
- <Carousel.Root>
26
- <Carousel.Content class="ml-10">
34
+ <Carousel.Root {plugins}>
35
+ <Carousel.Content
36
+ class={positionButtonPreviousAndNext == "top_right" ? "" : "ml-0"}
37
+ >
27
38
  {@render children()}
28
39
  </Carousel.Content>
29
- {#if isButtonPreviousAndNext}
30
- <Carousel.Previous
31
- class={`absolute top-1/2 -translate-y-1/2 left-1 mt-0.5 ${buttonPreviousAndNextClass}`}
32
- />
33
- <Carousel.Next
34
- class={`absolute top-1/2 -translate-y-1/2 right-1 mt-0.5 ${buttonPreviousAndNextClass}`}
35
- />
40
+ {#if positionButtonPreviousAndNext == "center"}
41
+ {#if isButtonPreviousAndNext}
42
+ <Carousel.Previous
43
+ class={`-left-8 ${styleCenter} ${buttonPreviousAndNextClass}`}
44
+ />
45
+ <Carousel.Next
46
+ class={`-right-8 ${styleCenter} ${buttonPreviousAndNextClass}`}
47
+ />
48
+ {/if}
49
+ {/if}
50
+ {#if positionButtonPreviousAndNext == "top_right"}
51
+ {#if isButtonPreviousAndNext}
52
+ <div class="absolute -top-6 right-10 md:right-10 lg:right-12 z-20">
53
+ <Carousel.Previous
54
+ class={`${styleTopCenter} ${buttonPreviousAndNextClass}`}
55
+ />
56
+ <Carousel.Next
57
+ class={`${styleTopCenter} ${buttonPreviousAndNextClass}`}
58
+ />
59
+ </div>
60
+ {/if}
36
61
  {/if}
37
62
  </Carousel.Root>
38
63
  </div>