negodesign 0.0.74 → 0.0.76
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/pages/security/auth/PageAuth01.svelte +65 -0
- package/dist/components/pages/security/auth/PageAuth01.svelte.d.ts +17 -0
- package/dist/components/pages/security/auth/PageAuth02.svelte +45 -0
- package/dist/components/pages/security/auth/PageAuth02.svelte.d.ts +17 -0
- package/dist/components/pages/security/auth/PageAuth03.svelte +45 -0
- package/dist/components/pages/security/auth/PageAuth03.svelte.d.ts +17 -0
- package/dist/components/pages/security/login/01/ui/PageLogin01.svelte +15 -40
- package/dist/components/pages/security/login/02/ui/LoginCard02.svelte +15 -4
- package/dist/components/pages/security/login/02/ui/PageLogin02.svelte +15 -20
- package/dist/components/pages/security/login/03/ui/LoginCard03.svelte +34 -16
- package/dist/components/pages/security/login/03/ui/PageLogin03.svelte +15 -20
- package/dist/components/pages/security/register/01/ui/PageRegister01.svelte +64 -0
- package/dist/components/pages/security/register/01/ui/PageRegister01.svelte.d.ts +3 -0
- package/dist/components/pages/security/register/01/ui/RegisterCard.svelte +100 -0
- package/dist/components/pages/security/register/01/ui/RegisterCard.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/02/ui/PageRegister02.svelte +76 -0
- package/dist/components/pages/security/register/02/ui/PageRegister02.svelte.d.ts +4 -0
- package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte +98 -0
- package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/03/ui/PageRegister03.svelte +76 -0
- package/dist/components/pages/security/register/03/ui/PageRegister03.svelte.d.ts +4 -0
- package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte +103 -0
- package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte.d.ts +12 -0
- package/dist/components/pages/security/register/FormRegister.svelte +120 -0
- package/dist/components/pages/security/register/FormRegister.svelte.d.ts +8 -0
- package/dist/components/pages/security/register/PageRegister.svelte +30 -0
- package/dist/components/pages/security/register/PageRegister.svelte.d.ts +8 -0
- package/dist/components/pages/security/register/types.d.ts +51 -0
- package/dist/components/pages/security/register/types.js +1 -0
- package/dist/components/ui/form/Form.svelte +171 -0
- package/dist/components/ui/form/Form.svelte.d.ts +4 -0
- package/dist/components/ui/form/types.d.ts +78 -0
- package/dist/components/ui/form/types.js +1 -0
- package/dist/components/ui/form/ui/input-badges.svelte +214 -0
- package/dist/components/ui/form/ui/input-badges.svelte.d.ts +14 -0
- package/dist/components/ui/form/ui/input-checkbox.svelte +86 -0
- package/dist/components/ui/form/ui/input-checkbox.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-radio.svelte +82 -0
- package/dist/components/ui/form/ui/input-radio.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-select.svelte +195 -0
- package/dist/components/ui/form/ui/input-select.svelte.d.ts +17 -0
- package/dist/components/ui/form/ui/input-textarea.svelte +61 -0
- package/dist/components/ui/form/ui/input-textarea.svelte.d.ts +13 -0
- package/dist/components/ui/form/ui/input-toggle.svelte +89 -0
- package/dist/components/ui/form/ui/input-toggle.svelte.d.ts +11 -0
- package/dist/components/ui/modal/badge/index.d.ts +3 -0
- package/dist/components/ui/modal/badge/index.js +2 -0
- package/dist/components/ui/modal/badge/types.d.ts +35 -0
- package/dist/components/ui/modal/badge/types.js +1 -0
- package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte +87 -0
- package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/core/index.d.ts +3 -0
- package/dist/components/ui/modal/core/index.js +2 -0
- package/dist/components/ui/modal/core/types.d.ts +84 -0
- package/dist/components/ui/modal/core/types.js +1 -0
- package/dist/components/ui/modal/core/ui/ModalCore.svelte +132 -0
- package/dist/components/ui/modal/core/ui/ModalCore.svelte.d.ts +4 -0
- package/dist/components/ui/modal/feedback/index.d.ts +3 -0
- package/dist/components/ui/modal/feedback/index.js +2 -0
- package/dist/components/ui/modal/feedback/types.d.ts +32 -0
- package/dist/components/ui/modal/feedback/types.js +1 -0
- package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte +135 -0
- package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte.d.ts +4 -0
- package/dist/components/ui/modal/form/index.d.ts +3 -0
- package/dist/components/ui/modal/form/index.js +2 -0
- package/dist/components/ui/modal/form/types.d.ts +38 -0
- package/dist/components/ui/modal/form/types.js +1 -0
- package/dist/components/ui/modal/form/ui/ModalForm.svelte +67 -0
- package/dist/components/ui/modal/form/ui/ModalForm.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/index.d.ts +5 -0
- package/dist/components/ui/modal/grid/index.js +4 -0
- package/dist/components/ui/modal/grid/types.d.ts +51 -0
- package/dist/components/ui/modal/grid/types.js +1 -0
- package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte +83 -0
- package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte +146 -0
- package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte.d.ts +4 -0
- package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte +60 -0
- package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/map/index.d.ts +3 -0
- package/dist/components/ui/modal/map/index.js +2 -0
- package/dist/components/ui/modal/map/types.d.ts +48 -0
- package/dist/components/ui/modal/map/types.js +1 -0
- package/dist/components/ui/modal/map/ui/ModalMap.svelte +187 -0
- package/dist/components/ui/modal/map/ui/ModalMap.svelte.d.ts +4 -0
- package/dist/components/ui/modal/notification/index.d.ts +3 -0
- package/dist/components/ui/modal/notification/index.js +2 -0
- package/dist/components/ui/modal/notification/types.d.ts +26 -0
- package/dist/components/ui/modal/notification/types.js +1 -0
- package/dist/components/ui/modal/notification/ui/ModalNotification.svelte +65 -0
- package/dist/components/ui/modal/notification/ui/ModalNotification.svelte.d.ts +4 -0
- package/dist/components/ui/modal/share/index.d.ts +3 -0
- package/dist/components/ui/modal/share/index.js +2 -0
- package/dist/components/ui/modal/share/types.d.ts +47 -0
- package/dist/components/ui/modal/share/types.js +1 -0
- package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte +156 -0
- package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte.d.ts +4 -0
- package/dist/components/ui/modal/upload/index.d.ts +3 -0
- package/dist/components/ui/modal/upload/index.js +2 -0
- package/dist/components/ui/modal/upload/types.d.ts +79 -0
- package/dist/components/ui/modal/upload/types.js +1 -0
- package/dist/components/ui/modal/upload/ui/ModalUpload.svelte +268 -0
- package/dist/components/ui/modal/upload/ui/ModalUpload.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section01.svelte +90 -0
- package/dist/components/ui/section/Section01.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section02.svelte +68 -0
- package/dist/components/ui/section/Section02.svelte.d.ts +4 -0
- package/dist/components/ui/section/Section03.svelte +52 -0
- package/dist/components/ui/section/Section03.svelte.d.ts +4 -0
- package/dist/components/ui/section/index.d.ts +4 -0
- package/dist/components/ui/section/index.js +3 -0
- package/dist/components/ui/section/types.d.ts +46 -0
- package/dist/components/ui/section/types.js +1 -0
- package/dist/docs/DocsSidebar.svelte +58 -1
- package/dist/docs/components.d.ts +1 -1
- package/dist/docs/components.js +968 -0
- package/dist/globals.css +360 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +185 -0
- package/dist/types/index.d.ts +53 -0
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../../../utils";
|
|
3
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
4
|
+
import ModalCore from "../../core/ui/ModalCore.svelte";
|
|
5
|
+
import type { ModalShareSelectionProps, ModelSocialShare } from "../types";
|
|
6
|
+
|
|
7
|
+
const defaultNetworks: ModelSocialShare[] = [
|
|
8
|
+
{ name: "WhatsApp", img: "/img/icons8-whatsapp-48.png", color: "#25D366" },
|
|
9
|
+
{
|
|
10
|
+
name: "Instagram",
|
|
11
|
+
img: "/img/icons8-instagram-48.png",
|
|
12
|
+
color: "#E4405F",
|
|
13
|
+
},
|
|
14
|
+
{ name: "Facebook", img: "/img/icons8-facebook-48.png", color: "#1877F2" },
|
|
15
|
+
{ name: "Twitter", img: "/img/icons8-twitterx-50.png", color: "#000000" },
|
|
16
|
+
{ name: "LinkedIn", img: "/img/icons8-linkedin-48.png", color: "#0A66C2" },
|
|
17
|
+
{
|
|
18
|
+
name: "Pinterest",
|
|
19
|
+
img: "/img/icons8-pinterest-48.png",
|
|
20
|
+
color: "#E60023",
|
|
21
|
+
},
|
|
22
|
+
{ name: "Tumblr", img: "/img/icons8-tumblr-48.png", color: "#36465D" },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
title,
|
|
27
|
+
subtitle,
|
|
28
|
+
networks,
|
|
29
|
+
isWhatsapp = true,
|
|
30
|
+
isInstagram = true,
|
|
31
|
+
isFacebook = true,
|
|
32
|
+
isTwitter = true,
|
|
33
|
+
isLinkedin = true,
|
|
34
|
+
isPinterest = false,
|
|
35
|
+
isTumblr = false,
|
|
36
|
+
linkLabel = "Or copy link",
|
|
37
|
+
linkValue = "",
|
|
38
|
+
copyText = "Copy",
|
|
39
|
+
copiedText = "Copied!",
|
|
40
|
+
isOpen = $bindable(false),
|
|
41
|
+
onShare,
|
|
42
|
+
onCopy,
|
|
43
|
+
onClose,
|
|
44
|
+
class: className,
|
|
45
|
+
}: ModalShareSelectionProps = $props();
|
|
46
|
+
|
|
47
|
+
let copied = $state(false);
|
|
48
|
+
|
|
49
|
+
const networkFlags: Record<string, boolean> = $derived({
|
|
50
|
+
whatsapp: isWhatsapp,
|
|
51
|
+
instagram: isInstagram,
|
|
52
|
+
facebook: isFacebook,
|
|
53
|
+
twitter: isTwitter,
|
|
54
|
+
linkedin: isLinkedin,
|
|
55
|
+
pinterest: isPinterest,
|
|
56
|
+
tumblr: isTumblr,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const visibleNetworks = $derived(
|
|
60
|
+
(networks?.length ? networks : defaultNetworks).filter(
|
|
61
|
+
(network) => networkFlags[network.name.toLowerCase()] ?? true,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
function handleCopy() {
|
|
66
|
+
navigator.clipboard.writeText(linkValue).then(() => {
|
|
67
|
+
copied = true;
|
|
68
|
+
onCopy?.(linkValue);
|
|
69
|
+
setTimeout(() => (copied = false), 2000);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<ModalCore
|
|
75
|
+
bind:isOpen
|
|
76
|
+
{title}
|
|
77
|
+
{subtitle}
|
|
78
|
+
showProgress={false}
|
|
79
|
+
showBack={false}
|
|
80
|
+
{onClose}
|
|
81
|
+
class={cn("max-w-lg", className)}
|
|
82
|
+
>
|
|
83
|
+
{#snippet content()}
|
|
84
|
+
<!-- Social Networks -->
|
|
85
|
+
{#if visibleNetworks.length > 0}
|
|
86
|
+
<div class="flex flex-wrap items-center justify-center gap-4">
|
|
87
|
+
{#each visibleNetworks as network (network.name)}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="group flex flex-col items-center gap-2"
|
|
91
|
+
onclick={() => onShare?.(network)}
|
|
92
|
+
>
|
|
93
|
+
<div
|
|
94
|
+
class={cn(
|
|
95
|
+
"flex size-14 items-center justify-center rounded-full border-2 border-border bg-white transition-all duration-200",
|
|
96
|
+
"hover:scale-110 hover:shadow-md",
|
|
97
|
+
)}
|
|
98
|
+
style={network.color ? `border-color: ${network.color}30` : ""}
|
|
99
|
+
>
|
|
100
|
+
{#snippet icon()}
|
|
101
|
+
{#if network.img}
|
|
102
|
+
<img
|
|
103
|
+
src={network.img}
|
|
104
|
+
alt={network.name}
|
|
105
|
+
class="size-7 object-contain transition-transform duration-200 group-hover:scale-110"
|
|
106
|
+
loading="lazy"
|
|
107
|
+
/>
|
|
108
|
+
{:else if typeof network.icon === "string"}
|
|
109
|
+
<i
|
|
110
|
+
class="{network.icon} text-2xl transition-colors"
|
|
111
|
+
style={network.color ? `color: ${network.color}` : ""}
|
|
112
|
+
></i>
|
|
113
|
+
{:else if network.icon}
|
|
114
|
+
<HugeiconsIcon
|
|
115
|
+
icon={network.icon}
|
|
116
|
+
class="size-6 transition-colors"
|
|
117
|
+
style={network.color ? `color: ${network.color}` : ""}
|
|
118
|
+
/>
|
|
119
|
+
{/if}
|
|
120
|
+
{/snippet}
|
|
121
|
+
{@render icon()}
|
|
122
|
+
</div>
|
|
123
|
+
<span class="text-xs text-muted-foreground">{network.name}</span>
|
|
124
|
+
</button>
|
|
125
|
+
{/each}
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
{/snippet}
|
|
129
|
+
|
|
130
|
+
{#snippet footer()}
|
|
131
|
+
<!-- Copy Link -->
|
|
132
|
+
<div>
|
|
133
|
+
<p class="mb-3 text-sm font-medium text-muted-foreground">{linkLabel}</p>
|
|
134
|
+
<div class="flex items-center gap-2">
|
|
135
|
+
<input
|
|
136
|
+
type="text"
|
|
137
|
+
readonly
|
|
138
|
+
value={linkValue}
|
|
139
|
+
class="flex-1 rounded-lg border border-border bg-muted px-4 py-2.5 text-sm text-foreground outline-none"
|
|
140
|
+
/>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
class={cn(
|
|
144
|
+
"shrink-0 rounded-lg px-5 py-2.5 text-sm font-semibold transition-colors",
|
|
145
|
+
copied
|
|
146
|
+
? "bg-green-500 text-white"
|
|
147
|
+
: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
148
|
+
)}
|
|
149
|
+
onclick={handleCopy}
|
|
150
|
+
>
|
|
151
|
+
{copied ? copiedText : copyText}
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
{/snippet}
|
|
156
|
+
</ModalCore>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estado de um ficheiro no upload.
|
|
3
|
+
* @property uploading - A subir.
|
|
4
|
+
* @property completed - Subida concluída.
|
|
5
|
+
* @property failed - Falhou o upload.
|
|
6
|
+
*/
|
|
7
|
+
export type UploadFileStatus = "uploading" | "completed" | "failed";
|
|
8
|
+
/**
|
|
9
|
+
* Modelo de dados para um ficheiro no modal de upload.
|
|
10
|
+
* @property id - Identificador único do ficheiro.
|
|
11
|
+
* @property name - Nome do ficheiro.
|
|
12
|
+
* @property size - Tamanho do ficheiro em bytes.
|
|
13
|
+
* @property date - Data de upload.
|
|
14
|
+
* @property progress - Percentagem de progresso (0-100).
|
|
15
|
+
* @property status - Estado do upload.
|
|
16
|
+
* @property type - Tipo MIME do ficheiro.
|
|
17
|
+
*/
|
|
18
|
+
export type UploadFile = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
size: number;
|
|
22
|
+
date?: string;
|
|
23
|
+
progress?: number;
|
|
24
|
+
status?: UploadFileStatus;
|
|
25
|
+
type?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Props do componente ModalUpload — modal de upload de ficheiros.
|
|
29
|
+
* Suporta drag and drop, lista de ficheiros com progress bar e estados.
|
|
30
|
+
*
|
|
31
|
+
* @property title - Título do modal (default: "Upload File").
|
|
32
|
+
* @property accept - Tipos MIME aceites (ex: ".csv,.xlsx,.xls").
|
|
33
|
+
* @property acceptedFormats - Texto com formatos aceites.
|
|
34
|
+
* @property downloadText - Texto do link de download.
|
|
35
|
+
* @property downloadLabel - Label do link de download.
|
|
36
|
+
* @property dragText - Texto principal da área de drag.
|
|
37
|
+
* @property browseText - Texto do link "Browse".
|
|
38
|
+
* @property cancelText - Texto do botão cancelar.
|
|
39
|
+
* @property uploadText - Texto do botão upload.
|
|
40
|
+
* @property uploadLoadingText - Texto durante upload.
|
|
41
|
+
* @property files - Lista de ficheiros (bindable).
|
|
42
|
+
* @property isOpen - Controla a visibilidade do modal (bindable).
|
|
43
|
+
* @property multiple - Permite selecionar múltiplos ficheiros.
|
|
44
|
+
* @property maxSize - Tamanho máximo por ficheiro em bytes.
|
|
45
|
+
* @property isLoading - Estado de carregamento do botão upload.
|
|
46
|
+
* @property onFilesSelected - Chamado ao selecionar ficheiros.
|
|
47
|
+
* @property onUpload - Chamado ao clicar no botão Upload.
|
|
48
|
+
* @property onCancel - Chamado ao clicar no Cancel.
|
|
49
|
+
* @property onRemoveFile - Chamado ao remover um ficheiro.
|
|
50
|
+
* @property onRetryFile - Chamado ao repetir um ficheiro com falha.
|
|
51
|
+
* @property onDownload - Chamado ao clicar no link de download.
|
|
52
|
+
* @property onClose - Chamado ao fechar o modal.
|
|
53
|
+
* @property class - Classe CSS extra no container.
|
|
54
|
+
*/
|
|
55
|
+
export type ModalUploadProps = {
|
|
56
|
+
title?: string;
|
|
57
|
+
accept?: string;
|
|
58
|
+
acceptedFormats?: string;
|
|
59
|
+
downloadText?: string;
|
|
60
|
+
downloadLabel?: string;
|
|
61
|
+
dragText?: string;
|
|
62
|
+
browseText?: string;
|
|
63
|
+
cancelText?: string;
|
|
64
|
+
uploadText?: string;
|
|
65
|
+
uploadLoadingText?: string;
|
|
66
|
+
files?: UploadFile[];
|
|
67
|
+
isOpen?: boolean;
|
|
68
|
+
multiple?: boolean;
|
|
69
|
+
maxSize?: number;
|
|
70
|
+
isLoading?: boolean;
|
|
71
|
+
onFilesSelected?: (files: File[]) => void;
|
|
72
|
+
onUpload?: (files: UploadFile[]) => void;
|
|
73
|
+
onCancel?: () => void;
|
|
74
|
+
onRemoveFile?: (id: string) => void;
|
|
75
|
+
onRetryFile?: (id: string) => void;
|
|
76
|
+
onDownload?: () => void;
|
|
77
|
+
onClose?: () => void;
|
|
78
|
+
class?: string;
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../../../utils";
|
|
3
|
+
import {
|
|
4
|
+
Cancel01Icon,
|
|
5
|
+
Delete02Icon,
|
|
6
|
+
ArrowDown01Icon,
|
|
7
|
+
RefreshIcon,
|
|
8
|
+
} from "@hugeicons/core-free-icons";
|
|
9
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
10
|
+
import ModalCore from "../../core/ui/ModalCore.svelte";
|
|
11
|
+
import type { ModalUploadProps, UploadFile } from "../types";
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
title = "Upload File",
|
|
15
|
+
accept = "",
|
|
16
|
+
acceptedFormats = "csv, xlsx, xls",
|
|
17
|
+
downloadText = "Download",
|
|
18
|
+
downloadLabel = "sample data for file upload",
|
|
19
|
+
dragText = "Drag and Drop or",
|
|
20
|
+
browseText = "Browse",
|
|
21
|
+
cancelText = "Cancel",
|
|
22
|
+
uploadText = "Upload",
|
|
23
|
+
uploadLoadingText = "Uploading...",
|
|
24
|
+
files = $bindable([]),
|
|
25
|
+
isOpen = $bindable(false),
|
|
26
|
+
multiple = true,
|
|
27
|
+
maxSize = 0,
|
|
28
|
+
isLoading = false,
|
|
29
|
+
onFilesSelected,
|
|
30
|
+
onUpload,
|
|
31
|
+
onCancel,
|
|
32
|
+
onRemoveFile,
|
|
33
|
+
onRetryFile,
|
|
34
|
+
onDownload,
|
|
35
|
+
onClose,
|
|
36
|
+
class: className,
|
|
37
|
+
}: ModalUploadProps = $props();
|
|
38
|
+
|
|
39
|
+
let isDragOver = $state(false);
|
|
40
|
+
let fileInputRef = $state<HTMLInputElement | null>(null);
|
|
41
|
+
|
|
42
|
+
function handleDragOver(e: DragEvent) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
isDragOver = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleDragLeave() {
|
|
48
|
+
isDragOver = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleDrop(e: DragEvent) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
isDragOver = false;
|
|
54
|
+
const droppedFiles = Array.from(e.dataTransfer?.files ?? []);
|
|
55
|
+
processFiles(droppedFiles);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleFileInput(e: Event) {
|
|
59
|
+
const inputFiles = Array.from((e.target as HTMLInputElement).files ?? []);
|
|
60
|
+
processFiles(inputFiles);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function processFiles(rawFiles: File[]) {
|
|
64
|
+
let filtered = rawFiles;
|
|
65
|
+
if (maxSize > 0) {
|
|
66
|
+
filtered = rawFiles.filter((f) => f.size <= maxSize);
|
|
67
|
+
}
|
|
68
|
+
if (filtered.length > 0) {
|
|
69
|
+
onFilesSelected?.(filtered);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openFilePicker() {
|
|
74
|
+
fileInputRef?.click();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatSize(bytes: number): string {
|
|
78
|
+
if (bytes === 0) return "0 B";
|
|
79
|
+
const k = 1024;
|
|
80
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
81
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
82
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(0)) + " " + sizes[i];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getStatusColor(status?: string): string {
|
|
86
|
+
switch (status) {
|
|
87
|
+
case "completed":
|
|
88
|
+
return "text-green-600";
|
|
89
|
+
case "failed":
|
|
90
|
+
return "text-destructive";
|
|
91
|
+
default:
|
|
92
|
+
return "text-muted-foreground";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleCancel() {
|
|
97
|
+
isOpen = false;
|
|
98
|
+
onCancel?.();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleUpload() {
|
|
102
|
+
onUpload?.(files);
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<ModalCore
|
|
107
|
+
bind:isOpen
|
|
108
|
+
{title}
|
|
109
|
+
showProgress={false}
|
|
110
|
+
showBack={false}
|
|
111
|
+
{onClose}
|
|
112
|
+
class={cn("max-w-lg", className)}
|
|
113
|
+
>
|
|
114
|
+
{#snippet content()}
|
|
115
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
116
|
+
<div
|
|
117
|
+
class={cn(
|
|
118
|
+
"flex flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors",
|
|
119
|
+
isDragOver
|
|
120
|
+
? "border-primary bg-primary/5"
|
|
121
|
+
: "border-border hover:border-primary/30"
|
|
122
|
+
)}
|
|
123
|
+
ondragover={handleDragOver}
|
|
124
|
+
ondragleave={handleDragLeave}
|
|
125
|
+
ondrop={handleDrop}
|
|
126
|
+
>
|
|
127
|
+
<input
|
|
128
|
+
bind:this={fileInputRef}
|
|
129
|
+
type="file"
|
|
130
|
+
{accept}
|
|
131
|
+
multiple={multiple}
|
|
132
|
+
class="hidden"
|
|
133
|
+
onchange={handleFileInput}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<div class="mb-3 flex size-12 items-center justify-center rounded-full bg-muted">
|
|
137
|
+
<HugeiconsIcon icon={ArrowDown01Icon} class="size-6 text-muted-foreground" />
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<p class="text-sm text-foreground">
|
|
141
|
+
{dragText}
|
|
142
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
143
|
+
<span
|
|
144
|
+
class="cursor-pointer font-semibold text-primary underline"
|
|
145
|
+
onclick={openFilePicker}
|
|
146
|
+
onkeydown={(e) => { if (e.key === "Enter") openFilePicker(); }}
|
|
147
|
+
>
|
|
148
|
+
{browseText}
|
|
149
|
+
</span>
|
|
150
|
+
to Upload
|
|
151
|
+
</p>
|
|
152
|
+
|
|
153
|
+
{#if acceptedFormats}
|
|
154
|
+
<p class="mt-1 text-xs text-muted-foreground">
|
|
155
|
+
Supported formats: {acceptedFormats}
|
|
156
|
+
</p>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<!-- Download Link -->
|
|
161
|
+
{#if onDownload}
|
|
162
|
+
<div class="mt-3 flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
163
|
+
<span class="text-green-600">⬇</span>
|
|
164
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
165
|
+
<span>
|
|
166
|
+
<span
|
|
167
|
+
class="cursor-pointer font-medium text-primary underline"
|
|
168
|
+
onclick={onDownload}
|
|
169
|
+
onkeydown={(e) => { if (e.key === "Enter") onDownload(); }}
|
|
170
|
+
>
|
|
171
|
+
{downloadText}
|
|
172
|
+
</span>
|
|
173
|
+
{downloadLabel}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
{/if}
|
|
177
|
+
|
|
178
|
+
<!-- File List -->
|
|
179
|
+
{#if files.length > 0}
|
|
180
|
+
<div class="mt-4 space-y-3">
|
|
181
|
+
{#each files as file (file.id)}
|
|
182
|
+
<div class="rounded-lg border border-border bg-card p-3">
|
|
183
|
+
<div class="flex items-center gap-3">
|
|
184
|
+
<!-- File Icon -->
|
|
185
|
+
<div class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10">
|
|
186
|
+
<span class="text-xs font-bold text-primary">
|
|
187
|
+
{file.name.split(".").pop()?.toUpperCase() ?? "FILE"}
|
|
188
|
+
</span>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- File Info -->
|
|
192
|
+
<div class="min-w-0 flex-1">
|
|
193
|
+
<p class="truncate text-sm font-medium">{file.name}</p>
|
|
194
|
+
<p class="text-xs text-muted-foreground">
|
|
195
|
+
{#if file.date}Date: {file.date} · {/if}Size: {formatSize(file.size)}
|
|
196
|
+
</p>
|
|
197
|
+
{#if file.status}
|
|
198
|
+
<p class={cn("text-xs font-medium", getStatusColor(file.status))}>
|
|
199
|
+
{file.status === "completed"
|
|
200
|
+
? "Completed"
|
|
201
|
+
: file.status === "failed"
|
|
202
|
+
? "Upload Failed"
|
|
203
|
+
: ""}
|
|
204
|
+
</p>
|
|
205
|
+
{/if}
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Actions -->
|
|
209
|
+
<div class="flex items-center gap-1">
|
|
210
|
+
{#if file.status === "failed" && onRetryFile}
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
class="rounded p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground"
|
|
214
|
+
onclick={() => onRetryFile(file.id)}
|
|
215
|
+
title="Retry"
|
|
216
|
+
>
|
|
217
|
+
<HugeiconsIcon icon={RefreshIcon} class="size-4" />
|
|
218
|
+
</button>
|
|
219
|
+
{/if}
|
|
220
|
+
{#if onRemoveFile}
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
class="rounded p-1 text-muted-foreground transition hover:bg-destructive/10 hover:text-destructive"
|
|
224
|
+
onclick={() => onRemoveFile(file.id)}
|
|
225
|
+
title="Remove"
|
|
226
|
+
>
|
|
227
|
+
<HugeiconsIcon icon={Delete02Icon} class="size-4" />
|
|
228
|
+
</button>
|
|
229
|
+
{/if}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<!-- Progress Bar -->
|
|
234
|
+
{#if file.status === "uploading" && file.progress !== undefined}
|
|
235
|
+
<div class="mt-2.5 h-1.5 w-full overflow-hidden rounded-full bg-muted">
|
|
236
|
+
<div
|
|
237
|
+
class="h-full rounded-full bg-primary transition-all duration-300"
|
|
238
|
+
style="width: {file.progress}%"
|
|
239
|
+
></div>
|
|
240
|
+
</div>
|
|
241
|
+
<p class="mt-1 text-right text-xs text-muted-foreground">{file.progress}%</p>
|
|
242
|
+
{/if}
|
|
243
|
+
</div>
|
|
244
|
+
{/each}
|
|
245
|
+
</div>
|
|
246
|
+
{/if}
|
|
247
|
+
{/snippet}
|
|
248
|
+
|
|
249
|
+
{#snippet footer()}
|
|
250
|
+
<div class="flex items-center justify-end gap-3">
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
class="rounded-lg border border-border px-5 py-2.5 text-sm font-semibold transition hover:bg-muted"
|
|
254
|
+
onclick={handleCancel}
|
|
255
|
+
>
|
|
256
|
+
{cancelText}
|
|
257
|
+
</button>
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
disabled={isLoading || files.length === 0}
|
|
261
|
+
class="rounded-lg bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
262
|
+
onclick={handleUpload}
|
|
263
|
+
>
|
|
264
|
+
{isLoading ? uploadLoadingText : uploadText}
|
|
265
|
+
</button>
|
|
266
|
+
</div>
|
|
267
|
+
{/snippet}
|
|
268
|
+
</ModalCore>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
3
|
+
import {
|
|
4
|
+
ArrowLeft01Icon,
|
|
5
|
+
ArrowRight01Icon,
|
|
6
|
+
} from "@hugeicons/core-free-icons";
|
|
7
|
+
import type { Section01Props } from "./types";
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
items = [],
|
|
13
|
+
selectedKey,
|
|
14
|
+
titleClass,
|
|
15
|
+
descriptionClass,
|
|
16
|
+
className,
|
|
17
|
+
onSelect,
|
|
18
|
+
onPrev,
|
|
19
|
+
onNext,
|
|
20
|
+
}: Section01Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<section class={className}>
|
|
24
|
+
<div class="px-6 py-12 md:px-12 md:py-20">
|
|
25
|
+
<div
|
|
26
|
+
class="flex flex-col md:flex-row md:items-start md:justify-between gap-8 mb-12"
|
|
27
|
+
>
|
|
28
|
+
<h2
|
|
29
|
+
class="text-3xl md:text-5xl lg:text-6xl font-bold leading-tight {titleClass}"
|
|
30
|
+
>
|
|
31
|
+
{title}
|
|
32
|
+
</h2>
|
|
33
|
+
<div class="flex flex-col gap-6 md:max-w-md">
|
|
34
|
+
<p class="text-sm md:text-base text-gray-600 {descriptionClass}">
|
|
35
|
+
{description}
|
|
36
|
+
</p>
|
|
37
|
+
<div class="flex items-center gap-4">
|
|
38
|
+
<button
|
|
39
|
+
onclick={onPrev}
|
|
40
|
+
class="flex items-center justify-center w-10 h-10 rounded-full border border-gray-300 hover:bg-gray-200 transition-colors"
|
|
41
|
+
>
|
|
42
|
+
<HugeiconsIcon icon={ArrowLeft01Icon} class="size-5" />
|
|
43
|
+
</button>
|
|
44
|
+
<div class="flex-1 h-0.5">
|
|
45
|
+
<div class="h-full bg-black w-1/3"></div>
|
|
46
|
+
</div>
|
|
47
|
+
<button
|
|
48
|
+
onclick={onNext}
|
|
49
|
+
class="flex items-center justify-center w-10 h-10 rounded-full border border-gray-300 hover:bg-gray-200 transition-colors"
|
|
50
|
+
>
|
|
51
|
+
<HugeiconsIcon icon={ArrowRight01Icon} class="size-5" />
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
58
|
+
{#each items as item, i (i)}
|
|
59
|
+
<button
|
|
60
|
+
onclick={() => onSelect?.(item.id ?? i)}
|
|
61
|
+
class="flex flex-col items-start text-left p-6 rounded-2xl transition-colors {item.isActive
|
|
62
|
+
? 'bg-[#3d3d3d] text-white'
|
|
63
|
+
: 'bg-white text-black hover:bg-gray-100'}"
|
|
64
|
+
>
|
|
65
|
+
<div
|
|
66
|
+
class="flex items-center justify-center w-14 h-14 rounded-full bg-white/20 mb-6 {item.isActive
|
|
67
|
+
? 'bg-white/20'
|
|
68
|
+
: 'bg-gray-100'}"
|
|
69
|
+
>
|
|
70
|
+
{#if typeof item.icon === "string"}
|
|
71
|
+
<i class="text-2xl {item.icon}"></i>
|
|
72
|
+
{:else}
|
|
73
|
+
<HugeiconsIcon icon={item.icon} class="size-7" />
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
<h3 class="text-lg font-bold mb-2">{item.title}</h3>
|
|
77
|
+
{#if item.description}
|
|
78
|
+
<p
|
|
79
|
+
class="text-sm {item.isActive
|
|
80
|
+
? 'text-gray-300'
|
|
81
|
+
: 'text-gray-500'}"
|
|
82
|
+
>
|
|
83
|
+
{item.description}
|
|
84
|
+
</p>
|
|
85
|
+
{/if}
|
|
86
|
+
</button>
|
|
87
|
+
{/each}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
3
|
+
import { ArrowRight01Icon } from "@hugeicons/core-free-icons";
|
|
4
|
+
import type { Section02Props } from "./types";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
image,
|
|
10
|
+
imageAlt,
|
|
11
|
+
features = [],
|
|
12
|
+
titleClass,
|
|
13
|
+
descriptionClass,
|
|
14
|
+
className,
|
|
15
|
+
imageClass,
|
|
16
|
+
}: Section02Props = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<section class={className}>
|
|
20
|
+
<div class="px-6 py-12 md:px-12 md:py-20">
|
|
21
|
+
<div class="flex flex-col md:flex-row md:items-start gap-8 md:gap-16">
|
|
22
|
+
<div class="flex flex-col gap-8 md:w-1/2">
|
|
23
|
+
<h2
|
|
24
|
+
class="text-3xl md:text-5xl lg:text-6xl font-bold leading-tight {titleClass}"
|
|
25
|
+
>
|
|
26
|
+
{title}
|
|
27
|
+
</h2>
|
|
28
|
+
{#if image}
|
|
29
|
+
<div class="rounded-3xl overflow-hidden">
|
|
30
|
+
<img
|
|
31
|
+
src={image}
|
|
32
|
+
alt={imageAlt ?? title}
|
|
33
|
+
class="w-full h-auto object-cover {imageClass}"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="flex flex-col gap-8 md:w-1/2">
|
|
40
|
+
{#if description}
|
|
41
|
+
<p class="text-sm md:text-base text-gray-600 {descriptionClass}">
|
|
42
|
+
{description}
|
|
43
|
+
</p>
|
|
44
|
+
{/if}
|
|
45
|
+
|
|
46
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
|
47
|
+
{#each features as feature, i (i)}
|
|
48
|
+
<div class="flex flex-col gap-3">
|
|
49
|
+
<h3 class="text-lg font-bold">{feature.title}</h3>
|
|
50
|
+
{#if feature.description}
|
|
51
|
+
<p class="text-sm text-gray-500">{feature.description}</p>
|
|
52
|
+
{/if}
|
|
53
|
+
{#if feature.link}
|
|
54
|
+
<a
|
|
55
|
+
href={feature.link}
|
|
56
|
+
class="inline-flex items-center gap-2 text-sm font-semibold mt-2 hover:opacity-80 transition-opacity"
|
|
57
|
+
>
|
|
58
|
+
{feature.linkLabel ?? "Learn More"}
|
|
59
|
+
<HugeiconsIcon icon={ArrowRight01Icon} class="size-4" />
|
|
60
|
+
</a>
|
|
61
|
+
{/if}
|
|
62
|
+
</div>
|
|
63
|
+
{/each}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|