negodesign 0.0.30 → 0.0.32
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/NegoDesign.svelte +2 -10
- package/dist/components/NegoDesign.svelte.d.ts +2 -7
- package/dist/components/core/banner/CtaCardBanner.svelte +1 -1
- package/dist/components/core/banner/types.d.ts +16 -6
- package/dist/components/core/card/types.d.ts +94 -0
- package/dist/components/core/carousel/grid/media/types.d.ts +11 -1
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +2 -2
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte.d.ts +2 -2
- package/dist/components/core/carousel/grid/profile/types.d.ts +11 -1
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +2 -2
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte.d.ts +2 -2
- package/dist/components/core/carousel/highlights/types.d.ts +11 -0
- package/dist/components/core/carousel/promotion/types.d.ts +10 -0
- package/dist/components/core/carousel/types.d.ts +56 -0
- package/dist/components/core/datatable/data/data-table.d.ts +25 -0
- package/dist/components/core/footer/types.d.ts +28 -0
- package/dist/components/core/nav/data/nav-menu-sidebar.d.ts +29 -0
- package/dist/components/core/nav/data/nav-menu.d.ts +43 -0
- package/dist/components/core/nav/data/nav-user-sidebar.d.ts +14 -0
- package/dist/components/core/nav/ui/menu-links.svelte +1 -1
- package/dist/components/core/panel/CarouselSlot.svelte +1 -1
- package/dist/components/core/panel/type.d.ts +11 -0
- package/dist/components/core/search/types.d.ts +17 -5
- package/dist/components/core/sidebar/types.d.ts +19 -0
- package/dist/components/core/sidebar/types.js +1 -0
- package/dist/components/core/tabs/data/TabModel.d.ts +11 -0
- package/dist/components/core/tabs/types.d.ts +11 -0
- package/dist/components/core/tabs/types.js +1 -0
- package/dist/components/pages/admin/01/ui/types.d.ts +21 -0
- package/dist/components/pages/admin/01/ui/types.js +1 -0
- package/dist/components/pages/product-details/01/ProductDetails01.svelte +71 -0
- package/dist/components/pages/product-details/01/ProductDetails01.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/02/ProductDetails02.svelte +88 -0
- package/dist/components/pages/product-details/02/ProductDetails02.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/03/ProductDetails03.svelte +77 -0
- package/dist/components/pages/product-details/03/ProductDetails03.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/04/ProductDetails04.svelte +65 -0
- package/dist/components/pages/product-details/04/ProductDetails04.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/ProductDetails.svelte +38 -0
- package/dist/components/pages/product-details/ProductDetails.svelte.d.ts +4 -0
- package/dist/components/pages/product-details/shared/ProductDetailActions.svelte +82 -0
- package/dist/components/pages/product-details/shared/ProductDetailActions.svelte.d.ts +13 -0
- package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte +46 -0
- package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte +38 -0
- package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte +40 -0
- package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte.d.ts +7 -0
- package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte +72 -0
- package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte +41 -0
- package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailImage.svelte +70 -0
- package/dist/components/pages/product-details/shared/ProductDetailImage.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailMap.svelte +57 -0
- package/dist/components/pages/product-details/shared/ProductDetailMap.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte +44 -0
- package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailRating.svelte +42 -0
- package/dist/components/pages/product-details/shared/ProductDetailRating.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte +70 -0
- package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte +96 -0
- package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte.d.ts +12 -0
- package/dist/components/pages/product-details/shared/ProductDetailTags.svelte +35 -0
- package/dist/components/pages/product-details/shared/ProductDetailTags.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte +58 -0
- package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/types.d.ts +170 -0
- package/dist/components/pages/product-details/types.js +1 -0
- package/dist/components/pages/profile-user/01/ProfileUser01.svelte +113 -0
- package/dist/components/pages/profile-user/01/ProfileUser01.svelte.d.ts +27 -0
- package/dist/components/pages/profile-user/02/ProfileUser02.svelte +83 -0
- package/dist/components/pages/profile-user/02/ProfileUser02.svelte.d.ts +20 -0
- package/dist/components/pages/profile-user/03/ProfileUser03.svelte +85 -0
- package/dist/components/pages/profile-user/03/ProfileUser03.svelte.d.ts +20 -0
- package/dist/components/pages/profile-user/ProfileUser.svelte +53 -0
- package/dist/components/pages/profile-user/ProfileUser.svelte.d.ts +4 -0
- package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte +84 -0
- package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte.d.ts +8 -0
- package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte +104 -0
- package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte.d.ts +9 -0
- package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte +116 -0
- package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte.d.ts +7 -0
- package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte +176 -0
- package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte.d.ts +14 -0
- package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte +78 -0
- package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte.d.ts +22 -0
- package/dist/components/pages/profile-user/types.d.ts +144 -0
- package/dist/components/pages/profile-user/types.js +1 -0
- package/dist/components/types.d.ts +6 -0
- package/dist/components/types.js +1 -0
- package/dist/globals.css +210 -3
- package/dist/i18n/langs/en.d.ts +83 -0
- package/dist/i18n/langs/en.js +83 -0
- package/dist/i18n/langs/pt.d.ts +83 -0
- package/dist/i18n/langs/pt.js +83 -0
- package/dist/i18n/translations.d.ts +166 -0
- package/dist/index.d.ts +190 -25
- package/dist/index.js +190 -25
- package/dist/types/index.d.ts +159 -5
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProductDetailsData } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
data: ProductDetailsData;
|
|
4
|
+
isLoading?: boolean;
|
|
5
|
+
onBuy?: (id: string | number) => void;
|
|
6
|
+
onFavorite?: (id: string | number) => void;
|
|
7
|
+
onCart?: (id: string | number) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const ProductDetails04: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type ProductDetails04 = ReturnType<typeof ProductDetails04>;
|
|
11
|
+
export default ProductDetails04;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Página de detalhes do produto com seleção de layout.
|
|
4
|
+
* Permite escolher entre quatro variantes:
|
|
5
|
+
* - **1**: Duas colunas (galeria à esquerda, info à direita).
|
|
6
|
+
* - **2**: Galeria em destaque com conteúdo centralizado.
|
|
7
|
+
* - **3**: Painel de compra sticky no lado direito.
|
|
8
|
+
* - **4**: Abas de conteúdo (descrição e avaliações) abaixo da info.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <ProductDetails varient={1} data={productData} />
|
|
14
|
+
*
|
|
15
|
+
* <ProductDetails varient={3} data={productData} onBuy={(id) => console.log(id)} />
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import type { ProductDetailsProps } from "./types";
|
|
22
|
+
import ProductDetails01 from "./01/ProductDetails01.svelte";
|
|
23
|
+
import ProductDetails02 from "./02/ProductDetails02.svelte";
|
|
24
|
+
import ProductDetails03 from "./03/ProductDetails03.svelte";
|
|
25
|
+
import ProductDetails04 from "./04/ProductDetails04.svelte";
|
|
26
|
+
|
|
27
|
+
let { varient = 1, data, isLoading = false, onBuy, onFavorite, onCart }: ProductDetailsProps = $props();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
{#if varient === 2}
|
|
31
|
+
<ProductDetails02 {data} {isLoading} {onBuy} {onFavorite} {onCart} />
|
|
32
|
+
{:else if varient === 3}
|
|
33
|
+
<ProductDetails03 {data} {isLoading} {onBuy} {onFavorite} {onCart} />
|
|
34
|
+
{:else if varient === 4}
|
|
35
|
+
<ProductDetails04 {data} {isLoading} {onBuy} {onFavorite} {onCart} />
|
|
36
|
+
{:else}
|
|
37
|
+
<ProductDetails01 {data} {isLoading} {onBuy} {onFavorite} {onCart} />
|
|
38
|
+
{/if}
|
|
@@ -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;
|