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.
Files changed (121) hide show
  1. package/dist/components/pages/security/auth/PageAuth01.svelte +65 -0
  2. package/dist/components/pages/security/auth/PageAuth01.svelte.d.ts +17 -0
  3. package/dist/components/pages/security/auth/PageAuth02.svelte +45 -0
  4. package/dist/components/pages/security/auth/PageAuth02.svelte.d.ts +17 -0
  5. package/dist/components/pages/security/auth/PageAuth03.svelte +45 -0
  6. package/dist/components/pages/security/auth/PageAuth03.svelte.d.ts +17 -0
  7. package/dist/components/pages/security/login/01/ui/PageLogin01.svelte +15 -40
  8. package/dist/components/pages/security/login/02/ui/LoginCard02.svelte +15 -4
  9. package/dist/components/pages/security/login/02/ui/PageLogin02.svelte +15 -20
  10. package/dist/components/pages/security/login/03/ui/LoginCard03.svelte +34 -16
  11. package/dist/components/pages/security/login/03/ui/PageLogin03.svelte +15 -20
  12. package/dist/components/pages/security/register/01/ui/PageRegister01.svelte +64 -0
  13. package/dist/components/pages/security/register/01/ui/PageRegister01.svelte.d.ts +3 -0
  14. package/dist/components/pages/security/register/01/ui/RegisterCard.svelte +100 -0
  15. package/dist/components/pages/security/register/01/ui/RegisterCard.svelte.d.ts +12 -0
  16. package/dist/components/pages/security/register/02/ui/PageRegister02.svelte +76 -0
  17. package/dist/components/pages/security/register/02/ui/PageRegister02.svelte.d.ts +4 -0
  18. package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte +98 -0
  19. package/dist/components/pages/security/register/02/ui/RegisterCard02.svelte.d.ts +12 -0
  20. package/dist/components/pages/security/register/03/ui/PageRegister03.svelte +76 -0
  21. package/dist/components/pages/security/register/03/ui/PageRegister03.svelte.d.ts +4 -0
  22. package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte +103 -0
  23. package/dist/components/pages/security/register/03/ui/RegisterCard03.svelte.d.ts +12 -0
  24. package/dist/components/pages/security/register/FormRegister.svelte +120 -0
  25. package/dist/components/pages/security/register/FormRegister.svelte.d.ts +8 -0
  26. package/dist/components/pages/security/register/PageRegister.svelte +30 -0
  27. package/dist/components/pages/security/register/PageRegister.svelte.d.ts +8 -0
  28. package/dist/components/pages/security/register/types.d.ts +51 -0
  29. package/dist/components/pages/security/register/types.js +1 -0
  30. package/dist/components/ui/form/Form.svelte +171 -0
  31. package/dist/components/ui/form/Form.svelte.d.ts +4 -0
  32. package/dist/components/ui/form/types.d.ts +78 -0
  33. package/dist/components/ui/form/types.js +1 -0
  34. package/dist/components/ui/form/ui/input-badges.svelte +214 -0
  35. package/dist/components/ui/form/ui/input-badges.svelte.d.ts +14 -0
  36. package/dist/components/ui/form/ui/input-checkbox.svelte +86 -0
  37. package/dist/components/ui/form/ui/input-checkbox.svelte.d.ts +17 -0
  38. package/dist/components/ui/form/ui/input-radio.svelte +82 -0
  39. package/dist/components/ui/form/ui/input-radio.svelte.d.ts +17 -0
  40. package/dist/components/ui/form/ui/input-select.svelte +195 -0
  41. package/dist/components/ui/form/ui/input-select.svelte.d.ts +17 -0
  42. package/dist/components/ui/form/ui/input-textarea.svelte +61 -0
  43. package/dist/components/ui/form/ui/input-textarea.svelte.d.ts +13 -0
  44. package/dist/components/ui/form/ui/input-toggle.svelte +89 -0
  45. package/dist/components/ui/form/ui/input-toggle.svelte.d.ts +11 -0
  46. package/dist/components/ui/modal/badge/index.d.ts +3 -0
  47. package/dist/components/ui/modal/badge/index.js +2 -0
  48. package/dist/components/ui/modal/badge/types.d.ts +35 -0
  49. package/dist/components/ui/modal/badge/types.js +1 -0
  50. package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte +87 -0
  51. package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte.d.ts +4 -0
  52. package/dist/components/ui/modal/core/index.d.ts +3 -0
  53. package/dist/components/ui/modal/core/index.js +2 -0
  54. package/dist/components/ui/modal/core/types.d.ts +84 -0
  55. package/dist/components/ui/modal/core/types.js +1 -0
  56. package/dist/components/ui/modal/core/ui/ModalCore.svelte +132 -0
  57. package/dist/components/ui/modal/core/ui/ModalCore.svelte.d.ts +4 -0
  58. package/dist/components/ui/modal/feedback/index.d.ts +3 -0
  59. package/dist/components/ui/modal/feedback/index.js +2 -0
  60. package/dist/components/ui/modal/feedback/types.d.ts +32 -0
  61. package/dist/components/ui/modal/feedback/types.js +1 -0
  62. package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte +135 -0
  63. package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte.d.ts +4 -0
  64. package/dist/components/ui/modal/form/index.d.ts +3 -0
  65. package/dist/components/ui/modal/form/index.js +2 -0
  66. package/dist/components/ui/modal/form/types.d.ts +38 -0
  67. package/dist/components/ui/modal/form/types.js +1 -0
  68. package/dist/components/ui/modal/form/ui/ModalForm.svelte +67 -0
  69. package/dist/components/ui/modal/form/ui/ModalForm.svelte.d.ts +4 -0
  70. package/dist/components/ui/modal/grid/index.d.ts +5 -0
  71. package/dist/components/ui/modal/grid/index.js +4 -0
  72. package/dist/components/ui/modal/grid/types.d.ts +51 -0
  73. package/dist/components/ui/modal/grid/types.js +1 -0
  74. package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte +83 -0
  75. package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte.d.ts +4 -0
  76. package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte +146 -0
  77. package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte.d.ts +4 -0
  78. package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte +60 -0
  79. package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte.d.ts +4 -0
  80. package/dist/components/ui/modal/map/index.d.ts +3 -0
  81. package/dist/components/ui/modal/map/index.js +2 -0
  82. package/dist/components/ui/modal/map/types.d.ts +48 -0
  83. package/dist/components/ui/modal/map/types.js +1 -0
  84. package/dist/components/ui/modal/map/ui/ModalMap.svelte +187 -0
  85. package/dist/components/ui/modal/map/ui/ModalMap.svelte.d.ts +4 -0
  86. package/dist/components/ui/modal/notification/index.d.ts +3 -0
  87. package/dist/components/ui/modal/notification/index.js +2 -0
  88. package/dist/components/ui/modal/notification/types.d.ts +26 -0
  89. package/dist/components/ui/modal/notification/types.js +1 -0
  90. package/dist/components/ui/modal/notification/ui/ModalNotification.svelte +65 -0
  91. package/dist/components/ui/modal/notification/ui/ModalNotification.svelte.d.ts +4 -0
  92. package/dist/components/ui/modal/share/index.d.ts +3 -0
  93. package/dist/components/ui/modal/share/index.js +2 -0
  94. package/dist/components/ui/modal/share/types.d.ts +47 -0
  95. package/dist/components/ui/modal/share/types.js +1 -0
  96. package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte +156 -0
  97. package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte.d.ts +4 -0
  98. package/dist/components/ui/modal/upload/index.d.ts +3 -0
  99. package/dist/components/ui/modal/upload/index.js +2 -0
  100. package/dist/components/ui/modal/upload/types.d.ts +79 -0
  101. package/dist/components/ui/modal/upload/types.js +1 -0
  102. package/dist/components/ui/modal/upload/ui/ModalUpload.svelte +268 -0
  103. package/dist/components/ui/modal/upload/ui/ModalUpload.svelte.d.ts +4 -0
  104. package/dist/components/ui/section/Section01.svelte +90 -0
  105. package/dist/components/ui/section/Section01.svelte.d.ts +4 -0
  106. package/dist/components/ui/section/Section02.svelte +68 -0
  107. package/dist/components/ui/section/Section02.svelte.d.ts +4 -0
  108. package/dist/components/ui/section/Section03.svelte +52 -0
  109. package/dist/components/ui/section/Section03.svelte.d.ts +4 -0
  110. package/dist/components/ui/section/index.d.ts +4 -0
  111. package/dist/components/ui/section/index.js +3 -0
  112. package/dist/components/ui/section/types.d.ts +46 -0
  113. package/dist/components/ui/section/types.js +1 -0
  114. package/dist/docs/DocsSidebar.svelte +58 -1
  115. package/dist/docs/components.d.ts +1 -1
  116. package/dist/docs/components.js +968 -0
  117. package/dist/globals.css +360 -0
  118. package/dist/index.d.ts +185 -0
  119. package/dist/index.js +185 -0
  120. package/dist/types/index.d.ts +53 -0
  121. 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,4 @@
1
+ import type { ModalShareSelectionProps } from "../types";
2
+ declare const ModalShareSelection: import("svelte").Component<ModalShareSelectionProps, {}, "isOpen">;
3
+ type ModalShareSelection = ReturnType<typeof ModalShareSelection>;
4
+ export default ModalShareSelection;
@@ -0,0 +1,3 @@
1
+ import Root from "./ui/ModalUpload.svelte";
2
+ export { Root, Root as ModalUpload, };
3
+ export type { ModalUploadProps, UploadFile, UploadFileStatus } from "./types";
@@ -0,0 +1,2 @@
1
+ import Root from "./ui/ModalUpload.svelte";
2
+ export { Root, Root as ModalUpload, };
@@ -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,4 @@
1
+ import type { ModalUploadProps } from "../types";
2
+ declare const ModalUpload: import("svelte").Component<ModalUploadProps, {}, "files" | "isOpen">;
3
+ type ModalUpload = ReturnType<typeof ModalUpload>;
4
+ export default ModalUpload;
@@ -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,4 @@
1
+ import type { Section01Props } from "./types";
2
+ declare const Section01: import("svelte").Component<Section01Props, {}, "">;
3
+ type Section01 = ReturnType<typeof Section01>;
4
+ export default Section01;
@@ -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>