negodesign 0.0.29 → 0.0.31

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 (131) hide show
  1. package/dist/assets/lonely-404.mp4 +0 -0
  2. package/dist/components/core/banner/AdCardBanner.svelte.d.ts +4 -0
  3. package/dist/components/core/banner/CtaCardBanner.svelte +118 -0
  4. package/dist/components/core/banner/CtaCardBanner.svelte.d.ts +4 -0
  5. package/dist/components/core/banner/{BannerNotification.svelte → NotificationBanner.svelte} +4 -3
  6. package/dist/components/core/banner/NotificationBanner.svelte.d.ts +4 -0
  7. package/dist/components/core/banner/types.d.ts +38 -7
  8. package/dist/components/core/card/highlight/01/CardHighlight01.svelte +15 -26
  9. package/dist/components/core/card/highlight/02/CardHighlight02.svelte +13 -24
  10. package/dist/components/core/card/media/01/CardMedia01.svelte +2 -12
  11. package/dist/components/core/card/media/02/CardMedia02.svelte +2 -12
  12. package/dist/components/core/card/promotion/CardPromotion.svelte +5 -1
  13. package/dist/components/core/card/shared/CardButton.svelte +9 -3
  14. package/dist/components/core/card/shared/CardButton.svelte.d.ts +1 -1
  15. package/dist/components/core/card/shared/CardLogo.svelte +19 -0
  16. package/dist/components/core/card/shared/CardLogo.svelte.d.ts +7 -0
  17. package/dist/components/core/card/shared/CardThumbnailVideo.svelte +3 -0
  18. package/dist/components/core/card/types.d.ts +97 -0
  19. package/dist/components/core/carousel/grid/media/types.d.ts +11 -0
  20. package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +2 -1
  21. package/dist/components/core/carousel/grid/profile/types.d.ts +11 -0
  22. package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +3 -5
  23. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte +4 -9
  24. package/dist/components/core/carousel/grid/shared/ui/CarouselGridSlot.svelte.d.ts +1 -0
  25. package/dist/components/core/carousel/highlights/types.d.ts +11 -0
  26. package/dist/components/core/carousel/promotion/types.d.ts +10 -0
  27. package/dist/components/core/carousel/types.d.ts +56 -0
  28. package/dist/components/core/datatable/data/data-table.d.ts +25 -0
  29. package/dist/components/core/footer/types.d.ts +51 -0
  30. package/dist/components/core/footer/types.js +1 -0
  31. package/dist/components/core/footer/ui/01/Footer01.svelte +76 -0
  32. package/dist/components/core/footer/ui/01/Footer01.svelte.d.ts +4 -0
  33. package/dist/components/core/footer/ui/02/Footer02.svelte +53 -0
  34. package/dist/components/core/footer/ui/02/Footer02.svelte.d.ts +4 -0
  35. package/dist/components/core/footer/ui/Footer.svelte +18 -0
  36. package/dist/components/core/footer/ui/Footer.svelte.d.ts +7 -0
  37. package/dist/components/core/footer/ui/shared/FooterColumn.svelte +18 -0
  38. package/dist/components/core/footer/ui/shared/FooterColumn.svelte.d.ts +4 -0
  39. package/dist/components/core/footer/ui/shared/FooterNewsLetter.svelte +57 -0
  40. package/dist/components/core/footer/ui/shared/FooterNewsLetter.svelte.d.ts +9 -0
  41. package/dist/components/core/footer/ui/shared/FooterSocialIcon.svelte +62 -0
  42. package/dist/components/core/footer/ui/shared/FooterSocialIcon.svelte.d.ts +4 -0
  43. package/dist/components/core/nav/data/nav-menu-sidebar.d.ts +29 -0
  44. package/dist/components/core/nav/data/nav-menu.d.ts +43 -0
  45. package/dist/components/core/nav/data/nav-user-sidebar.d.ts +14 -0
  46. package/dist/components/core/nav/ui/menu-links.svelte +1 -1
  47. package/dist/components/core/panel/CarouselSlot.svelte +2 -2
  48. package/dist/components/core/panel/LeftHero.svelte +2 -2
  49. package/dist/components/core/panel/type.d.ts +11 -0
  50. package/dist/components/core/search/types.d.ts +17 -5
  51. package/dist/components/core/sidebar/types.d.ts +19 -0
  52. package/dist/components/core/sidebar/types.js +1 -0
  53. package/dist/components/core/tabs/data/TabModel.d.ts +11 -0
  54. package/dist/components/core/tabs/types.d.ts +11 -0
  55. package/dist/components/core/tabs/types.js +1 -0
  56. package/dist/components/pages/admin/01/ui/types.d.ts +21 -0
  57. package/dist/components/pages/admin/01/ui/types.js +1 -0
  58. package/dist/components/pages/product-details/01/ProductDetails01.svelte +71 -0
  59. package/dist/components/pages/product-details/01/ProductDetails01.svelte.d.ts +11 -0
  60. package/dist/components/pages/product-details/02/ProductDetails02.svelte +88 -0
  61. package/dist/components/pages/product-details/02/ProductDetails02.svelte.d.ts +11 -0
  62. package/dist/components/pages/product-details/03/ProductDetails03.svelte +77 -0
  63. package/dist/components/pages/product-details/03/ProductDetails03.svelte.d.ts +11 -0
  64. package/dist/components/pages/product-details/04/ProductDetails04.svelte +65 -0
  65. package/dist/components/pages/product-details/04/ProductDetails04.svelte.d.ts +11 -0
  66. package/dist/components/pages/product-details/ProductDetails.svelte +38 -0
  67. package/dist/components/pages/product-details/ProductDetails.svelte.d.ts +4 -0
  68. package/dist/components/pages/product-details/shared/ProductDetailActions.svelte +82 -0
  69. package/dist/components/pages/product-details/shared/ProductDetailActions.svelte.d.ts +13 -0
  70. package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte +46 -0
  71. package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte.d.ts +8 -0
  72. package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte +38 -0
  73. package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte.d.ts +8 -0
  74. package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte +40 -0
  75. package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte.d.ts +7 -0
  76. package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte +72 -0
  77. package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte.d.ts +9 -0
  78. package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte +41 -0
  79. package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte.d.ts +9 -0
  80. package/dist/components/pages/product-details/shared/ProductDetailImage.svelte +70 -0
  81. package/dist/components/pages/product-details/shared/ProductDetailImage.svelte.d.ts +9 -0
  82. package/dist/components/pages/product-details/shared/ProductDetailMap.svelte +57 -0
  83. package/dist/components/pages/product-details/shared/ProductDetailMap.svelte.d.ts +9 -0
  84. package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte +44 -0
  85. package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte.d.ts +9 -0
  86. package/dist/components/pages/product-details/shared/ProductDetailRating.svelte +42 -0
  87. package/dist/components/pages/product-details/shared/ProductDetailRating.svelte.d.ts +8 -0
  88. package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte +70 -0
  89. package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte.d.ts +8 -0
  90. package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte +96 -0
  91. package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte.d.ts +12 -0
  92. package/dist/components/pages/product-details/shared/ProductDetailTags.svelte +35 -0
  93. package/dist/components/pages/product-details/shared/ProductDetailTags.svelte.d.ts +8 -0
  94. package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte +58 -0
  95. package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte.d.ts +11 -0
  96. package/dist/components/pages/product-details/types.d.ts +170 -0
  97. package/dist/components/pages/product-details/types.js +1 -0
  98. package/dist/components/pages/profile-user/01/ProfileUser01.svelte +113 -0
  99. package/dist/components/pages/profile-user/01/ProfileUser01.svelte.d.ts +27 -0
  100. package/dist/components/pages/profile-user/02/ProfileUser02.svelte +83 -0
  101. package/dist/components/pages/profile-user/02/ProfileUser02.svelte.d.ts +20 -0
  102. package/dist/components/pages/profile-user/03/ProfileUser03.svelte +85 -0
  103. package/dist/components/pages/profile-user/03/ProfileUser03.svelte.d.ts +20 -0
  104. package/dist/components/pages/profile-user/ProfileUser.svelte +53 -0
  105. package/dist/components/pages/profile-user/ProfileUser.svelte.d.ts +4 -0
  106. package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte +84 -0
  107. package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte.d.ts +8 -0
  108. package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte +104 -0
  109. package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte.d.ts +9 -0
  110. package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte +116 -0
  111. package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte.d.ts +7 -0
  112. package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte +176 -0
  113. package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte.d.ts +14 -0
  114. package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte +78 -0
  115. package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte.d.ts +22 -0
  116. package/dist/components/pages/profile-user/types.d.ts +144 -0
  117. package/dist/components/pages/profile-user/types.js +1 -0
  118. package/dist/globals.css +319 -4
  119. package/dist/i18n/langs/en.d.ts +87 -0
  120. package/dist/i18n/langs/en.js +87 -0
  121. package/dist/i18n/langs/pt.d.ts +87 -0
  122. package/dist/i18n/langs/pt.js +93 -6
  123. package/dist/i18n/translations.d.ts +174 -0
  124. package/dist/index.d.ts +194 -27
  125. package/dist/index.js +194 -27
  126. package/dist/styles/globals.css +1 -1
  127. package/dist/types/index.d.ts +196 -5
  128. package/package.json +1 -1
  129. package/dist/components/core/banner/BannerNotification.svelte.d.ts +0 -4
  130. package/dist/components/core/banner/CardAdBanner.svelte.d.ts +0 -4
  131. /package/dist/components/core/banner/{CardAdBanner.svelte → AdCardBanner.svelte} +0 -0
@@ -0,0 +1,72 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Lista vertical de ficheiros/thumbnails do produto com seleção de imagem.
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailFilesVertical images={data.gallery} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import type { ProductDetailsImage } from "../types";
16
+
17
+ let {
18
+ images,
19
+ className,
20
+ isLoading = false,
21
+ }: {
22
+ images: ProductDetailsImage[];
23
+ className?: string;
24
+ isLoading?: boolean;
25
+ } = $props();
26
+
27
+ let activeIndex = $state(0);
28
+ const active = $derived(images[activeIndex]);
29
+ </script>
30
+
31
+ <div class="flex flex-col gap-3 {className}">
32
+ {#if isLoading}
33
+ <div class="flex gap-3">
34
+ <div class="flex shrink-0 flex-col gap-2">
35
+ {#each Array.from({ length: Math.min(images.length || 3, 4) }) as _, i (i)}
36
+ <Skeleton class="h-16 w-16 rounded-md" />
37
+ {/each}
38
+ </div>
39
+ <Skeleton class="aspect-4/3 min-w-0 flex-1 rounded-lg" />
40
+ </div>
41
+ {:else if images.length > 0}
42
+ <div class="flex gap-3">
43
+ <div class="flex shrink-0 flex-col gap-2">
44
+ {#each images as image, i (image.src)}
45
+ <button
46
+ type="button"
47
+ aria-label={image.alt ?? `Imagem ${i + 1}`}
48
+ onclick={() => (activeIndex = i)}
49
+ class="overflow-hidden rounded-md border transition-opacity {i ===
50
+ activeIndex
51
+ ? 'border-foreground opacity-100'
52
+ : 'border-muted opacity-60 hover:opacity-100'}"
53
+ >
54
+ <img
55
+ src={image.src}
56
+ alt={image.alt ?? ""}
57
+ class="h-16 w-16 object-cover"
58
+ />
59
+ </button>
60
+ {/each}
61
+ </div>
62
+
63
+ <div class="min-w-0 flex-1 overflow-hidden rounded-lg border">
64
+ <img
65
+ src={active.src}
66
+ alt={active.alt ?? ""}
67
+ class="aspect-4/3 w-full object-cover"
68
+ />
69
+ </div>
70
+ </div>
71
+ {/if}
72
+ </div>
@@ -0,0 +1,9 @@
1
+ import type { ProductDetailsImage } from "../types";
2
+ type $$ComponentProps = {
3
+ images: ProductDetailsImage[];
4
+ className?: string;
5
+ isLoading?: boolean;
6
+ };
7
+ declare const ProductDetailFilesVertical: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ProductDetailFilesVertical = ReturnType<typeof ProductDetailFilesVertical>;
9
+ export default ProductDetailFilesVertical;
@@ -0,0 +1,41 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Cabeçalho do produto: título e subtítulo.
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailHeader title={data.title} subtitle={data.subtitle} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+
16
+ let {
17
+ title,
18
+ subtitle,
19
+ className,
20
+ isLoading = false,
21
+ }: {
22
+ title: string;
23
+ subtitle?: string;
24
+ className?: string;
25
+ isLoading?: boolean;
26
+ } = $props();
27
+ </script>
28
+
29
+ {#if isLoading}
30
+ <div class="flex flex-col gap-2 {className}">
31
+ <Skeleton class="h-8 w-2/3 rounded-md" />
32
+ <Skeleton class="h-4 w-1/2 rounded-md" />
33
+ </div>
34
+ {:else}
35
+ <header class="flex flex-col gap-2 {className}">
36
+ <h1 class="text-3xl font-bold tracking-tight">{title}</h1>
37
+ {#if subtitle}
38
+ <p class="text-muted-foreground">{subtitle}</p>
39
+ {/if}
40
+ </header>
41
+ {/if}
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ title: string;
3
+ subtitle?: string;
4
+ className?: string;
5
+ isLoading?: boolean;
6
+ };
7
+ declare const ProductDetailHeader: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ProductDetailHeader = ReturnType<typeof ProductDetailHeader>;
9
+ export default ProductDetailHeader;
@@ -0,0 +1,70 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Galeria de imagens do produto com miniatura selecionável.
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailImage images={data.gallery} className="md:col-span-2" />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import type { ProductDetailsImage } from "../types";
16
+
17
+ let {
18
+ images,
19
+ className,
20
+ isLoading = false,
21
+ }: {
22
+ images: ProductDetailsImage[];
23
+ className?: string;
24
+ isLoading?: boolean;
25
+ } = $props();
26
+
27
+ let activeIndex = $state(0);
28
+ const active = $derived(images[activeIndex]);
29
+ </script>
30
+
31
+ <div class="flex flex-col gap-3 {className}">
32
+ {#if isLoading}
33
+ <Skeleton class="aspect-4/3 w-full rounded-lg" />
34
+ <div class="flex gap-2">
35
+ {#each Array.from({ length: Math.min(images.length || 3, 4) }) as _, i (i)}
36
+ <Skeleton class="h-16 w-16 rounded-md md:h-20 md:w-20" />
37
+ {/each}
38
+ </div>
39
+ {:else if active}
40
+ <div class="overflow-hidden rounded-lg border">
41
+ <img
42
+ src={active.src}
43
+ alt={active.alt ?? ""}
44
+ class="aspect-4/3 w-full object-cover"
45
+ />
46
+ </div>
47
+
48
+ {#if images.length > 1}
49
+ <div class="flex gap-2 overflow-x-auto no-scrollbar">
50
+ {#each images as image, i (image.src)}
51
+ <button
52
+ type="button"
53
+ aria-label={image.alt ?? `Imagem ${i + 1}`}
54
+ onclick={() => (activeIndex = i)}
55
+ class="shrink-0 overflow-hidden rounded-md border transition-opacity {i ===
56
+ activeIndex
57
+ ? 'border-foreground opacity-100'
58
+ : 'border-muted opacity-60 hover:opacity-100'}"
59
+ >
60
+ <img
61
+ src={image.src}
62
+ alt={image.alt ?? ""}
63
+ class="h-16 w-16 object-cover md:h-20 md:w-20"
64
+ />
65
+ </button>
66
+ {/each}
67
+ </div>
68
+ {/if}
69
+ {/if}
70
+ </div>
@@ -0,0 +1,9 @@
1
+ import type { ProductDetailsImage } from "../types";
2
+ type $$ComponentProps = {
3
+ images: ProductDetailsImage[];
4
+ className?: string;
5
+ isLoading?: boolean;
6
+ };
7
+ declare const ProductDetailImage: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ProductDetailImage = ReturnType<typeof ProductDetailImage>;
9
+ export default ProductDetailImage;
@@ -0,0 +1,57 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Mapa de localização do produto (iframe embed).
4
+ * Quando `isLoading` é true, exibe um Skeleton.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailMap location={data.location} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import type { ProductDetailsLocation } from "../types";
16
+
17
+ let {
18
+ location,
19
+ className,
20
+ isLoading = false,
21
+ }: {
22
+ location?: ProductDetailsLocation;
23
+ className?: string;
24
+ isLoading?: boolean;
25
+ } = $props();
26
+
27
+ const defaultMapUrl = $derived(
28
+ "https://www.openstreetmap.org/export/embed.html?bbox=13.2292%2C-8.8486%2C13.2462%2C-8.8339&layer=mapnik&marker=-8.8412%2C13.2377",
29
+ );
30
+ </script>
31
+
32
+ {#if isLoading}
33
+ <div class="flex flex-col gap-3 {className}">
34
+ <Skeleton class="h-4 w-40 rounded-md" />
35
+ <Skeleton class="h-72 w-full rounded-lg" />
36
+ </div>
37
+ {:else if location}
38
+ <div class="flex flex-col gap-3 {className}">
39
+ {#if location.label || location.address}
40
+ <div class="flex flex-col gap-1">
41
+ {#if location.label}
42
+ <p class="text-sm font-semibold">{location.label}</p>
43
+ {/if}
44
+ {#if location.address}
45
+ <p class="text-sm text-muted-foreground">{location.address}</p>
46
+ {/if}
47
+ </div>
48
+ {/if}
49
+
50
+ <iframe
51
+ title={location.label ?? "Localização"}
52
+ src={location.mapUrl ?? defaultMapUrl}
53
+ class="h-72 w-full rounded-lg border"
54
+ loading="lazy"
55
+ ></iframe>
56
+ </div>
57
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { ProductDetailsLocation } from "../types";
2
+ type $$ComponentProps = {
3
+ location?: ProductDetailsLocation;
4
+ className?: string;
5
+ isLoading?: boolean;
6
+ };
7
+ declare const ProductDetailMap: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ProductDetailMap = ReturnType<typeof ProductDetailMap>;
9
+ export default ProductDetailMap;
@@ -0,0 +1,44 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Preço do produto (novo e antigo), com moeda opcional.
4
+ * Quando `isLoading` é true, exibe um Skeleton.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailPrice newPrice={1000} oldPrice={2000} currency="Kz" />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import CardStartPrice from "../../../core/card/shared/CardStartPrice.svelte";
15
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
16
+
17
+ let {
18
+ newPrice,
19
+ oldPrice,
20
+ currency,
21
+ isLoading = false,
22
+ }: {
23
+ newPrice?: string | number;
24
+ oldPrice?: string | number;
25
+ currency?: string;
26
+ isLoading?: boolean;
27
+ } = $props();
28
+
29
+ const formatted = $derived(
30
+ newPrice != null
31
+ ? currency
32
+ ? `${newPrice} ${currency}`
33
+ : String(newPrice)
34
+ : undefined,
35
+ );
36
+ </script>
37
+
38
+ {#if isLoading}
39
+ <Skeleton class="h-7 w-28 rounded-md" />
40
+ {:else if formatted || oldPrice}
41
+ <div class="flex items-end gap-3">
42
+ <CardStartPrice newPrice={formatted} oldPrice={oldPrice} />
43
+ </div>
44
+ {/if}
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ newPrice?: string | number;
3
+ oldPrice?: string | number;
4
+ currency?: string;
5
+ isLoading?: boolean;
6
+ };
7
+ declare const ProductDetailPrice: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ProductDetailPrice = ReturnType<typeof ProductDetailPrice>;
9
+ export default ProductDetailPrice;
@@ -0,0 +1,42 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Avaliação (estrelas + contagem) do produto.
4
+ * Quando `isLoading` é true, exibe um Skeleton.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailRating rating={4.5} reviewsCount={120} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import CardStarRating from "../../../core/card/shared/CardStarRating.svelte";
15
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
16
+
17
+ let {
18
+ rating,
19
+ reviewsCount,
20
+ isLoading = false,
21
+ }: {
22
+ rating?: number;
23
+ reviewsCount?: number;
24
+ isLoading?: boolean;
25
+ } = $props();
26
+ </script>
27
+
28
+ {#if isLoading}
29
+ <div class="flex items-center gap-2">
30
+ <Skeleton class="h-6 w-24 rounded-md" />
31
+ <Skeleton class="h-4 w-16 rounded-md" />
32
+ </div>
33
+ {:else if rating}
34
+ <div class="flex items-center gap-2">
35
+ <CardStarRating startNumber={rating} startMax={5} />
36
+ {#if reviewsCount}
37
+ <span class="text-sm text-muted-foreground">
38
+ ({reviewsCount} avaliações)
39
+ </span>
40
+ {/if}
41
+ </div>
42
+ {/if}
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ rating?: number;
3
+ reviewsCount?: number;
4
+ isLoading?: boolean;
5
+ };
6
+ declare const ProductDetailRating: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ProductDetailRating = ReturnType<typeof ProductDetailRating>;
8
+ export default ProductDetailRating;
@@ -0,0 +1,70 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Lista de avaliações de clientes do produto.
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailReviews reviews={data.reviews} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import CardStarRating from "../../../core/card/shared/CardStarRating.svelte";
15
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
16
+ import type { ProductDetailsReview } from "../types";
17
+
18
+ let {
19
+ reviews,
20
+ isLoading = false,
21
+ }: {
22
+ reviews?: ProductDetailsReview[];
23
+ isLoading?: boolean;
24
+ } = $props();
25
+ </script>
26
+
27
+ {#if isLoading}
28
+ <div class="flex flex-col gap-6">
29
+ {#each Array.from({ length: 2 }) as _, i (i)}
30
+ <div class="flex flex-col gap-2 border-b pb-6 last:border-b-0">
31
+ <div class="flex items-center justify-between">
32
+ <div class="flex items-center gap-3">
33
+ <Skeleton class="size-9 rounded-full" />
34
+ <div class="flex flex-col gap-1">
35
+ <Skeleton class="h-4 w-28 rounded-md" />
36
+ <Skeleton class="h-3 w-20 rounded-md" />
37
+ </div>
38
+ </div>
39
+ <Skeleton class="h-5 w-24 rounded-md" />
40
+ </div>
41
+ <Skeleton class="h-4 w-full rounded-md" />
42
+ <Skeleton class="h-4 w-3/4 rounded-md" />
43
+ </div>
44
+ {/each}
45
+ </div>
46
+ {:else if reviews?.length}
47
+ <div class="flex flex-col gap-6">
48
+ {#each reviews as review (review.id)}
49
+ <article class="flex flex-col gap-2 border-b pb-6 last:border-b-0">
50
+ <div class="flex items-center justify-between">
51
+ <div class="flex items-center gap-3">
52
+ <div class="flex size-9 items-center justify-center rounded-full bg-muted text-sm font-semibold">
53
+ {review.author.charAt(0)}
54
+ </div>
55
+ <div>
56
+ <p class="text-sm font-medium">{review.author}</p>
57
+ {#if review.date}
58
+ <p class="text-xs text-muted-foreground">{review.date}</p>
59
+ {/if}
60
+ </div>
61
+ </div>
62
+ <CardStarRating startNumber={review.rating} startMax={5} />
63
+ </div>
64
+ <p class="text-sm leading-6 text-foreground/85">{review.comment}</p>
65
+ </article>
66
+ {/each}
67
+ </div>
68
+ {:else}
69
+ <p class="text-muted-foreground">Sem avaliações disponíveis.</p>
70
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { ProductDetailsReview } from "../types";
2
+ type $$ComponentProps = {
3
+ reviews?: ProductDetailsReview[];
4
+ isLoading?: boolean;
5
+ };
6
+ declare const ProductDetailReviews: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ProductDetailReviews = ReturnType<typeof ProductDetailReviews>;
8
+ export default ProductDetailReviews;
@@ -0,0 +1,96 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Abas de conteúdo do produto (descrição, avaliações, etc).
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailTabs tabs={data.tabs} {data}>
9
+ * {#snippet tabContent(tab)}
10
+ * ...
11
+ * {/snippet}
12
+ * </ProductDetailTabs>
13
+ * ```
14
+ */
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ import type { Snippet } from "svelte";
19
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
20
+ import type { ProductDetailsData, ProductDetailsTab } from "../types";
21
+ import ProductDetailDescription from "./ProductDetailDescription.svelte";
22
+ import ProductDetailFeatures from "./ProductDetailFeatures.svelte";
23
+ import ProductDetailReviews from "./ProductDetailReviews.svelte";
24
+
25
+ let {
26
+ data,
27
+ tabs,
28
+ isLoading = false,
29
+ children,
30
+ }: {
31
+ data: ProductDetailsData;
32
+ tabs?: ProductDetailsTab[];
33
+ isLoading?: boolean;
34
+ /** Snippet opcional para renderizar o conteúdo de cada aba. */
35
+ children?: Snippet<[ProductDetailsTab]>;
36
+ } = $props();
37
+
38
+ const defaultTabs = $derived(
39
+ tabs?.length
40
+ ? tabs
41
+ : [
42
+ { id: "description", label: "Descrição" },
43
+ { id: "reviews", label: "Avaliações" },
44
+ ],
45
+ );
46
+
47
+ let activeTab = $state<string | undefined>(undefined);
48
+ const currentTab = $derived(activeTab ?? defaultTabs[0]?.id ?? "description");
49
+ </script>
50
+
51
+ <div>
52
+ <div class="mb-6 flex gap-1 border-b">
53
+ {#if isLoading}
54
+ {#each defaultTabs as tab (tab.id)}
55
+ <Skeleton class="h-9 w-24 rounded-t-md" />
56
+ {/each}
57
+ {:else}
58
+ {#each defaultTabs as tab (tab.id)}
59
+ <button
60
+ type="button"
61
+ onclick={() => (activeTab = tab.id)}
62
+ class="px-4 py-2 text-sm font-medium transition-colors {currentTab ===
63
+ tab.id
64
+ ? 'border-b-2 border-foreground text-foreground'
65
+ : 'border-b-2 border-transparent text-muted-foreground hover:text-foreground'}"
66
+ >
67
+ {tab.label}
68
+ </button>
69
+ {/each}
70
+ {/if}
71
+ </div>
72
+
73
+ {#if isLoading}
74
+ <div class="flex max-w-3xl flex-col gap-2">
75
+ {#each Array.from({ length: 4 }) as _, i (i)}
76
+ <Skeleton class="h-4 w-full rounded-md" />
77
+ {/each}
78
+ </div>
79
+ {:else if children}
80
+ {@render children(defaultTabs.find((tab) => tab.id === currentTab) ?? defaultTabs[0])}
81
+ {:else if currentTab === "description"}
82
+ <div class="flex max-w-3xl flex-col gap-5">
83
+ <ProductDetailDescription
84
+ description={data.description}
85
+ longDescription={data.longDescription}
86
+ />
87
+ {#if data.features}
88
+ <ProductDetailFeatures features={data.features} />
89
+ {/if}
90
+ </div>
91
+ {:else if currentTab === "reviews"}
92
+ <div class="max-w-3xl">
93
+ <ProductDetailReviews reviews={data.reviews} />
94
+ </div>
95
+ {/if}
96
+ </div>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ProductDetailsData, ProductDetailsTab } from "../types";
3
+ type $$ComponentProps = {
4
+ data: ProductDetailsData;
5
+ tabs?: ProductDetailsTab[];
6
+ isLoading?: boolean;
7
+ /** Snippet opcional para renderizar o conteúdo de cada aba. */
8
+ children?: Snippet<[ProductDetailsTab]>;
9
+ };
10
+ declare const ProductDetailTabs: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type ProductDetailTabs = ReturnType<typeof ProductDetailTabs>;
12
+ export default ProductDetailTabs;
@@ -0,0 +1,35 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Lista de tags do produto (ex: localização, categoria).
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailTags tags={[{ text: "Luanda" }, { text: "Kilamba" }]} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import CardTags from "../../../core/card/shared/CardTags.svelte";
15
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
16
+ import type { ProductDetailsTag } from "../types";
17
+
18
+ let {
19
+ tags,
20
+ isLoading = false,
21
+ }: {
22
+ tags?: ProductDetailsTag[];
23
+ isLoading?: boolean;
24
+ } = $props();
25
+ </script>
26
+
27
+ {#if isLoading}
28
+ <div class="flex flex-wrap gap-2">
29
+ {#each Array.from({ length: 3 }) as _, i (i)}
30
+ <Skeleton class="h-5 w-20 rounded-full" />
31
+ {/each}
32
+ </div>
33
+ {:else if tags?.length}
34
+ <CardTags tags={tags} isWrap />
35
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { ProductDetailsTag } from "../types";
2
+ type $$ComponentProps = {
3
+ tags?: ProductDetailsTag[];
4
+ isLoading?: boolean;
5
+ };
6
+ declare const ProductDetailTags: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ProductDetailTags = ReturnType<typeof ProductDetailTags>;
8
+ export default ProductDetailTags;