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.
Files changed (93) hide show
  1. package/dist/components/pages/security/login/02/ui/LoginCard02.svelte +15 -4
  2. package/dist/components/pages/security/login/03/ui/LoginCard03.svelte +34 -16
  3. package/dist/components/ui/form/Form.svelte +171 -0
  4. package/dist/components/ui/form/Form.svelte.d.ts +4 -0
  5. package/dist/components/ui/form/types.d.ts +78 -0
  6. package/dist/components/ui/form/types.js +1 -0
  7. package/dist/components/ui/form/ui/input-badges.svelte +214 -0
  8. package/dist/components/ui/form/ui/input-badges.svelte.d.ts +14 -0
  9. package/dist/components/ui/form/ui/input-checkbox.svelte +86 -0
  10. package/dist/components/ui/form/ui/input-checkbox.svelte.d.ts +17 -0
  11. package/dist/components/ui/form/ui/input-radio.svelte +82 -0
  12. package/dist/components/ui/form/ui/input-radio.svelte.d.ts +17 -0
  13. package/dist/components/ui/form/ui/input-select.svelte +195 -0
  14. package/dist/components/ui/form/ui/input-select.svelte.d.ts +17 -0
  15. package/dist/components/ui/form/ui/input-textarea.svelte +61 -0
  16. package/dist/components/ui/form/ui/input-textarea.svelte.d.ts +13 -0
  17. package/dist/components/ui/form/ui/input-toggle.svelte +89 -0
  18. package/dist/components/ui/form/ui/input-toggle.svelte.d.ts +11 -0
  19. package/dist/components/ui/modal/badge/index.d.ts +3 -0
  20. package/dist/components/ui/modal/badge/index.js +2 -0
  21. package/dist/components/ui/modal/badge/types.d.ts +35 -0
  22. package/dist/components/ui/modal/badge/types.js +1 -0
  23. package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte +87 -0
  24. package/dist/components/ui/modal/badge/ui/ModalBadgeSelection.svelte.d.ts +4 -0
  25. package/dist/components/ui/modal/core/index.d.ts +3 -0
  26. package/dist/components/ui/modal/core/index.js +2 -0
  27. package/dist/components/ui/modal/core/types.d.ts +82 -0
  28. package/dist/components/ui/modal/core/types.js +1 -0
  29. package/dist/components/ui/modal/core/ui/ModalCore.svelte +129 -0
  30. package/dist/components/ui/modal/core/ui/ModalCore.svelte.d.ts +4 -0
  31. package/dist/components/ui/modal/feedback/index.d.ts +3 -0
  32. package/dist/components/ui/modal/feedback/index.js +2 -0
  33. package/dist/components/ui/modal/feedback/types.d.ts +32 -0
  34. package/dist/components/ui/modal/feedback/types.js +1 -0
  35. package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte +135 -0
  36. package/dist/components/ui/modal/feedback/ui/ModalFeedback.svelte.d.ts +4 -0
  37. package/dist/components/ui/modal/form/index.d.ts +3 -0
  38. package/dist/components/ui/modal/form/index.js +2 -0
  39. package/dist/components/ui/modal/form/types.d.ts +38 -0
  40. package/dist/components/ui/modal/form/types.js +1 -0
  41. package/dist/components/ui/modal/form/ui/ModalForm.svelte +67 -0
  42. package/dist/components/ui/modal/form/ui/ModalForm.svelte.d.ts +4 -0
  43. package/dist/components/ui/modal/grid/index.d.ts +5 -0
  44. package/dist/components/ui/modal/grid/index.js +4 -0
  45. package/dist/components/ui/modal/grid/types.d.ts +51 -0
  46. package/dist/components/ui/modal/grid/types.js +1 -0
  47. package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte +83 -0
  48. package/dist/components/ui/modal/grid/ui/01/ModalGridSelection01.svelte.d.ts +4 -0
  49. package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte +142 -0
  50. package/dist/components/ui/modal/grid/ui/02/ModalGridSelection02.svelte.d.ts +4 -0
  51. package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte +60 -0
  52. package/dist/components/ui/modal/grid/ui/ModalGridSelection.svelte.d.ts +4 -0
  53. package/dist/components/ui/modal/map/index.d.ts +3 -0
  54. package/dist/components/ui/modal/map/index.js +2 -0
  55. package/dist/components/ui/modal/map/types.d.ts +48 -0
  56. package/dist/components/ui/modal/map/types.js +1 -0
  57. package/dist/components/ui/modal/map/ui/ModalMap.svelte +187 -0
  58. package/dist/components/ui/modal/map/ui/ModalMap.svelte.d.ts +4 -0
  59. package/dist/components/ui/modal/notification/index.d.ts +3 -0
  60. package/dist/components/ui/modal/notification/index.js +2 -0
  61. package/dist/components/ui/modal/notification/types.d.ts +26 -0
  62. package/dist/components/ui/modal/notification/types.js +1 -0
  63. package/dist/components/ui/modal/notification/ui/ModalNotification.svelte +65 -0
  64. package/dist/components/ui/modal/notification/ui/ModalNotification.svelte.d.ts +4 -0
  65. package/dist/components/ui/modal/share/index.d.ts +3 -0
  66. package/dist/components/ui/modal/share/index.js +2 -0
  67. package/dist/components/ui/modal/share/types.d.ts +32 -0
  68. package/dist/components/ui/modal/share/types.js +1 -0
  69. package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte +103 -0
  70. package/dist/components/ui/modal/share/ui/ModalShareSelection.svelte.d.ts +4 -0
  71. package/dist/components/ui/modal/upload/index.d.ts +3 -0
  72. package/dist/components/ui/modal/upload/index.js +2 -0
  73. package/dist/components/ui/modal/upload/types.d.ts +79 -0
  74. package/dist/components/ui/modal/upload/types.js +1 -0
  75. package/dist/components/ui/modal/upload/ui/ModalUpload.svelte +268 -0
  76. package/dist/components/ui/modal/upload/ui/ModalUpload.svelte.d.ts +4 -0
  77. package/dist/components/ui/section/Section01.svelte +90 -0
  78. package/dist/components/ui/section/Section01.svelte.d.ts +4 -0
  79. package/dist/components/ui/section/Section02.svelte +68 -0
  80. package/dist/components/ui/section/Section02.svelte.d.ts +4 -0
  81. package/dist/components/ui/section/Section03.svelte +52 -0
  82. package/dist/components/ui/section/Section03.svelte.d.ts +4 -0
  83. package/dist/components/ui/section/index.d.ts +4 -0
  84. package/dist/components/ui/section/index.js +3 -0
  85. package/dist/components/ui/section/types.d.ts +46 -0
  86. package/dist/components/ui/section/types.js +1 -0
  87. package/dist/docs/components.d.ts +1 -1
  88. package/dist/docs/components.js +882 -0
  89. package/dist/globals.css +443 -0
  90. package/dist/index.d.ts +169 -0
  91. package/dist/index.js +169 -0
  92. package/dist/types/index.d.ts +46 -0
  93. 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,4 @@
1
+ import type { ModalNotificationProps } from "../types";
2
+ declare const ModalNotification: import("svelte").Component<ModalNotificationProps, {}, "isOpen">;
3
+ type ModalNotification = ReturnType<typeof ModalNotification>;
4
+ export default ModalNotification;
@@ -0,0 +1,3 @@
1
+ import Root from "./ui/ModalShareSelection.svelte";
2
+ export { Root, Root as ModalShareSelection, };
3
+ export type { ModelSocialShare, ModalShareSelectionProps } from "./types";
@@ -0,0 +1,2 @@
1
+ import Root from "./ui/ModalShareSelection.svelte";
2
+ export { Root, Root as ModalShareSelection, };
@@ -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,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;