@makolabs/ripple 1.14.0 → 2.1.0
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/elements/file-upload/FileUpload.svelte +252 -18
- package/dist/elements/file-upload/FileUpload.svelte.d.ts +1 -1
- package/dist/elements/file-upload/file-upload-types.d.ts +74 -107
- package/dist/index.d.ts +2 -3
- package/dist/index.js +0 -1
- package/dist/layout/activity-list/ActivityList.svelte +109 -36
- package/dist/layout/activity-list/activity-list-types.d.ts +82 -2
- package/dist/layout/activity-list/activity-list.d.ts +124 -0
- package/dist/layout/activity-list/activity-list.js +98 -9
- package/package.json +1 -1
- package/dist/elements/file-upload/MultiFileUpload.svelte +0 -274
- package/dist/elements/file-upload/MultiFileUpload.svelte.d.ts +0 -4
|
@@ -1,36 +1,111 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../../helper/cls.js';
|
|
3
3
|
import { fileUpload } from './file-upload.js';
|
|
4
|
-
import
|
|
4
|
+
import Button from '../../button/Button.svelte';
|
|
5
|
+
import Spinner from '../spinner/Spinner.svelte';
|
|
6
|
+
import { Size, Color } from '../../variants.js';
|
|
7
|
+
import type { FileUploadProps, StagedFile } from '../../index.js';
|
|
5
8
|
|
|
6
9
|
let {
|
|
7
10
|
allowedMimeTypes = [],
|
|
8
|
-
maxFiles =
|
|
11
|
+
maxFiles = 1,
|
|
9
12
|
maxSize,
|
|
10
13
|
size = 'xl',
|
|
11
14
|
class: className = '',
|
|
12
15
|
dropzoneClass = '',
|
|
13
16
|
id = 'file-upload',
|
|
14
17
|
onfiles,
|
|
15
|
-
uploadContent
|
|
18
|
+
uploadContent,
|
|
19
|
+
files = $bindable<StagedFile[]>([]),
|
|
20
|
+
clearAfterUpload = true,
|
|
21
|
+
uploadButtonLabel = 'Upload All',
|
|
22
|
+
clearButtonLabel = 'Clear All',
|
|
23
|
+
filesListLabel = 'Selected files',
|
|
24
|
+
listMaxHeight = 'max-h-64',
|
|
25
|
+
header
|
|
16
26
|
}: FileUploadProps = $props();
|
|
17
27
|
|
|
18
28
|
const slots = $derived(fileUpload({ size }));
|
|
19
29
|
|
|
30
|
+
/** True when the component operates as a multi-file staging uploader. */
|
|
31
|
+
const isMulti = $derived(maxFiles > 1);
|
|
32
|
+
|
|
33
|
+
/** Hard-disabled dropzone (maxFiles <= 0). */
|
|
20
34
|
const disabled = $derived(maxFiles <= 0);
|
|
21
35
|
|
|
36
|
+
/** Soft-disabled dropzone when the staging list is at capacity. */
|
|
37
|
+
const dropzoneFull = $derived(isMulti && files.length >= maxFiles);
|
|
38
|
+
|
|
39
|
+
/** Effective "can drop files" flag. */
|
|
40
|
+
const dropzoneEnabled = $derived(!disabled && !dropzoneFull);
|
|
41
|
+
|
|
22
42
|
let isDragging = $state(false);
|
|
23
43
|
let inputRef: HTMLInputElement;
|
|
24
44
|
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
27
|
-
|
|
45
|
+
function makeId(): string {
|
|
46
|
+
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
47
|
+
return crypto.randomUUID();
|
|
48
|
+
}
|
|
49
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleIncoming(newFiles: FileList | File[]) {
|
|
53
|
+
if (!dropzoneEnabled) return;
|
|
54
|
+
const arr = Array.from(newFiles);
|
|
55
|
+
if (!isMulti) {
|
|
56
|
+
// Single-file mode: fire-and-forget, same as pre-v2 FileUpload.
|
|
57
|
+
onfiles?.(arr);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Multi-file mode: cap and stage
|
|
61
|
+
const room = Math.max(0, maxFiles - files.length);
|
|
62
|
+
const accepted = arr.slice(0, room);
|
|
63
|
+
const toAdd: StagedFile[] = accepted.map((file) => ({
|
|
64
|
+
id: makeId(),
|
|
65
|
+
file,
|
|
66
|
+
status: 'ready' as const
|
|
67
|
+
}));
|
|
68
|
+
files = [...files, ...toAdd];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleRemove(fileId: string) {
|
|
72
|
+
files = files.filter((f) => f.id !== fileId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleClearAll() {
|
|
76
|
+
files = [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const readyCount = $derived(files.filter((f) => !f.status || f.status === 'ready').length);
|
|
80
|
+
const hasAnyFile = $derived(files.length > 0);
|
|
81
|
+
const anyUploading = $derived(files.some((f) => f.status === 'uploading'));
|
|
82
|
+
const canUpload = $derived(readyCount > 0 && !anyUploading && !!onfiles);
|
|
83
|
+
|
|
84
|
+
function handleUploadAll() {
|
|
85
|
+
if (!onfiles) return;
|
|
86
|
+
const rawFiles = files.filter((f) => !f.status || f.status === 'ready').map((f) => f.file);
|
|
87
|
+
if (rawFiles.length === 0) return;
|
|
88
|
+
onfiles(rawFiles);
|
|
89
|
+
if (clearAfterUpload) {
|
|
90
|
+
files = [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleRetry(fileId: string) {
|
|
95
|
+
const target = files.find((f) => f.id === fileId);
|
|
96
|
+
if (!target || !onfiles) return;
|
|
97
|
+
files = files.map((f) =>
|
|
98
|
+
f.id === fileId
|
|
99
|
+
? { ...f, status: 'ready' as const, error: undefined, progress: undefined }
|
|
100
|
+
: f
|
|
101
|
+
);
|
|
102
|
+
onfiles([target.file]);
|
|
28
103
|
}
|
|
29
104
|
|
|
30
105
|
function handleDragEnter(e: DragEvent) {
|
|
31
106
|
e.preventDefault();
|
|
32
107
|
e.stopPropagation();
|
|
33
|
-
if (
|
|
108
|
+
if (dropzoneEnabled) isDragging = true;
|
|
34
109
|
}
|
|
35
110
|
|
|
36
111
|
function handleDragLeave(e: DragEvent) {
|
|
@@ -43,15 +118,15 @@
|
|
|
43
118
|
e.preventDefault();
|
|
44
119
|
e.stopPropagation();
|
|
45
120
|
isDragging = false;
|
|
46
|
-
if (
|
|
47
|
-
|
|
121
|
+
if (dropzoneEnabled && e.dataTransfer?.files) {
|
|
122
|
+
handleIncoming(e.dataTransfer.files);
|
|
48
123
|
}
|
|
49
124
|
}
|
|
50
125
|
|
|
51
126
|
function handleInputChange(e: Event) {
|
|
52
127
|
const input = e.target as HTMLInputElement;
|
|
53
128
|
if (input.files) {
|
|
54
|
-
|
|
129
|
+
handleIncoming(input.files);
|
|
55
130
|
input.value = '';
|
|
56
131
|
}
|
|
57
132
|
}
|
|
@@ -59,7 +134,7 @@
|
|
|
59
134
|
function handleDragOver(e: DragEvent) {
|
|
60
135
|
e.preventDefault();
|
|
61
136
|
e.stopPropagation();
|
|
62
|
-
if (
|
|
137
|
+
if (dropzoneEnabled) isDragging = true;
|
|
63
138
|
}
|
|
64
139
|
|
|
65
140
|
function formatFileSize(bytes: number): string {
|
|
@@ -71,15 +146,19 @@
|
|
|
71
146
|
}
|
|
72
147
|
</script>
|
|
73
148
|
|
|
74
|
-
<div class={cn('w-full', className)}>
|
|
149
|
+
<div class={cn('flex w-full flex-col gap-4', className)}>
|
|
150
|
+
{#if header}
|
|
151
|
+
{@render header()}
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
75
154
|
<label
|
|
76
155
|
class={cn(
|
|
77
156
|
slots.dropzone(),
|
|
78
157
|
{
|
|
79
158
|
'border-primary-400 bg-primary-50': isDragging,
|
|
80
159
|
'border-default-200 bg-white': !isDragging,
|
|
81
|
-
'cursor-not-allowed opacity-50':
|
|
82
|
-
'hover:bg-default-50 cursor-pointer':
|
|
160
|
+
'cursor-not-allowed opacity-50': !dropzoneEnabled,
|
|
161
|
+
'hover:bg-default-50 cursor-pointer': dropzoneEnabled
|
|
83
162
|
},
|
|
84
163
|
dropzoneClass
|
|
85
164
|
)}
|
|
@@ -93,15 +172,14 @@
|
|
|
93
172
|
type="file"
|
|
94
173
|
bind:this={inputRef}
|
|
95
174
|
accept={allowedMimeTypes.join(',')}
|
|
96
|
-
multiple={maxFiles
|
|
97
|
-
{
|
|
175
|
+
multiple={maxFiles > 1}
|
|
176
|
+
disabled={!dropzoneEnabled}
|
|
98
177
|
class="hidden"
|
|
99
178
|
onchange={handleInputChange}
|
|
100
179
|
{id}
|
|
101
180
|
/>
|
|
102
181
|
|
|
103
182
|
<div class={slots.content()}>
|
|
104
|
-
<!-- Upload Icon -->
|
|
105
183
|
<div class={slots.iconWrapper()} data-fileupload-icon-wrapper="">
|
|
106
184
|
<svg xmlns="http://www.w3.org/2000/svg" class={slots.icon()} viewBox="0 0 24 24">
|
|
107
185
|
<path
|
|
@@ -111,7 +189,6 @@
|
|
|
111
189
|
</svg>
|
|
112
190
|
</div>
|
|
113
191
|
|
|
114
|
-
<!-- Text stack (wrapped so row layout in sm can place icon on the left and this block on the right) -->
|
|
115
192
|
<div class={slots.textBlock()}>
|
|
116
193
|
{#if !uploadContent}
|
|
117
194
|
<div class={slots.mainText()}>
|
|
@@ -135,4 +212,161 @@
|
|
|
135
212
|
</div>
|
|
136
213
|
</div>
|
|
137
214
|
</label>
|
|
215
|
+
|
|
216
|
+
{#if isMulti && hasAnyFile}
|
|
217
|
+
<div class="flex flex-col gap-2">
|
|
218
|
+
<div class="flex items-center justify-between">
|
|
219
|
+
<span class="text-default-700 text-sm font-medium">
|
|
220
|
+
{filesListLabel} ({files.length})
|
|
221
|
+
</span>
|
|
222
|
+
<div class="flex gap-2">
|
|
223
|
+
<Button
|
|
224
|
+
size={Size.XS}
|
|
225
|
+
variant="outline"
|
|
226
|
+
color={Color.DEFAULT}
|
|
227
|
+
onclick={handleClearAll}
|
|
228
|
+
disabled={anyUploading}
|
|
229
|
+
>
|
|
230
|
+
{clearButtonLabel}
|
|
231
|
+
</Button>
|
|
232
|
+
<Button
|
|
233
|
+
size={Size.XS}
|
|
234
|
+
color={Color.PRIMARY}
|
|
235
|
+
onclick={handleUploadAll}
|
|
236
|
+
disabled={!canUpload}
|
|
237
|
+
>
|
|
238
|
+
{uploadButtonLabel}{readyCount > 0 ? ` (${readyCount})` : ''}
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<ul
|
|
244
|
+
class={cn(
|
|
245
|
+
'border-default-200 divide-default-100 divide-y overflow-y-auto rounded-lg border',
|
|
246
|
+
listMaxHeight
|
|
247
|
+
)}
|
|
248
|
+
>
|
|
249
|
+
{#each files as stagedFile (stagedFile.id)}
|
|
250
|
+
{@const status = stagedFile.status ?? 'ready'}
|
|
251
|
+
<li
|
|
252
|
+
class={cn('flex items-center gap-3 px-3 py-2', status === 'success' && 'opacity-60')}
|
|
253
|
+
data-fileupload-row=""
|
|
254
|
+
data-status={status}
|
|
255
|
+
>
|
|
256
|
+
{#if status === 'ready'}
|
|
257
|
+
<span
|
|
258
|
+
class="bg-default-100 text-default-500 flex size-7 shrink-0 items-center justify-center rounded-full"
|
|
259
|
+
aria-hidden="true"
|
|
260
|
+
>
|
|
261
|
+
<svg class="size-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
262
|
+
<path
|
|
263
|
+
stroke-linecap="round"
|
|
264
|
+
stroke-linejoin="round"
|
|
265
|
+
stroke-width="2"
|
|
266
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
267
|
+
/>
|
|
268
|
+
</svg>
|
|
269
|
+
</span>
|
|
270
|
+
{:else if status === 'uploading'}
|
|
271
|
+
<div class="flex size-7 shrink-0 items-center justify-center">
|
|
272
|
+
<Spinner size={Size.SM} color={Color.PRIMARY} />
|
|
273
|
+
</div>
|
|
274
|
+
{:else if status === 'success'}
|
|
275
|
+
<span
|
|
276
|
+
class="bg-success-100 text-success-600 flex size-7 shrink-0 items-center justify-center rounded-full"
|
|
277
|
+
aria-hidden="true"
|
|
278
|
+
>
|
|
279
|
+
<svg class="size-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
280
|
+
<path
|
|
281
|
+
stroke-linecap="round"
|
|
282
|
+
stroke-linejoin="round"
|
|
283
|
+
stroke-width="3"
|
|
284
|
+
d="M5 13l4 4L19 7"
|
|
285
|
+
/>
|
|
286
|
+
</svg>
|
|
287
|
+
</span>
|
|
288
|
+
{:else if status === 'error'}
|
|
289
|
+
<span
|
|
290
|
+
class="bg-danger-100 text-danger-600 flex size-7 shrink-0 items-center justify-center rounded-full"
|
|
291
|
+
aria-hidden="true"
|
|
292
|
+
>
|
|
293
|
+
<svg class="size-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
294
|
+
<path
|
|
295
|
+
stroke-linecap="round"
|
|
296
|
+
stroke-linejoin="round"
|
|
297
|
+
stroke-width="3"
|
|
298
|
+
d="M6 18L18 6M6 6l12 12"
|
|
299
|
+
/>
|
|
300
|
+
</svg>
|
|
301
|
+
</span>
|
|
302
|
+
{/if}
|
|
303
|
+
|
|
304
|
+
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
305
|
+
<span class="text-default-900 truncate text-sm font-medium">
|
|
306
|
+
{stagedFile.file.name}
|
|
307
|
+
</span>
|
|
308
|
+
<span class="text-default-500 text-xs">
|
|
309
|
+
{formatFileSize(stagedFile.file.size)}
|
|
310
|
+
{#if stagedFile.error}
|
|
311
|
+
<span class="text-danger-600"> · {stagedFile.error}</span>
|
|
312
|
+
{/if}
|
|
313
|
+
</span>
|
|
314
|
+
{#if status === 'uploading' && stagedFile.progress !== undefined}
|
|
315
|
+
<div class="bg-default-100 mt-1 h-1 w-full overflow-hidden rounded-full">
|
|
316
|
+
<div
|
|
317
|
+
data-fileupload-progress=""
|
|
318
|
+
class="bg-primary-500 h-full transition-all duration-200"
|
|
319
|
+
style="width: {Math.max(0, Math.min(100, stagedFile.progress))}%"
|
|
320
|
+
></div>
|
|
321
|
+
</div>
|
|
322
|
+
{/if}
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{#if status === 'error'}
|
|
326
|
+
<button
|
|
327
|
+
type="button"
|
|
328
|
+
onclick={() => handleRetry(stagedFile.id)}
|
|
329
|
+
class="text-default-500 hover:bg-primary-50 hover:text-primary-600 shrink-0 cursor-pointer rounded p-1 transition-colors"
|
|
330
|
+
aria-label="Retry upload for {stagedFile.file.name}"
|
|
331
|
+
title="Retry"
|
|
332
|
+
>
|
|
333
|
+
<svg
|
|
334
|
+
class="size-4"
|
|
335
|
+
fill="none"
|
|
336
|
+
viewBox="0 0 24 24"
|
|
337
|
+
stroke="currentColor"
|
|
338
|
+
stroke-width="2"
|
|
339
|
+
stroke-linecap="round"
|
|
340
|
+
stroke-linejoin="round"
|
|
341
|
+
>
|
|
342
|
+
<polyline points="23 4 23 10 17 10" />
|
|
343
|
+
<polyline points="1 20 1 14 7 14" />
|
|
344
|
+
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
345
|
+
</svg>
|
|
346
|
+
</button>
|
|
347
|
+
{/if}
|
|
348
|
+
|
|
349
|
+
{#if status !== 'uploading' && status !== 'success'}
|
|
350
|
+
<button
|
|
351
|
+
type="button"
|
|
352
|
+
onclick={() => handleRemove(stagedFile.id)}
|
|
353
|
+
class="text-default-400 hover:bg-default-100 hover:text-danger-500 shrink-0 cursor-pointer rounded p-1 transition-colors"
|
|
354
|
+
aria-label="Remove {stagedFile.file.name}"
|
|
355
|
+
title="Remove"
|
|
356
|
+
>
|
|
357
|
+
<svg class="size-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
358
|
+
<path
|
|
359
|
+
stroke-linecap="round"
|
|
360
|
+
stroke-linejoin="round"
|
|
361
|
+
stroke-width="2"
|
|
362
|
+
d="M6 18L18 6M6 6l12 12"
|
|
363
|
+
/>
|
|
364
|
+
</svg>
|
|
365
|
+
</button>
|
|
366
|
+
{/if}
|
|
367
|
+
</li>
|
|
368
|
+
{/each}
|
|
369
|
+
</ul>
|
|
370
|
+
</div>
|
|
371
|
+
{/if}
|
|
138
372
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { FileUploadProps } from '../../index.js';
|
|
2
|
-
declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "">;
|
|
2
|
+
declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "files">;
|
|
3
3
|
type FileUpload = ReturnType<typeof FileUpload>;
|
|
4
4
|
export default FileUpload;
|
|
@@ -1,93 +1,13 @@
|
|
|
1
1
|
import type { ClassValue } from 'tailwind-variants';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
export type FileUploadSize = 'xl' | 'md' | 'sm';
|
|
4
|
-
export interface FileUploadProps {
|
|
5
|
-
/**
|
|
6
|
-
* Array of allowed file MIME types or extensions
|
|
7
|
-
* @example ['image/jpeg', 'image/png']
|
|
8
|
-
*/
|
|
9
|
-
allowedMimeTypes?: string[];
|
|
10
|
-
/**
|
|
11
|
-
* Maximum file size in bytes
|
|
12
|
-
*/
|
|
13
|
-
maxSize?: number;
|
|
14
|
-
/**
|
|
15
|
-
* Maximum number of files that can be uploaded; maxFiles<=0 is disabled, maxFiles=1 is singular, maxFiles>1 is multiple
|
|
16
|
-
* @default 10
|
|
17
|
-
*/
|
|
18
|
-
maxFiles?: number;
|
|
19
|
-
/**
|
|
20
|
-
* Visual size variant.
|
|
21
|
-
*
|
|
22
|
-
* - `xl` (default) — hero square card, column layout, 48px padding +
|
|
23
|
-
* 96px circular icon. Use for empty-state pages or standalone upload
|
|
24
|
-
* dialogs where the dropzone is the main focus of the screen.
|
|
25
|
-
* - `md` — wide medium rectangle, row layout, icon-left + text-right,
|
|
26
|
-
* ~80-90px tall, full-width. Use as a prominent form section when
|
|
27
|
-
* the upload is a primary action alongside other fields.
|
|
28
|
-
* - `sm` — compact inline rectangle, row layout, ~56-60px tall. Behaves
|
|
29
|
-
* like a single form field; sits next to other inputs without
|
|
30
|
-
* dominating.
|
|
31
|
-
*
|
|
32
|
-
* @default 'xl'
|
|
33
|
-
*/
|
|
34
|
-
size?: FileUploadSize;
|
|
35
|
-
/**
|
|
36
|
-
* CSS class for the component container
|
|
37
|
-
*/
|
|
38
|
-
class?: ClassValue;
|
|
39
|
-
/**
|
|
40
|
-
* CSS class for the dropzone
|
|
41
|
-
*/
|
|
42
|
-
dropzoneClass?: ClassValue;
|
|
43
|
-
/**
|
|
44
|
-
* ID for the file input element
|
|
45
|
-
* @default 'file-upload'
|
|
46
|
-
*/
|
|
47
|
-
id?: string;
|
|
48
|
-
/**
|
|
49
|
-
* Callback when files are selected or dropped
|
|
50
|
-
*/
|
|
51
|
-
onfiles?: (files: FileList | File[]) => void;
|
|
52
|
-
/**
|
|
53
|
-
* Content to display when no files are uploaded
|
|
54
|
-
*/
|
|
55
|
-
uploadContent?: Snippet;
|
|
56
|
-
testId?: string;
|
|
57
|
-
}
|
|
58
|
-
export interface FilePreviewProps {
|
|
59
|
-
files: UploadedFile[];
|
|
60
|
-
ondelete?: (fileId: string, index: number) => void;
|
|
61
|
-
class?: ClassValue;
|
|
62
|
-
testId?: string;
|
|
63
|
-
}
|
|
64
|
-
export interface UploadedFile {
|
|
65
|
-
FileID: string;
|
|
66
|
-
OriginalFilename: string;
|
|
67
|
-
Size: number;
|
|
68
|
-
ContentType: string;
|
|
69
|
-
MD5Hash: string;
|
|
70
|
-
UploadTimestamp: string;
|
|
71
|
-
Expiry?: string;
|
|
72
|
-
CustomMetadata?: Record<string, string>;
|
|
73
|
-
/**
|
|
74
|
-
* Current status of the file
|
|
75
|
-
*/
|
|
76
|
-
status: 'uploading' | 'success' | 'error';
|
|
77
|
-
/**
|
|
78
|
-
* Upload progress (0-100)
|
|
79
|
-
*/
|
|
80
|
-
progress?: number;
|
|
81
|
-
/**
|
|
82
|
-
* Error message if upload failed
|
|
83
|
-
*/
|
|
84
|
-
error?: string;
|
|
85
|
-
}
|
|
86
4
|
/**
|
|
87
|
-
* A file held in a `
|
|
5
|
+
* A file held in a multi-file `FileUpload`'s staging list. Either purely
|
|
88
6
|
* internal (the component owns the state) or bindable (the caller owns the
|
|
89
7
|
* state and can mutate `status` / `progress` / `error` during upload for
|
|
90
8
|
* per-file visual feedback).
|
|
9
|
+
*
|
|
10
|
+
* Only used when `maxFiles > 1`.
|
|
91
11
|
*/
|
|
92
12
|
export interface StagedFile {
|
|
93
13
|
/** Stable client-generated id for list keys and caller-side updates. */
|
|
@@ -104,41 +24,67 @@ export interface StagedFile {
|
|
|
104
24
|
/** Optional error message rendered under the file name while status='error'. */
|
|
105
25
|
error?: string;
|
|
106
26
|
}
|
|
107
|
-
export interface
|
|
27
|
+
export interface FileUploadProps {
|
|
108
28
|
/**
|
|
109
29
|
* Array of allowed file MIME types or extensions.
|
|
110
|
-
*
|
|
111
|
-
* @example ['text/csv', '.csv.gz']
|
|
30
|
+
* @example ['image/jpeg', 'image/png', '.csv', '.csv.gz']
|
|
112
31
|
*/
|
|
113
32
|
allowedMimeTypes?: string[];
|
|
114
33
|
/**
|
|
115
|
-
* Maximum file size in bytes (
|
|
116
|
-
* Forwarded to the internal `FileUpload` dropzone.
|
|
34
|
+
* Maximum file size in bytes (shown in the dropzone hint).
|
|
117
35
|
*/
|
|
118
36
|
maxSize?: number;
|
|
119
37
|
/**
|
|
120
|
-
* Maximum number of files that can be
|
|
121
|
-
*
|
|
122
|
-
*
|
|
38
|
+
* Maximum number of files that can be uploaded.
|
|
39
|
+
*
|
|
40
|
+
* - `<= 0` — dropzone is disabled
|
|
41
|
+
* - `=== 1` (default) — single-file mode. `onfiles` fires immediately
|
|
42
|
+
* when the user drops or picks a file. No staging list, no "Upload All"
|
|
43
|
+
* button.
|
|
44
|
+
* - `> 1` — multi-file staging mode. Users drop/pick files which appear in
|
|
45
|
+
* a staging list with per-row remove buttons. `onfiles` fires when the
|
|
46
|
+
* user clicks "Upload All" with all ready-status files. The staging list
|
|
47
|
+
* auto-clears on fire unless `clearAfterUpload={false}` is passed.
|
|
48
|
+
*
|
|
49
|
+
* @default 1
|
|
123
50
|
*/
|
|
124
51
|
maxFiles?: number;
|
|
125
52
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
53
|
+
* Visual size variant of the dropzone.
|
|
54
|
+
*
|
|
55
|
+
* - `xl` (default) — hero square card, column layout, 48px padding +
|
|
56
|
+
* 96px circular icon. For empty-state pages or standalone dialogs.
|
|
57
|
+
* - `md` — wide rectangle, row layout, icon-left + text-right, ~80-90px
|
|
58
|
+
* tall. Form-prominent.
|
|
59
|
+
* - `sm` — compact inline rectangle, row layout, ~56-60px tall. Behaves
|
|
60
|
+
* like a single form field.
|
|
61
|
+
*
|
|
62
|
+
* @default 'xl'
|
|
128
63
|
*/
|
|
129
64
|
size?: FileUploadSize;
|
|
130
|
-
/** CSS class for the component
|
|
65
|
+
/** CSS class for the component container. */
|
|
131
66
|
class?: ClassValue;
|
|
132
|
-
/** CSS class for the
|
|
67
|
+
/** CSS class for the dropzone area. */
|
|
133
68
|
dropzoneClass?: ClassValue;
|
|
134
|
-
/** ID for the
|
|
69
|
+
/** ID for the file input element. */
|
|
135
70
|
id?: string;
|
|
136
71
|
/**
|
|
137
|
-
*
|
|
72
|
+
* Callback fired with the selected files as a normalized `File[]`.
|
|
73
|
+
*
|
|
74
|
+
* - In **single-file mode** (`maxFiles === 1`) — fires immediately on drop/pick.
|
|
75
|
+
* - In **multi-file mode** (`maxFiles > 1`) — fires when the user clicks
|
|
76
|
+
* "Upload All" with the current ready-status files from the staging list.
|
|
77
|
+
*/
|
|
78
|
+
onfiles?: (files: File[]) => void;
|
|
79
|
+
/** Snippet to override the default "Click here / Drag & drop" dropzone copy. */
|
|
80
|
+
uploadContent?: Snippet;
|
|
81
|
+
testId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Bindable staging list. Only relevant when `maxFiles > 1`.
|
|
138
84
|
*
|
|
139
85
|
* - **Simple mode** (omit the prop): the component owns the staging list,
|
|
140
86
|
* auto-clears it after `onfiles` fires, and never renders status icons.
|
|
141
|
-
* Rows show `name + size + ×` only.
|
|
87
|
+
* Rows show `name + size + ×` only.
|
|
142
88
|
*
|
|
143
89
|
* - **Rich mode** (`bind:files`): the caller owns the list. The component
|
|
144
90
|
* writes adds/removes through the binding, and reads each file's
|
|
@@ -149,17 +95,11 @@ export interface MultiFileUploadProps {
|
|
|
149
95
|
* the component from emptying the list after firing `onfiles`.
|
|
150
96
|
*/
|
|
151
97
|
files?: StagedFile[];
|
|
152
|
-
/**
|
|
153
|
-
* Fires when the user clicks "Upload All", with the raw `File[]` for every
|
|
154
|
-
* file currently in the staging list with status `'ready'`. Identical shape
|
|
155
|
-
* to `FileUpload.onfiles` — callers that already handle `FileUpload` can
|
|
156
|
-
* drop this in as a batched equivalent.
|
|
157
|
-
*/
|
|
158
|
-
onfiles?: (files: File[]) => void;
|
|
159
98
|
/**
|
|
160
99
|
* Whether to automatically empty the staging list after `onfiles` fires.
|
|
161
|
-
* Set to `false` in rich mode so the
|
|
162
|
-
* while updating per-file status during
|
|
100
|
+
* Only relevant when `maxFiles > 1`. Set to `false` in rich mode so the
|
|
101
|
+
* caller can keep the list visible while updating per-file status during
|
|
102
|
+
* upload.
|
|
163
103
|
* @default true
|
|
164
104
|
*/
|
|
165
105
|
clearAfterUpload?: boolean;
|
|
@@ -178,5 +118,32 @@ export interface MultiFileUploadProps {
|
|
|
178
118
|
listMaxHeight?: string;
|
|
179
119
|
/** Optional snippet rendered above the dropzone (for titles, selects, banners). */
|
|
180
120
|
header?: Snippet;
|
|
121
|
+
}
|
|
122
|
+
export interface FilePreviewProps {
|
|
123
|
+
files: UploadedFile[];
|
|
124
|
+
ondelete?: (fileId: string, index: number) => void;
|
|
125
|
+
class?: ClassValue;
|
|
181
126
|
testId?: string;
|
|
182
127
|
}
|
|
128
|
+
export interface UploadedFile {
|
|
129
|
+
FileID: string;
|
|
130
|
+
OriginalFilename: string;
|
|
131
|
+
Size: number;
|
|
132
|
+
ContentType: string;
|
|
133
|
+
MD5Hash: string;
|
|
134
|
+
UploadTimestamp: string;
|
|
135
|
+
Expiry?: string;
|
|
136
|
+
CustomMetadata?: Record<string, string>;
|
|
137
|
+
/**
|
|
138
|
+
* Current status of the file
|
|
139
|
+
*/
|
|
140
|
+
status: 'uploading' | 'success' | 'error';
|
|
141
|
+
/**
|
|
142
|
+
* Upload progress (0-100)
|
|
143
|
+
*/
|
|
144
|
+
progress?: number;
|
|
145
|
+
/**
|
|
146
|
+
* Error message if upload failed
|
|
147
|
+
*/
|
|
148
|
+
error?: string;
|
|
149
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -45,8 +45,8 @@ export type { ComboboxItem, ComboboxProps } from './elements/combobox/combobox-t
|
|
|
45
45
|
export type { AccordionProps } from './elements/accordion/accordion-types.js';
|
|
46
46
|
export type { TimelineItem } from './elements/timeline/timeline-types.js';
|
|
47
47
|
export type { FilterTab, FilterGroup, CompactFiltersProps } from './filters/filter-types.js';
|
|
48
|
-
export type { ActivityItemBadge, ActivityItemAction, ActivityItem, ActivityListProps } from './layout/activity-list/activity-list-types.js';
|
|
49
|
-
export type { FileUploadProps, FileUploadSize, FilePreviewProps, UploadedFile, StagedFile
|
|
48
|
+
export type { ActivityItemBadge, ActivityItemAction, ActivityItem, ActivityListProps, ActivityListSize } from './layout/activity-list/activity-list-types.js';
|
|
49
|
+
export type { FileUploadProps, FileUploadSize, FilePreviewProps, UploadedFile, StagedFile } from './elements/file-upload/file-upload-types.js';
|
|
50
50
|
export type { ChatMessageType, StreamingCallback, ChatAction, ChatMessage, ChatResponse, QuickAction, FileBrowserProps } from './ai/ai-types.js';
|
|
51
51
|
export type { GetUsersOptions, GetUsersResult, UserEmail, UserPhone, User, Permission, Role, UserTableProps, UserModalProps, UserViewModalProps, UserManagementAdapter, UserManagementProps, FormErrors } from './user-management/user-management-types.js';
|
|
52
52
|
export { tv, cn } from './helper/cls.js';
|
|
@@ -92,7 +92,6 @@ export { default as Timeline } from './elements/timeline/Timeline.svelte';
|
|
|
92
92
|
export { default as Chart } from './charts/Chart.svelte';
|
|
93
93
|
export { default as FileUpload } from './elements/file-upload/FileUpload.svelte';
|
|
94
94
|
export { default as FilesPreview } from './elements/file-upload/FilesPreview.svelte';
|
|
95
|
-
export { default as MultiFileUpload } from './elements/file-upload/MultiFileUpload.svelte';
|
|
96
95
|
export { default as Toaster } from './sonner/sonner.svelte';
|
|
97
96
|
export { default as AIChatInterface } from './ai/AIChatInterface.svelte';
|
|
98
97
|
export { default as MermaidRenderer } from './ai/MermaidRenderer.svelte';
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,6 @@ export { default as Chart } from './charts/Chart.svelte';
|
|
|
82
82
|
// File Upload
|
|
83
83
|
export { default as FileUpload } from './elements/file-upload/FileUpload.svelte';
|
|
84
84
|
export { default as FilesPreview } from './elements/file-upload/FilesPreview.svelte';
|
|
85
|
-
export { default as MultiFileUpload } from './elements/file-upload/MultiFileUpload.svelte';
|
|
86
85
|
// Toaster: Should be registered in +layout.svelte
|
|
87
86
|
export { default as Toaster } from './sonner/sonner.svelte';
|
|
88
87
|
// AI Components
|