negodesign 0.0.74 → 0.0.75
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/login/02/ui/LoginCard02.svelte +15 -4
- package/dist/components/pages/security/login/03/ui/LoginCard03.svelte +34 -16
- 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 +82 -0
- package/dist/components/ui/modal/core/types.js +1 -0
- package/dist/components/ui/modal/core/ui/ModalCore.svelte +129 -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 +142 -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 +32 -0
- package/dist/components/ui/modal/share/types.js +1 -0
- package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte +103 -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/components.d.ts +1 -1
- package/dist/docs/components.js +882 -0
- package/dist/globals.css +443 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.js +169 -0
- package/dist/types/index.d.ts +46 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../../../utils";
|
|
3
|
+
import ModalCore from "../../core/ui/ModalCore.svelte";
|
|
4
|
+
import type { ModalNotificationProps } from "../types";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
placeholder = "Your email address",
|
|
10
|
+
buttonText = "Sign me up",
|
|
11
|
+
buttonLoadingText = "Signing up...",
|
|
12
|
+
isLoading = false,
|
|
13
|
+
isOpen = $bindable(false),
|
|
14
|
+
onSubmit,
|
|
15
|
+
onClose,
|
|
16
|
+
class: className,
|
|
17
|
+
}: ModalNotificationProps = $props();
|
|
18
|
+
|
|
19
|
+
let email = $state("");
|
|
20
|
+
|
|
21
|
+
function handleSubmit() {
|
|
22
|
+
if (!email.trim() || isLoading) return;
|
|
23
|
+
onSubmit?.(email);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
27
|
+
if (e.key === "Enter") handleSubmit();
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<ModalCore
|
|
32
|
+
bind:isOpen
|
|
33
|
+
{title}
|
|
34
|
+
showProgress={false}
|
|
35
|
+
showBack={false}
|
|
36
|
+
{onClose}
|
|
37
|
+
class={cn("max-w-lg", className)}
|
|
38
|
+
>
|
|
39
|
+
{#snippet content()}
|
|
40
|
+
{#if description}
|
|
41
|
+
<p class="text-sm text-muted-foreground">{description}</p>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
<div class="mt-6 flex items-center gap-3">
|
|
45
|
+
<input
|
|
46
|
+
type="email"
|
|
47
|
+
{placeholder}
|
|
48
|
+
bind:value={email}
|
|
49
|
+
onkeydown={handleKeydown}
|
|
50
|
+
class="flex-1 rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/20"
|
|
51
|
+
/>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
disabled={isLoading || !email.trim()}
|
|
55
|
+
class={cn(
|
|
56
|
+
"shrink-0 rounded-lg bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition-all",
|
|
57
|
+
"hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
58
|
+
)}
|
|
59
|
+
onclick={handleSubmit}
|
|
60
|
+
>
|
|
61
|
+
{isLoading ? buttonLoadingText : buttonText}
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
{/snippet}
|
|
65
|
+
</ModalCore>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ModelSocialShare } from "../core/types";
|
|
2
|
+
export type { ModelSocialShare } from "../core/types";
|
|
3
|
+
/**
|
|
4
|
+
* Props do componente ModalShareSelection — modal de partilha em redes sociais.
|
|
5
|
+
* Terceira variação do sistema de modais do NegoDesign.
|
|
6
|
+
* @property title - Título principal do modal (ex: "Share this design").
|
|
7
|
+
* @property subtitle - Subtítulo/descrição abaixo do título.
|
|
8
|
+
* @property networks - Lista de redes sociais para partilhar.
|
|
9
|
+
* @property linkLabel - Texto acima do input de link (ex: "Or copy link").
|
|
10
|
+
* @property linkValue - URL a ser partilhada/copiada.
|
|
11
|
+
* @property copyText - Texto do botão de copiar.
|
|
12
|
+
* @property copiedText - Texto exibido após copiar com sucesso.
|
|
13
|
+
* @property isOpen - Controla a visibilidade do modal.
|
|
14
|
+
* @property onShare - Chamado ao clicar numa rede social.
|
|
15
|
+
* @property onCopy - Chamado ao clicar no botão copiar.
|
|
16
|
+
* @property onClose - Chamado ao fechar o modal.
|
|
17
|
+
* @property class - Classe CSS extra no container do modal.
|
|
18
|
+
*/
|
|
19
|
+
export type ModalShareSelectionProps = {
|
|
20
|
+
title: string;
|
|
21
|
+
subtitle?: string;
|
|
22
|
+
networks: ModelSocialShare[];
|
|
23
|
+
linkLabel?: string;
|
|
24
|
+
linkValue?: string;
|
|
25
|
+
copyText?: string;
|
|
26
|
+
copiedText?: string;
|
|
27
|
+
isOpen?: boolean;
|
|
28
|
+
onShare?: (network: ModelSocialShare) => void;
|
|
29
|
+
onCopy?: (link: string) => void;
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
class?: string;
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
let {
|
|
8
|
+
title,
|
|
9
|
+
subtitle,
|
|
10
|
+
networks = [],
|
|
11
|
+
linkLabel = "Or copy link",
|
|
12
|
+
linkValue = "",
|
|
13
|
+
copyText = "Copy",
|
|
14
|
+
copiedText = "Copied!",
|
|
15
|
+
isOpen = $bindable(false),
|
|
16
|
+
onShare,
|
|
17
|
+
onCopy,
|
|
18
|
+
onClose,
|
|
19
|
+
class: className,
|
|
20
|
+
}: ModalShareSelectionProps = $props();
|
|
21
|
+
|
|
22
|
+
let copied = $state(false);
|
|
23
|
+
|
|
24
|
+
function handleCopy() {
|
|
25
|
+
navigator.clipboard.writeText(linkValue).then(() => {
|
|
26
|
+
copied = true;
|
|
27
|
+
onCopy?.(linkValue);
|
|
28
|
+
setTimeout(() => (copied = false), 2000);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<ModalCore
|
|
34
|
+
bind:isOpen
|
|
35
|
+
{title}
|
|
36
|
+
{subtitle}
|
|
37
|
+
showProgress={false}
|
|
38
|
+
showBack={false}
|
|
39
|
+
{onClose}
|
|
40
|
+
class={cn("max-w-lg", className)}
|
|
41
|
+
>
|
|
42
|
+
{#snippet content()}
|
|
43
|
+
<!-- Social Networks -->
|
|
44
|
+
<div class="flex flex-wrap items-center justify-center gap-4">
|
|
45
|
+
{#each networks as network (network.name)}
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
class="group flex flex-col items-center gap-2"
|
|
49
|
+
onclick={() => onShare?.(network)}
|
|
50
|
+
>
|
|
51
|
+
<div
|
|
52
|
+
class={cn(
|
|
53
|
+
"flex size-14 items-center justify-center rounded-full border-2 border-border transition-all duration-200",
|
|
54
|
+
"hover:scale-110 hover:shadow-md"
|
|
55
|
+
)}
|
|
56
|
+
style={network.color ? `border-color: ${network.color}30` : ""}
|
|
57
|
+
>
|
|
58
|
+
{#if typeof network.icon === "string"}
|
|
59
|
+
<i
|
|
60
|
+
class="{network.icon} text-2xl transition-colors"
|
|
61
|
+
style={network.color ? `color: ${network.color}` : ""}
|
|
62
|
+
></i>
|
|
63
|
+
{:else}
|
|
64
|
+
<HugeiconsIcon
|
|
65
|
+
icon={network.icon}
|
|
66
|
+
class="size-6 transition-colors"
|
|
67
|
+
style={network.color ? `color: ${network.color}` : ""}
|
|
68
|
+
/>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
71
|
+
<span class="text-xs text-muted-foreground">{network.name}</span>
|
|
72
|
+
</button>
|
|
73
|
+
{/each}
|
|
74
|
+
</div>
|
|
75
|
+
{/snippet}
|
|
76
|
+
|
|
77
|
+
{#snippet footer()}
|
|
78
|
+
<!-- Copy Link -->
|
|
79
|
+
<div>
|
|
80
|
+
<p class="mb-3 text-sm font-medium text-muted-foreground">{linkLabel}</p>
|
|
81
|
+
<div class="flex items-center gap-2">
|
|
82
|
+
<input
|
|
83
|
+
type="text"
|
|
84
|
+
readonly
|
|
85
|
+
value={linkValue}
|
|
86
|
+
class="flex-1 rounded-lg border border-border bg-muted px-4 py-2.5 text-sm text-foreground outline-none"
|
|
87
|
+
/>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class={cn(
|
|
91
|
+
"shrink-0 rounded-lg px-5 py-2.5 text-sm font-semibold transition-colors",
|
|
92
|
+
copied
|
|
93
|
+
? "bg-green-500 text-white"
|
|
94
|
+
: "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
95
|
+
)}
|
|
96
|
+
onclick={handleCopy}
|
|
97
|
+
>
|
|
98
|
+
{copied ? copiedText : copyText}
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
{/snippet}
|
|
103
|
+
</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>
|