negodesign 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/core/carousel/hero/CarouselHero.svelte +140 -0
- package/dist/components/core/carousel/hero/CarouselHero.svelte.d.ts +10 -0
- package/dist/components/core/carousel/types.d.ts +10 -0
- package/dist/components/core/hero/SimpleHero.svelte +65 -0
- package/dist/components/core/hero/SimpleHero.svelte.d.ts +4 -0
- package/dist/components/core/hero/types.d.ts +18 -0
- package/dist/components/core/hero/types.js +1 -0
- package/dist/components/core/nav/data/nav-menu.d.ts +5 -1
- package/dist/components/core/nav/ui/nav-menu.svelte +94 -87
- package/dist/globals.css +3657 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/styles/animate.min.css +4385 -0
- package/dist/styles/globals.css +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CarouselHeroItem, CarouselHeroProps } from "../types";
|
|
3
|
+
import NavMenu from "../../nav/ui/nav-menu.svelte";
|
|
4
|
+
import type { Snippet } from "svelte";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
navMenu,
|
|
8
|
+
items,
|
|
9
|
+
children,
|
|
10
|
+
intervalMs = 5000,
|
|
11
|
+
transitionMs = 1200,
|
|
12
|
+
}: CarouselHeroProps & {
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
intervalMs?: number;
|
|
15
|
+
transitionMs?: number;
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
// Buffer duplo: duas camadas fixas, nunca desmontadas — só a opacidade muda.
|
|
19
|
+
let layers = $state<[string, string]>([items?.[0]?.image ?? "", ""]);
|
|
20
|
+
let active = $state<0 | 1>(0);
|
|
21
|
+
let index = $state(0);
|
|
22
|
+
let switching = false; // evita trocas sobrepostas se o intervalo disparar antes do preload acabar
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
25
|
+
const preloadCache = new Map<string, Promise<void>>();
|
|
26
|
+
|
|
27
|
+
function preload(src: string): Promise<void> {
|
|
28
|
+
if (!src) return Promise.resolve();
|
|
29
|
+
const cached = preloadCache.get(src);
|
|
30
|
+
if (cached) return cached;
|
|
31
|
+
|
|
32
|
+
const promise = new Promise<void>((resolve) => {
|
|
33
|
+
const img = new Image();
|
|
34
|
+
img.onload = () => resolve();
|
|
35
|
+
img.onerror = () => resolve(); // não bloqueia o carousel por uma imagem partida
|
|
36
|
+
img.src = src;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
preloadCache.set(src, promise);
|
|
40
|
+
return promise;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Pré-carrega todas as imagens uma vez, assim que os items ficam disponíveis.
|
|
44
|
+
$effect(() => {
|
|
45
|
+
items?.forEach((item) => void preload(item.image));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
async function goTo(nextIndex: number) {
|
|
49
|
+
if (!items || nextIndex === index || switching) return;
|
|
50
|
+
switching = true;
|
|
51
|
+
|
|
52
|
+
const nextSrc = items[nextIndex].image;
|
|
53
|
+
await preload(nextSrc); // garante decode/cache antes de trocar a camada -> sem flash
|
|
54
|
+
|
|
55
|
+
const nextLayer = active === 0 ? 1 : 0;
|
|
56
|
+
layers[nextLayer] = nextSrc;
|
|
57
|
+
active = nextLayer;
|
|
58
|
+
index = nextIndex;
|
|
59
|
+
switching = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
$effect(() => {
|
|
63
|
+
if (!items || items.length <= 1) return;
|
|
64
|
+
|
|
65
|
+
const timer = setInterval(() => {
|
|
66
|
+
goTo((index + 1) % items.length);
|
|
67
|
+
}, intervalMs);
|
|
68
|
+
|
|
69
|
+
return () => clearInterval(timer);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function fadeIn(node: HTMLElement) {
|
|
73
|
+
requestAnimationFrame(() => {
|
|
74
|
+
node.style.opacity = "1";
|
|
75
|
+
});
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let selected = $derived<CarouselHeroItem | undefined>(items?.[index]);
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
{#if selected}
|
|
83
|
+
<header
|
|
84
|
+
class="w-full relative flex flex-col justify-center items-center h-[300px] md:h-[400px] overflow-hidden"
|
|
85
|
+
>
|
|
86
|
+
{#each layers as bg, i (i)}
|
|
87
|
+
<div
|
|
88
|
+
class="absolute inset-0 bg-cover bg-center will-change-[opacity]"
|
|
89
|
+
style="
|
|
90
|
+
background-image: {bg ? `url(${bg})` : 'none'};
|
|
91
|
+
opacity: {i === active ? 1 : 0};
|
|
92
|
+
transition: opacity {transitionMs}ms ease-in-out;
|
|
93
|
+
"
|
|
94
|
+
></div>
|
|
95
|
+
{/each}
|
|
96
|
+
|
|
97
|
+
<div class="absolute inset-0 bg-black/40"></div>
|
|
98
|
+
|
|
99
|
+
<div class="relative z-10 w-full flex flex-col items-center flex-1">
|
|
100
|
+
<NavMenu {...navMenu} />
|
|
101
|
+
|
|
102
|
+
<section
|
|
103
|
+
class="flex flex-col justify-center items-center flex-1 w-full h-auto text-center px-4"
|
|
104
|
+
>
|
|
105
|
+
{#key selected.title}
|
|
106
|
+
<div
|
|
107
|
+
style="transition: opacity {transitionMs * 0.6}ms ease-in-out;"
|
|
108
|
+
class="opacity-0"
|
|
109
|
+
use:fadeIn
|
|
110
|
+
>
|
|
111
|
+
<h1 class="text-white text-2xl md:text-4xl font-bold">
|
|
112
|
+
{selected.title}
|
|
113
|
+
</h1>
|
|
114
|
+
<p class="text-white/90 mt-2 max-w-2xl">
|
|
115
|
+
{selected.description}
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
{/key}
|
|
119
|
+
|
|
120
|
+
{#if children}
|
|
121
|
+
{@render children()}
|
|
122
|
+
{/if}
|
|
123
|
+
</section>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{#if items && items.length > 1}
|
|
127
|
+
<div class="absolute bottom-3 z-10 flex gap-2">
|
|
128
|
+
{#each items as item, i (i)}
|
|
129
|
+
<button
|
|
130
|
+
aria-label={`Ir para o slide ${i + 1}: ${item.title}`}
|
|
131
|
+
class="w-2 h-2 rounded-full transition-colors {i === index
|
|
132
|
+
? 'bg-white'
|
|
133
|
+
: 'bg-white/40'}"
|
|
134
|
+
onclick={() => goTo(i)}
|
|
135
|
+
></button>
|
|
136
|
+
{/each}
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
</header>
|
|
140
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CarouselHeroProps } from "../types";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
type $$ComponentProps = CarouselHeroProps & {
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
transitionMs?: number;
|
|
7
|
+
};
|
|
8
|
+
declare const CarouselHero: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type CarouselHero = ReturnType<typeof CarouselHero>;
|
|
10
|
+
export default CarouselHero;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IconSvgElement } from "@hugeicons/svelte";
|
|
2
2
|
import type { CardPromotionProps } from "../card/types";
|
|
3
|
+
import type { NavMenuProps } from "../nav/data/nav-menu";
|
|
3
4
|
export interface ItemCarousel {
|
|
4
5
|
value: string;
|
|
5
6
|
label: string;
|
|
@@ -32,3 +33,12 @@ export interface CarouselPromotionProps {
|
|
|
32
33
|
containerClass?: string;
|
|
33
34
|
onButtonViewAll?: () => void;
|
|
34
35
|
}
|
|
36
|
+
export interface CarouselHeroItem {
|
|
37
|
+
title: string;
|
|
38
|
+
image: string;
|
|
39
|
+
description: string;
|
|
40
|
+
}
|
|
41
|
+
export interface CarouselHeroProps {
|
|
42
|
+
navMenu: NavMenuProps;
|
|
43
|
+
items: CarouselHeroItem[];
|
|
44
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "../../ui/button/index.js";
|
|
3
|
+
import type { HeroSimpleProps } from "./types";
|
|
4
|
+
let {
|
|
5
|
+
title,
|
|
6
|
+
subTitle,
|
|
7
|
+
description,
|
|
8
|
+
image,
|
|
9
|
+
buttons,
|
|
10
|
+
titleClass,
|
|
11
|
+
subTitleClass,
|
|
12
|
+
descriptionClass,
|
|
13
|
+
imageClass,
|
|
14
|
+
}: HeroSimpleProps = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<section class="relative flex p-2">
|
|
18
|
+
<div class="w-full grid grid-cols-10 md:grid-cols-2 min-h-50 md:min-h-125">
|
|
19
|
+
<div class="flex justify-center items-center">
|
|
20
|
+
<div class="space-y-1">
|
|
21
|
+
{#if title}
|
|
22
|
+
<h1
|
|
23
|
+
class="font-bold text-[24px] md:text-[60px] lg:text-[80px] {titleClass}"
|
|
24
|
+
>
|
|
25
|
+
{title}
|
|
26
|
+
</h1>
|
|
27
|
+
{/if}
|
|
28
|
+
{#if subTitle}
|
|
29
|
+
<p class="font-semibold text-[16px] md:text-[40px] {subTitleClass}">
|
|
30
|
+
{subTitle}
|
|
31
|
+
</p>
|
|
32
|
+
{/if}
|
|
33
|
+
{#if description}
|
|
34
|
+
<p
|
|
35
|
+
class="text-[12px] md:text-[20px] md:w-8/12 mt-5 {descriptionClass}"
|
|
36
|
+
>
|
|
37
|
+
{description}
|
|
38
|
+
</p>
|
|
39
|
+
{/if}
|
|
40
|
+
{#if buttons}
|
|
41
|
+
<div class="flex flex-wrap gap-2 mt-5 md:mt-10">
|
|
42
|
+
{#each buttons as button, i (i)}
|
|
43
|
+
<Button
|
|
44
|
+
href={button.href}
|
|
45
|
+
class={button.className}
|
|
46
|
+
onclick={button.onClick}
|
|
47
|
+
>
|
|
48
|
+
{button.label}
|
|
49
|
+
</Button>
|
|
50
|
+
{/each}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="flex justify-center items-center">
|
|
56
|
+
{#if image}
|
|
57
|
+
<img
|
|
58
|
+
src={image}
|
|
59
|
+
alt={image}
|
|
60
|
+
class="rounded-lg h-50 md:h-125 w-[60%] object-cover {imageClass}"
|
|
61
|
+
/>
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface HeroButtonProps {
|
|
2
|
+
label: string;
|
|
3
|
+
href: string;
|
|
4
|
+
variant?: "primary" | "outline" | "ghost";
|
|
5
|
+
className?: string;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface HeroSimpleProps {
|
|
9
|
+
title?: string;
|
|
10
|
+
subTitle?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
image?: string;
|
|
13
|
+
buttons?: HeroButtonProps[];
|
|
14
|
+
titleClass?: string;
|
|
15
|
+
subTitleClass?: string;
|
|
16
|
+
descriptionClass?: string;
|
|
17
|
+
imageClass?: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IconSvgElement } from "@hugeicons/svelte";
|
|
2
2
|
export interface NavMenuLogoProps {
|
|
3
3
|
img?: string;
|
|
4
|
+
url?: string;
|
|
4
5
|
label: string;
|
|
5
6
|
className?: string;
|
|
6
7
|
onclick?: () => void;
|
|
@@ -20,12 +21,15 @@ export interface NavMenuActionsProps {
|
|
|
20
21
|
onclick?: () => void;
|
|
21
22
|
}
|
|
22
23
|
export interface NavMenuProps {
|
|
24
|
+
navClass?: string;
|
|
23
25
|
linkClass?: string;
|
|
24
26
|
buttonClass?: string;
|
|
25
27
|
logo?: NavMenuLogoProps;
|
|
26
28
|
links: NavMenuLinksProps[];
|
|
27
|
-
actions
|
|
29
|
+
actions?: NavMenuActionsProps[];
|
|
28
30
|
align?: 'LINK_SEPARATED_ACTIONS' | 'LINK_INTO_ACTIONS';
|
|
31
|
+
isLightSwitch?: boolean;
|
|
32
|
+
isLanguageSwitcher?: boolean;
|
|
29
33
|
onclickButtonLogin?: () => void;
|
|
30
34
|
onclickButtonRegister?: () => void;
|
|
31
35
|
}
|
|
@@ -1,98 +1,105 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { Button } from "../../../ui/button/index.js";
|
|
3
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
4
|
+
import type { NavMenuProps } from "../data/nav-menu";
|
|
5
|
+
import LightSwitch from "../../../ui/light-switch/light-switch.svelte";
|
|
6
|
+
import LanguageSwitcher from "../../../ui/language-switcher/language-switcher.svelte";
|
|
7
|
+
import { t } from "../../../../i18n";
|
|
8
|
+
import { Login03Icon, User03Icon } from "@hugeicons/core-free-icons";
|
|
9
|
+
import { useIsMobile } from "../../../../hooks/responsive.svelte";
|
|
10
|
+
import NavMenuDrawer from "./mobile/nav-menu-drawer.svelte";
|
|
11
|
+
import MenuLinks from "./menu-links.svelte";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
let {
|
|
14
|
+
logo,
|
|
15
|
+
links,
|
|
16
|
+
actions,
|
|
17
|
+
navClass = "",
|
|
18
|
+
linkClass = "",
|
|
19
|
+
buttonClass = "",
|
|
20
|
+
align = "LINK_SEPARATED_ACTIONS",
|
|
21
|
+
isLightSwitch = false,
|
|
22
|
+
isLanguageSwitcher = false,
|
|
23
|
+
onclickButtonLogin,
|
|
24
|
+
onclickButtonRegister,
|
|
25
|
+
}: NavMenuProps = $props();
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
const responsive = useIsMobile();
|
|
25
28
|
</script>
|
|
26
29
|
|
|
27
30
|
{#snippet actionButtons()}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{#if onclickButtonRegister}
|
|
44
|
-
<Button
|
|
45
|
-
variant="outline"
|
|
46
|
-
onclick={onclickButtonRegister}
|
|
47
|
-
class={buttonClass}
|
|
48
|
-
>
|
|
49
|
-
<HugeiconsIcon icon={User03Icon} />
|
|
50
|
-
{$t("label.register")}
|
|
51
|
-
</Button>
|
|
52
|
-
{/if}
|
|
53
|
-
{#if onclickButtonLogin}
|
|
54
|
-
<Button
|
|
55
|
-
variant="outline"
|
|
56
|
-
onclick={onclickButtonLogin}
|
|
57
|
-
class={buttonClass}
|
|
58
|
-
>
|
|
59
|
-
<HugeiconsIcon icon={Login03Icon} />
|
|
60
|
-
{$t("label.login")}
|
|
61
|
-
</Button>
|
|
31
|
+
<div class="flex items-center gap-2">
|
|
32
|
+
{#if isLightSwitch}
|
|
33
|
+
<LightSwitch />
|
|
34
|
+
{/if}
|
|
35
|
+
{#if isLanguageSwitcher}
|
|
36
|
+
<LanguageSwitcher />
|
|
37
|
+
{/if}
|
|
38
|
+
{#each actions as item (item.label)}
|
|
39
|
+
<Button
|
|
40
|
+
variant={item.type === "LINK" ? "ghost" : "outline"}
|
|
41
|
+
class={item.className}
|
|
42
|
+
onclick={item.onclick}
|
|
43
|
+
>
|
|
44
|
+
{#if item.icon}
|
|
45
|
+
<HugeiconsIcon icon={item.icon} />
|
|
62
46
|
{/if}
|
|
63
|
-
|
|
47
|
+
{item.label}
|
|
48
|
+
</Button>
|
|
49
|
+
{/each}
|
|
50
|
+
{#if onclickButtonRegister}
|
|
51
|
+
<Button
|
|
52
|
+
variant="outline"
|
|
53
|
+
onclick={onclickButtonRegister}
|
|
54
|
+
class={buttonClass}
|
|
55
|
+
>
|
|
56
|
+
<HugeiconsIcon icon={User03Icon} />
|
|
57
|
+
{$t("label.register")}
|
|
58
|
+
</Button>
|
|
59
|
+
{/if}
|
|
60
|
+
{#if onclickButtonLogin}
|
|
61
|
+
<Button
|
|
62
|
+
variant="outline"
|
|
63
|
+
onclick={onclickButtonLogin}
|
|
64
|
+
class={buttonClass}
|
|
65
|
+
>
|
|
66
|
+
<HugeiconsIcon icon={Login03Icon} />
|
|
67
|
+
{$t("label.login")}
|
|
68
|
+
</Button>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
64
71
|
{/snippet}
|
|
65
72
|
|
|
66
|
-
<nav
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
</
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
<nav
|
|
74
|
+
class={`flex items-center justify-between container py-5 md:px-5 ${navClass}`}
|
|
75
|
+
>
|
|
76
|
+
{#if logo}
|
|
77
|
+
<div>
|
|
78
|
+
{#if logo.img}
|
|
79
|
+
<img
|
|
80
|
+
src={logo.img}
|
|
81
|
+
alt="logo of site"
|
|
82
|
+
class={`max-h-50 ${logo.className}`}
|
|
83
|
+
/>
|
|
84
|
+
{:else if logo.label}
|
|
85
|
+
<span class={logo.className}>{logo.label}</span>
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
88
|
+
{:else}
|
|
89
|
+
<div>
|
|
90
|
+
<span class="hidden">Logo</span>
|
|
91
|
+
</div>
|
|
92
|
+
{/if}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{/if}
|
|
97
|
-
{/if}
|
|
94
|
+
{#if responsive.isMobile}
|
|
95
|
+
<NavMenuDrawer {links} {onclickButtonLogin} {onclickButtonRegister} />
|
|
96
|
+
{:else if align === "LINK_SEPARATED_ACTIONS"}
|
|
97
|
+
<MenuLinks {links} {linkClass} orientation="horizontal" />
|
|
98
|
+
{@render actionButtons()}
|
|
99
|
+
{:else if align === "LINK_INTO_ACTIONS"}
|
|
100
|
+
<div class="flex items-center gap-2">
|
|
101
|
+
<MenuLinks {links} {linkClass} orientation="vertical" />
|
|
102
|
+
{@render actionButtons()}
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
98
105
|
</nav>
|