negodesign 0.0.30 → 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 (94) hide show
  1. package/dist/components/core/banner/CtaCardBanner.svelte +1 -1
  2. package/dist/components/core/banner/types.d.ts +16 -6
  3. package/dist/components/core/card/types.d.ts +94 -0
  4. package/dist/components/core/carousel/grid/media/types.d.ts +10 -0
  5. package/dist/components/core/carousel/grid/profile/types.d.ts +10 -0
  6. package/dist/components/core/carousel/highlights/types.d.ts +11 -0
  7. package/dist/components/core/carousel/promotion/types.d.ts +10 -0
  8. package/dist/components/core/carousel/types.d.ts +56 -0
  9. package/dist/components/core/datatable/data/data-table.d.ts +25 -0
  10. package/dist/components/core/footer/types.d.ts +28 -0
  11. package/dist/components/core/nav/data/nav-menu-sidebar.d.ts +29 -0
  12. package/dist/components/core/nav/data/nav-menu.d.ts +43 -0
  13. package/dist/components/core/nav/data/nav-user-sidebar.d.ts +14 -0
  14. package/dist/components/core/nav/ui/menu-links.svelte +1 -1
  15. package/dist/components/core/panel/CarouselSlot.svelte +1 -1
  16. package/dist/components/core/panel/type.d.ts +11 -0
  17. package/dist/components/core/search/types.d.ts +17 -5
  18. package/dist/components/core/sidebar/types.d.ts +19 -0
  19. package/dist/components/core/sidebar/types.js +1 -0
  20. package/dist/components/core/tabs/data/TabModel.d.ts +11 -0
  21. package/dist/components/core/tabs/types.d.ts +11 -0
  22. package/dist/components/core/tabs/types.js +1 -0
  23. package/dist/components/pages/admin/01/ui/types.d.ts +21 -0
  24. package/dist/components/pages/admin/01/ui/types.js +1 -0
  25. package/dist/components/pages/product-details/01/ProductDetails01.svelte +71 -0
  26. package/dist/components/pages/product-details/01/ProductDetails01.svelte.d.ts +11 -0
  27. package/dist/components/pages/product-details/02/ProductDetails02.svelte +88 -0
  28. package/dist/components/pages/product-details/02/ProductDetails02.svelte.d.ts +11 -0
  29. package/dist/components/pages/product-details/03/ProductDetails03.svelte +77 -0
  30. package/dist/components/pages/product-details/03/ProductDetails03.svelte.d.ts +11 -0
  31. package/dist/components/pages/product-details/04/ProductDetails04.svelte +65 -0
  32. package/dist/components/pages/product-details/04/ProductDetails04.svelte.d.ts +11 -0
  33. package/dist/components/pages/product-details/ProductDetails.svelte +38 -0
  34. package/dist/components/pages/product-details/ProductDetails.svelte.d.ts +4 -0
  35. package/dist/components/pages/product-details/shared/ProductDetailActions.svelte +82 -0
  36. package/dist/components/pages/product-details/shared/ProductDetailActions.svelte.d.ts +13 -0
  37. package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte +46 -0
  38. package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte.d.ts +8 -0
  39. package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte +38 -0
  40. package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte.d.ts +8 -0
  41. package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte +40 -0
  42. package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte.d.ts +7 -0
  43. package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte +72 -0
  44. package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte.d.ts +9 -0
  45. package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte +41 -0
  46. package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte.d.ts +9 -0
  47. package/dist/components/pages/product-details/shared/ProductDetailImage.svelte +70 -0
  48. package/dist/components/pages/product-details/shared/ProductDetailImage.svelte.d.ts +9 -0
  49. package/dist/components/pages/product-details/shared/ProductDetailMap.svelte +57 -0
  50. package/dist/components/pages/product-details/shared/ProductDetailMap.svelte.d.ts +9 -0
  51. package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte +44 -0
  52. package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte.d.ts +9 -0
  53. package/dist/components/pages/product-details/shared/ProductDetailRating.svelte +42 -0
  54. package/dist/components/pages/product-details/shared/ProductDetailRating.svelte.d.ts +8 -0
  55. package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte +70 -0
  56. package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte.d.ts +8 -0
  57. package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte +96 -0
  58. package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte.d.ts +12 -0
  59. package/dist/components/pages/product-details/shared/ProductDetailTags.svelte +35 -0
  60. package/dist/components/pages/product-details/shared/ProductDetailTags.svelte.d.ts +8 -0
  61. package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte +58 -0
  62. package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte.d.ts +11 -0
  63. package/dist/components/pages/product-details/types.d.ts +170 -0
  64. package/dist/components/pages/product-details/types.js +1 -0
  65. package/dist/components/pages/profile-user/01/ProfileUser01.svelte +113 -0
  66. package/dist/components/pages/profile-user/01/ProfileUser01.svelte.d.ts +27 -0
  67. package/dist/components/pages/profile-user/02/ProfileUser02.svelte +83 -0
  68. package/dist/components/pages/profile-user/02/ProfileUser02.svelte.d.ts +20 -0
  69. package/dist/components/pages/profile-user/03/ProfileUser03.svelte +85 -0
  70. package/dist/components/pages/profile-user/03/ProfileUser03.svelte.d.ts +20 -0
  71. package/dist/components/pages/profile-user/ProfileUser.svelte +53 -0
  72. package/dist/components/pages/profile-user/ProfileUser.svelte.d.ts +4 -0
  73. package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte +84 -0
  74. package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte.d.ts +8 -0
  75. package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte +104 -0
  76. package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte.d.ts +9 -0
  77. package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte +116 -0
  78. package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte.d.ts +7 -0
  79. package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte +176 -0
  80. package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte.d.ts +14 -0
  81. package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte +78 -0
  82. package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte.d.ts +22 -0
  83. package/dist/components/pages/profile-user/types.d.ts +144 -0
  84. package/dist/components/pages/profile-user/types.js +1 -0
  85. package/dist/globals.css +210 -3
  86. package/dist/i18n/langs/en.d.ts +83 -0
  87. package/dist/i18n/langs/en.js +83 -0
  88. package/dist/i18n/langs/pt.d.ts +83 -0
  89. package/dist/i18n/langs/pt.js +83 -0
  90. package/dist/i18n/translations.d.ts +166 -0
  91. package/dist/index.d.ts +190 -25
  92. package/dist/index.js +190 -25
  93. package/dist/types/index.d.ts +196 -5
  94. package/package.json +1 -1
@@ -0,0 +1,82 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Ações do produto: botões de compra, favorito e carrinho.
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailActions id={data.id} {onBuy} {onFavorite} {onCart} buttonBuyText="Comprar" />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import { Button } from "../../../ui/button/index.js";
15
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
16
+ import {
17
+ HeartAddIcon,
18
+ HeartRemoveIcon,
19
+ ShoppingCartAdd01Icon,
20
+ } from "@hugeicons/core-free-icons";
21
+ import { HugeiconsIcon } from "@hugeicons/svelte";
22
+
23
+ let {
24
+ id,
25
+ buttonBuyText = "Comprar",
26
+ isFavorite = $bindable(false),
27
+ isCart = $bindable(false),
28
+ isLoading = false,
29
+ onBuy,
30
+ onFavorite,
31
+ onCart,
32
+ }: {
33
+ id: string | number;
34
+ buttonBuyText?: string;
35
+ isFavorite?: boolean;
36
+ isCart?: boolean;
37
+ isLoading?: boolean;
38
+ onBuy?: (id: string | number) => void;
39
+ onFavorite?: (id: string | number) => void;
40
+ onCart?: (id: string | number) => void;
41
+ } = $props();
42
+ </script>
43
+
44
+ {#if isLoading}
45
+ <div class="flex items-center gap-2">
46
+ <Skeleton class="h-9 min-w-40 flex-1 rounded-md" />
47
+ <Skeleton class="size-9 rounded-full" />
48
+ <Skeleton class="size-9 rounded-full" />
49
+ </div>
50
+ {:else}
51
+ <div class="flex flex-wrap items-center gap-2">
52
+ <Button class="flex-1 min-w-40 bg-gradient" onclick={() => onBuy?.(id)}>
53
+ {buttonBuyText}
54
+ </Button>
55
+
56
+ <Button
57
+ size="icon"
58
+ class="rounded-full"
59
+ variant="outline"
60
+ aria-label={isFavorite ? "Remover dos favoritos" : "Adicionar aos favoritos"}
61
+ onclick={() => {
62
+ isFavorite = !isFavorite;
63
+ onFavorite?.(id);
64
+ }}
65
+ >
66
+ <HugeiconsIcon icon={isFavorite ? HeartRemoveIcon : HeartAddIcon} />
67
+ </Button>
68
+
69
+ <Button
70
+ size="icon"
71
+ class="rounded-full"
72
+ variant="outline"
73
+ aria-label="Adicionar ao carrinho"
74
+ onclick={() => {
75
+ isCart = !isCart;
76
+ onCart?.(id);
77
+ }}
78
+ >
79
+ <HugeiconsIcon icon={ShoppingCartAdd01Icon} />
80
+ </Button>
81
+ </div>
82
+ {/if}
@@ -0,0 +1,13 @@
1
+ type $$ComponentProps = {
2
+ id: string | number;
3
+ buttonBuyText?: string;
4
+ isFavorite?: boolean;
5
+ isCart?: boolean;
6
+ isLoading?: boolean;
7
+ onBuy?: (id: string | number) => void;
8
+ onFavorite?: (id: string | number) => void;
9
+ onCart?: (id: string | number) => void;
10
+ };
11
+ declare const ProductDetailActions: import("svelte").Component<$$ComponentProps, {}, "isFavorite" | "isCart">;
12
+ type ProductDetailActions = ReturnType<typeof ProductDetailActions>;
13
+ export default ProductDetailActions;
@@ -0,0 +1,46 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Breadcrumb de navegação para a página de detalhes do produto.
4
+ * Quando `isLoading` é true, exibe um Skeleton.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailBreadcrumb items={[{ label: "Home", href: "/" }, { label: "Hotel Baía Azul" }]} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+ import type { ProductDetailsBreadcrumbItem } from "../types";
16
+
17
+ let {
18
+ items,
19
+ isLoading = false,
20
+ }: {
21
+ items: ProductDetailsBreadcrumbItem[];
22
+ isLoading?: boolean;
23
+ } = $props();
24
+ </script>
25
+
26
+ {#if isLoading}
27
+ <Skeleton class="h-4 w-48 rounded-md" />
28
+ {:else}
29
+ <nav
30
+ aria-label="Breadcrumb"
31
+ class="flex items-center gap-1.5 text-sm text-muted-foreground"
32
+ >
33
+ {#each items as item, i (item.label)}
34
+ {#if item.href}
35
+ <a href={item.href} class="hover:text-foreground transition-colors"
36
+ >{item.label}</a
37
+ >
38
+ {:else}
39
+ <span class="text-foreground font-medium">{item.label}</span>
40
+ {/if}
41
+ {#if i < items.length - 1}
42
+ <span class="select-none">/</span>
43
+ {/if}
44
+ {/each}
45
+ </nav>
46
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { ProductDetailsBreadcrumbItem } from "../types";
2
+ type $$ComponentProps = {
3
+ items: ProductDetailsBreadcrumbItem[];
4
+ isLoading?: boolean;
5
+ };
6
+ declare const ProductDetailBreadcrumb: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ProductDetailBreadcrumb = ReturnType<typeof ProductDetailBreadcrumb>;
8
+ export default ProductDetailBreadcrumb;
@@ -0,0 +1,38 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Descrição do produto (curta e longa opcional).
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailDescription description={data.description} longDescription={data.longDescription} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+
16
+ let {
17
+ description,
18
+ longDescription,
19
+ isLoading = false,
20
+ }: {
21
+ description: string;
22
+ longDescription?: string;
23
+ isLoading?: boolean;
24
+ } = $props();
25
+ </script>
26
+
27
+ {#if isLoading}
28
+ <div class="flex flex-col gap-2">
29
+ {#each Array.from({ length: 3 }) as _, i (i)}
30
+ <Skeleton class="h-4 w-full rounded-md" />
31
+ {/each}
32
+ </div>
33
+ {:else}
34
+ <p class="leading-7 text-foreground/90">{description}</p>
35
+ {#if longDescription}
36
+ <p class="leading-7 text-foreground/75">{longDescription}</p>
37
+ {/if}
38
+ {/if}
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ description: string;
3
+ longDescription?: string;
4
+ isLoading?: boolean;
5
+ };
6
+ declare const ProductDetailDescription: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ProductDetailDescription = ReturnType<typeof ProductDetailDescription>;
8
+ export default ProductDetailDescription;
@@ -0,0 +1,40 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Bloco de características do produto (lista de bullets).
4
+ * Quando `isLoading` é true, exibe Skeletons.
5
+ * @component
6
+ * @example
7
+ * ```svelte
8
+ * <ProductDetailFeatures features={["Wi-Fi gratuito", "Pequeno-almoço incluído"]} />
9
+ * ```
10
+ */
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import Skeleton from "../../../ui/skeleton/skeleton.svelte";
15
+
16
+ let {
17
+ features,
18
+ isLoading = false,
19
+ }: {
20
+ features: string[];
21
+ isLoading?: boolean;
22
+ } = $props();
23
+ </script>
24
+
25
+ {#if isLoading}
26
+ <div class="flex flex-col gap-2">
27
+ {#each Array.from({ length: 3 }) as _, i (i)}
28
+ <Skeleton class="h-4 w-2/3 rounded-md" />
29
+ {/each}
30
+ </div>
31
+ {:else}
32
+ <ul class="flex flex-col gap-2">
33
+ {#each features as feature, i (feature)}
34
+ <li class="flex items-start gap-2 text-sm text-foreground/90">
35
+ <span aria-hidden="true" class="mt-1.5 size-1.5 shrink-0 rounded-full bg-primary"></span>
36
+ <span>{feature}</span>
37
+ </li>
38
+ {/each}
39
+ </ul>
40
+ {/if}
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ features: string[];
3
+ isLoading?: boolean;
4
+ };
5
+ declare const ProductDetailFeatures: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type ProductDetailFeatures = ReturnType<typeof ProductDetailFeatures>;
7
+ export default ProductDetailFeatures;
@@ -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;