@r2digisolutions/components 0.6.2 → 0.6.7
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/molecules/Combobox/Combobox.stories.d.ts +6 -0
- package/dist/components/molecules/Combobox/Combobox.stories.js +5 -2
- package/dist/components/molecules/Combobox/ComboboxStory.svelte +11 -2
- package/dist/components/molecules/Combobox/ComboboxStory.svelte.d.ts +1 -0
- package/dist/components/molecules/Combobox/combobox-item.svelte.d.ts +17 -0
- package/dist/components/molecules/Combobox/combobox-item.svelte.js +57 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItem.svelte +20 -35
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItem.stories.d.ts +9 -0
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItem.stories.js +8 -0
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte +84 -0
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte.d.ts +17 -0
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItemStory.svelte +42 -0
- package/dist/components/molecules/ComboboxMenuItem/ComboboxMenuItemStory.svelte.d.ts +3 -0
- package/dist/components/molecules/Dock/Dock.stories.d.ts +22 -0
- package/dist/components/molecules/Dock/Dock.stories.js +14 -0
- package/dist/components/molecules/Dock/Dock.svelte +29 -0
- package/dist/components/molecules/Dock/Dock.svelte.d.ts +10 -0
- package/dist/components/molecules/Dock/DockItem.svelte +69 -0
- package/dist/components/molecules/Dock/DockItem.svelte.d.ts +16 -0
- package/dist/components/molecules/Dock/DockStory.svelte +25 -0
- package/dist/components/molecules/Dock/DockStory.svelte.d.ts +7 -0
- package/dist/components/molecules/SpeedDial/SpeedDial.stories.d.ts +1 -1
- package/dist/components/organisms/DataGrid/DataGrid.svelte.d.ts +2 -2
- package/dist/components/organisms/FileUploader/FileUploader.svelte +160 -120
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/utils/i18n.d.ts +23 -1
- package/dist/utils/i18n.js +150 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte';
|
|
3
|
+
import { i18n } from '../../../utils/i18n.svelte.js';
|
|
3
4
|
|
|
4
5
|
type FileKind = 'image' | 'video' | 'audio' | 'pdf' | 'doc' | 'other';
|
|
5
6
|
type UploaderVariant = 'multiple' | 'single' | 'avatar';
|
|
@@ -50,8 +51,8 @@
|
|
|
50
51
|
|
|
51
52
|
let {
|
|
52
53
|
id = `upload-${Math.random().toString(36).slice(2, 9)}`,
|
|
53
|
-
label
|
|
54
|
-
helperText
|
|
54
|
+
label,
|
|
55
|
+
helperText,
|
|
55
56
|
accept = 'image/*',
|
|
56
57
|
variant = 'multiple',
|
|
57
58
|
view = $bindable('list'),
|
|
@@ -75,13 +76,13 @@
|
|
|
75
76
|
|
|
76
77
|
const isMultiple = $derived(variant === 'multiple');
|
|
77
78
|
const hasFile = $derived(fileList.length > 0);
|
|
79
|
+
const resolvedLabel = $derived(label ?? i18n.t('uploadFiles'));
|
|
80
|
+
const resolvedHelperText = $derived(helperText ?? i18n.t('uploadHelper'));
|
|
78
81
|
const primaryFile = $derived(fileList[0] as FileItem | undefined);
|
|
79
82
|
const externalSrc = $derived(src?.trim() || '');
|
|
80
|
-
const showSinglePreview = $derived(
|
|
81
|
-
variant === 'single' && (!!primaryFile || !!externalSrc)
|
|
82
|
-
);
|
|
83
|
+
const showSinglePreview = $derived(variant === 'single' && (!!primaryFile || !!externalSrc));
|
|
83
84
|
const singlePreviewUrl = $derived(primaryFile?.previewUrl || externalSrc);
|
|
84
|
-
const singlePreviewName = $derived(primaryFile?.name || srcName || '
|
|
85
|
+
const singlePreviewName = $derived(primaryFile?.name || srcName || i18n.t('media'));
|
|
85
86
|
const singlePreviewMeta = $derived(primaryFile?.sizeFormatted || '');
|
|
86
87
|
|
|
87
88
|
const labelId = $derived(`${id}-label`);
|
|
@@ -103,7 +104,8 @@
|
|
|
103
104
|
primaryFile?.kind === 'audio' ||
|
|
104
105
|
(!primaryFile &&
|
|
105
106
|
!!externalSrc &&
|
|
106
|
-
(resolvedAccept.includes('audio') ||
|
|
107
|
+
(resolvedAccept.includes('audio') ||
|
|
108
|
+
/\.(mp3|wav|ogg|m4a|aac|flac)(\?|$)/i.test(externalSrc)))
|
|
107
109
|
);
|
|
108
110
|
const dropLayout = $derived(layout === 'compact' ? 'compact' : layout);
|
|
109
111
|
|
|
@@ -142,9 +144,12 @@
|
|
|
142
144
|
function kindBadgeClass(kind: FileKind) {
|
|
143
145
|
if (kind === 'pdf') return 'bg-red-50 text-red-600 dark:bg-red-950/50 dark:text-red-300';
|
|
144
146
|
if (kind === 'doc') return 'bg-sky-50 text-sky-700 dark:bg-sky-950/50 dark:text-sky-300';
|
|
145
|
-
if (kind === 'image')
|
|
146
|
-
|
|
147
|
-
if (kind === '
|
|
147
|
+
if (kind === 'image')
|
|
148
|
+
return 'bg-brand-50 text-brand-700 dark:bg-brand-950/50 dark:text-brand-300';
|
|
149
|
+
if (kind === 'video')
|
|
150
|
+
return 'bg-violet-50 text-violet-700 dark:bg-violet-950/50 dark:text-violet-300';
|
|
151
|
+
if (kind === 'audio')
|
|
152
|
+
return 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-300';
|
|
148
153
|
return 'bg-surface-overlay text-secondary';
|
|
149
154
|
}
|
|
150
155
|
|
|
@@ -180,15 +185,15 @@
|
|
|
180
185
|
|
|
181
186
|
for (const file of incoming) {
|
|
182
187
|
if (variant === 'avatar' && !file.type.startsWith('image/')) {
|
|
183
|
-
rejected.push(
|
|
188
|
+
rejected.push(i18n.t('imageRequired', { name: file.name }));
|
|
184
189
|
continue;
|
|
185
190
|
}
|
|
186
191
|
if (!matchesAccept(file)) {
|
|
187
|
-
rejected.push(
|
|
192
|
+
rejected.push(i18n.t('typeNotAllowed', { name: file.name }));
|
|
188
193
|
continue;
|
|
189
194
|
}
|
|
190
195
|
if (file.size > maxBytes) {
|
|
191
|
-
rejected.push(
|
|
196
|
+
rejected.push(i18n.t('exceedsMaxSize', { name: file.name, max: maxSizeMb }));
|
|
192
197
|
continue;
|
|
193
198
|
}
|
|
194
199
|
|
|
@@ -296,7 +301,7 @@
|
|
|
296
301
|
{#snippet fileBadge(kind: FileKind, className = '')}
|
|
297
302
|
<div
|
|
298
303
|
class={[
|
|
299
|
-
'flex items-center justify-center text-[10px]
|
|
304
|
+
'font-bold tracking-wide flex items-center justify-center text-[10px] uppercase',
|
|
300
305
|
kindBadgeClass(kind),
|
|
301
306
|
className
|
|
302
307
|
]}
|
|
@@ -313,8 +318,8 @@
|
|
|
313
318
|
<div
|
|
314
319
|
role="button"
|
|
315
320
|
tabindex={disabled ? -1 : 0}
|
|
316
|
-
aria-labelledby={
|
|
317
|
-
aria-describedby={[
|
|
321
|
+
aria-labelledby={resolvedLabel ? labelId : undefined}
|
|
322
|
+
aria-describedby={[resolvedHelperText ? helperId : '', errorMessage ? errorId : '']
|
|
318
323
|
.filter(Boolean)
|
|
319
324
|
.join(' ') || undefined}
|
|
320
325
|
aria-disabled={disabled || undefined}
|
|
@@ -332,27 +337,27 @@
|
|
|
332
337
|
onclick={openPicker}
|
|
333
338
|
class={[
|
|
334
339
|
'relative overflow-hidden border border-dashed transition-colors duration-150 outline-none',
|
|
335
|
-
'focus-visible:ring-
|
|
340
|
+
'focus-visible:ring-brand-500/30 focus-visible:border-brand-500 focus-visible:ring-2',
|
|
336
341
|
'rounded-xl',
|
|
337
342
|
isDragging
|
|
338
343
|
? 'border-brand-500 bg-brand-500/5 dark:bg-brand-950/40'
|
|
339
344
|
: 'border-border bg-surface-elevated hover:border-border-strong hover:bg-surface-overlay/60',
|
|
340
|
-
disabled ? '
|
|
345
|
+
disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
|
|
341
346
|
]}
|
|
342
347
|
>
|
|
343
348
|
<div
|
|
344
349
|
class={[
|
|
345
350
|
'flex',
|
|
346
351
|
vertical
|
|
347
|
-
? '
|
|
352
|
+
? 'gap-2 px-3 py-5 flex-col items-center text-center'
|
|
348
353
|
: compact
|
|
349
|
-
? '
|
|
350
|
-
: '
|
|
354
|
+
? 'gap-3 px-3 py-2.5 flex-row items-center'
|
|
355
|
+
: 'gap-4 px-4 py-5 sm:px-5 flex-row items-center'
|
|
351
356
|
]}
|
|
352
357
|
>
|
|
353
358
|
<div
|
|
354
359
|
class={[
|
|
355
|
-
'flex shrink-0 items-center justify-center
|
|
360
|
+
'rounded-lg flex shrink-0 items-center justify-center transition-colors duration-150',
|
|
356
361
|
vertical ? 'h-10 w-10' : compact ? 'h-8 w-8' : 'h-11 w-11',
|
|
357
362
|
isDragging
|
|
358
363
|
? 'bg-brand-600 text-white'
|
|
@@ -384,47 +389,44 @@
|
|
|
384
389
|
]}
|
|
385
390
|
>
|
|
386
391
|
{#if isDragging}
|
|
387
|
-
|
|
392
|
+
{i18n.t('dropToUpload')}
|
|
388
393
|
{:else}
|
|
389
|
-
|
|
390
|
-
<span class="text-brand-700 dark:text-brand-300"> browse</span>
|
|
394
|
+
{i18n.t('dropOrBrowse')}
|
|
395
|
+
<span class="text-brand-700 dark:text-brand-300"> {i18n.t('browse')}</span>
|
|
391
396
|
{/if}
|
|
392
397
|
</p>
|
|
393
|
-
{#if
|
|
398
|
+
{#if resolvedHelperText}
|
|
394
399
|
<p
|
|
395
400
|
id={helperId}
|
|
396
|
-
class={[
|
|
397
|
-
'mt-0.5 text-[11px] leading-relaxed text-secondary',
|
|
398
|
-
vertical && 'text-center'
|
|
399
|
-
]}
|
|
401
|
+
class={['mt-0.5 leading-relaxed text-secondary text-[11px]', vertical && 'text-center']}
|
|
400
402
|
>
|
|
401
|
-
{
|
|
403
|
+
{resolvedHelperText}
|
|
402
404
|
</p>
|
|
403
405
|
{/if}
|
|
404
406
|
</div>
|
|
405
407
|
|
|
406
408
|
{#if !disabled && !compact && !vertical}
|
|
407
409
|
<span
|
|
408
|
-
class="
|
|
410
|
+
class="sm:inline-flex rounded-lg border-border bg-surface-elevated px-3 py-1.5 text-xs font-medium text-primary pointer-events-none hidden shrink-0 items-center border"
|
|
409
411
|
>
|
|
410
|
-
|
|
412
|
+
{i18n.t('browseButton')}
|
|
411
413
|
</span>
|
|
412
414
|
{/if}
|
|
413
415
|
</div>
|
|
414
416
|
|
|
415
417
|
{#if isDragging}
|
|
416
418
|
<div
|
|
417
|
-
class="pointer-events-none absolute
|
|
419
|
+
class="inset-0 ring-brand-500/40 rounded-xl pointer-events-none absolute ring-2 ring-inset"
|
|
418
420
|
aria-hidden="true"
|
|
419
421
|
></div>
|
|
420
422
|
{/if}
|
|
421
423
|
</div>
|
|
422
424
|
{/snippet}
|
|
423
425
|
|
|
424
|
-
<div class={['
|
|
426
|
+
<div class={['gap-3 flex w-full flex-col', className]}>
|
|
425
427
|
<input
|
|
426
428
|
bind:this={fileInputNode}
|
|
427
|
-
|
|
429
|
+
{id}
|
|
428
430
|
type="file"
|
|
429
431
|
accept={resolvedAccept}
|
|
430
432
|
multiple={isMultiple}
|
|
@@ -435,31 +437,35 @@
|
|
|
435
437
|
aria-hidden="true"
|
|
436
438
|
/>
|
|
437
439
|
|
|
438
|
-
{#if
|
|
439
|
-
<div class="flex items-center justify-between
|
|
440
|
-
<div id={labelId} class="text-sm font-medium text-primary">{
|
|
440
|
+
{#if resolvedLabel}
|
|
441
|
+
<div class="gap-3 flex items-center justify-between">
|
|
442
|
+
<div id={labelId} class="text-sm font-medium text-primary">{resolvedLabel}</div>
|
|
441
443
|
|
|
442
444
|
{#if isMultiple && hasFile}
|
|
443
|
-
<div class="flex items-center
|
|
445
|
+
<div class="gap-2 flex items-center">
|
|
444
446
|
{#if showViewToggle}
|
|
445
447
|
<div
|
|
446
|
-
class="
|
|
448
|
+
class="rounded-lg border-border bg-surface-elevated p-0.5 inline-flex items-center border"
|
|
447
449
|
role="group"
|
|
448
|
-
aria-label=
|
|
450
|
+
aria-label={i18n.t('viewMode')}
|
|
449
451
|
>
|
|
450
452
|
<button
|
|
451
453
|
type="button"
|
|
452
454
|
onclick={() => (view = 'list')}
|
|
453
455
|
class={[
|
|
454
456
|
'rounded-md p-1.5 transition-colors',
|
|
455
|
-
view === 'list'
|
|
456
|
-
? 'bg-brand-600 text-white'
|
|
457
|
-
: 'text-secondary hover:text-primary'
|
|
457
|
+
view === 'list' ? 'bg-brand-600 text-white' : 'text-secondary hover:text-primary'
|
|
458
458
|
]}
|
|
459
459
|
aria-pressed={view === 'list'}
|
|
460
|
-
aria-label=
|
|
460
|
+
aria-label={i18n.t('listView')}
|
|
461
461
|
>
|
|
462
|
-
<svg
|
|
462
|
+
<svg
|
|
463
|
+
class="h-3.5 w-3.5"
|
|
464
|
+
viewBox="0 0 24 24"
|
|
465
|
+
fill="none"
|
|
466
|
+
stroke="currentColor"
|
|
467
|
+
stroke-width="2"
|
|
468
|
+
>
|
|
463
469
|
<path stroke-linecap="round" d="M4 6h16M4 12h16M4 18h16" />
|
|
464
470
|
</svg>
|
|
465
471
|
</button>
|
|
@@ -468,17 +474,13 @@
|
|
|
468
474
|
onclick={() => (view = 'grid')}
|
|
469
475
|
class={[
|
|
470
476
|
'rounded-md p-1.5 transition-colors',
|
|
471
|
-
view === 'grid'
|
|
472
|
-
? 'bg-brand-600 text-white'
|
|
473
|
-
: 'text-secondary hover:text-primary'
|
|
477
|
+
view === 'grid' ? 'bg-brand-600 text-white' : 'text-secondary hover:text-primary'
|
|
474
478
|
]}
|
|
475
479
|
aria-pressed={view === 'grid'}
|
|
476
|
-
aria-label=
|
|
480
|
+
aria-label={i18n.t('gridView')}
|
|
477
481
|
>
|
|
478
482
|
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="currentColor">
|
|
479
|
-
<path
|
|
480
|
-
d="M4 4h6v6H4V4zm10 0h6v6h-6V4zM4 14h6v6H4v-6zm10 0h6v6h-6v-6z"
|
|
481
|
-
/>
|
|
483
|
+
<path d="M4 4h6v6H4V4zm10 0h6v6h-6V4zM4 14h6v6H4v-6zm10 0h6v6h-6v-6z" />
|
|
482
484
|
</svg>
|
|
483
485
|
</button>
|
|
484
486
|
</div>
|
|
@@ -490,7 +492,7 @@
|
|
|
490
492
|
onclick={clearAll}
|
|
491
493
|
class="text-xs font-medium text-secondary hover:text-primary transition-colors"
|
|
492
494
|
>
|
|
493
|
-
|
|
495
|
+
{i18n.t('clearAll')}
|
|
494
496
|
</button>
|
|
495
497
|
{/if}
|
|
496
498
|
</div>
|
|
@@ -500,13 +502,13 @@
|
|
|
500
502
|
|
|
501
503
|
<!-- Avatar variant -->
|
|
502
504
|
{#if variant === 'avatar'}
|
|
503
|
-
<div class="flex items-center
|
|
505
|
+
<div class="gap-4 flex items-center">
|
|
504
506
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
505
507
|
<div
|
|
506
508
|
role="button"
|
|
507
509
|
tabindex={disabled ? -1 : 0}
|
|
508
|
-
aria-labelledby={
|
|
509
|
-
aria-describedby={
|
|
510
|
+
aria-labelledby={resolvedLabel ? labelId : undefined}
|
|
511
|
+
aria-describedby={resolvedHelperText ? helperId : undefined}
|
|
510
512
|
aria-disabled={disabled || undefined}
|
|
511
513
|
ondragenter={handleDragEnter}
|
|
512
514
|
ondragover={handleDragOver}
|
|
@@ -521,14 +523,14 @@
|
|
|
521
523
|
}}
|
|
522
524
|
onclick={openPicker}
|
|
523
525
|
class={[
|
|
524
|
-
'
|
|
525
|
-
'focus-visible:ring-
|
|
526
|
+
'group h-24 w-24 relative shrink-0 overflow-hidden rounded-full border-2 border-dashed transition-colors outline-none',
|
|
527
|
+
'focus-visible:ring-brand-500/30 focus-visible:border-brand-500 focus-visible:ring-2',
|
|
526
528
|
isDragging
|
|
527
529
|
? 'border-brand-500 bg-brand-500/10'
|
|
528
530
|
: hasFile || externalSrc
|
|
529
531
|
? 'border-transparent'
|
|
530
532
|
: 'border-border bg-surface-elevated hover:border-border-strong',
|
|
531
|
-
disabled ? '
|
|
533
|
+
disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
|
|
532
534
|
]}
|
|
533
535
|
>
|
|
534
536
|
{#if primaryFile?.previewUrl || externalSrc}
|
|
@@ -539,13 +541,19 @@
|
|
|
539
541
|
/>
|
|
540
542
|
{#if !disabled}
|
|
541
543
|
<div
|
|
542
|
-
class="
|
|
544
|
+
class="inset-0 bg-black/45 absolute flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
543
545
|
>
|
|
544
546
|
<span
|
|
545
|
-
class="
|
|
547
|
+
class="h-8 w-8 bg-white text-primary shadow-sm inline-flex items-center justify-center rounded-full"
|
|
546
548
|
aria-hidden="true"
|
|
547
549
|
>
|
|
548
|
-
<svg
|
|
550
|
+
<svg
|
|
551
|
+
class="h-4 w-4"
|
|
552
|
+
fill="none"
|
|
553
|
+
viewBox="0 0 24 24"
|
|
554
|
+
stroke="currentColor"
|
|
555
|
+
stroke-width="2"
|
|
556
|
+
>
|
|
549
557
|
<path
|
|
550
558
|
stroke-linecap="round"
|
|
551
559
|
stroke-linejoin="round"
|
|
@@ -556,115 +564,135 @@
|
|
|
556
564
|
</div>
|
|
557
565
|
{/if}
|
|
558
566
|
{:else}
|
|
559
|
-
<div class="flex h-full w-full flex-col items-center justify-center
|
|
560
|
-
<svg
|
|
567
|
+
<div class="gap-1 text-secondary flex h-full w-full flex-col items-center justify-center">
|
|
568
|
+
<svg
|
|
569
|
+
class="h-6 w-6"
|
|
570
|
+
fill="none"
|
|
571
|
+
viewBox="0 0 24 24"
|
|
572
|
+
stroke="currentColor"
|
|
573
|
+
stroke-width="1.75"
|
|
574
|
+
>
|
|
561
575
|
<path
|
|
562
576
|
stroke-linecap="round"
|
|
563
577
|
stroke-linejoin="round"
|
|
564
578
|
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0"
|
|
565
579
|
/>
|
|
566
580
|
</svg>
|
|
567
|
-
<span class="text-[10px]
|
|
581
|
+
<span class="font-medium text-[10px]">{i18n.t('upload')}</span>
|
|
568
582
|
</div>
|
|
569
583
|
{/if}
|
|
570
584
|
</div>
|
|
571
585
|
|
|
572
586
|
<div class="min-w-0 flex-1">
|
|
573
587
|
{#if primaryFile}
|
|
574
|
-
<p class="
|
|
588
|
+
<p class="text-sm font-medium text-primary truncate">{primaryFile.name}</p>
|
|
575
589
|
<p class="text-xs text-secondary">{primaryFile.sizeFormatted}</p>
|
|
576
|
-
<div class="mt-2 flex items-center
|
|
590
|
+
<div class="mt-2 gap-2 flex items-center">
|
|
577
591
|
<button
|
|
578
592
|
type="button"
|
|
579
593
|
onclick={openPicker}
|
|
580
|
-
|
|
581
|
-
class="
|
|
594
|
+
{disabled}
|
|
595
|
+
class="gap-1.5 rounded-lg border-border bg-surface-elevated px-2.5 py-1.5 text-xs font-medium text-primary hover:bg-surface-overlay inline-flex items-center border transition-colors disabled:opacity-50"
|
|
582
596
|
>
|
|
583
|
-
<svg
|
|
597
|
+
<svg
|
|
598
|
+
class="h-3.5 w-3.5"
|
|
599
|
+
fill="none"
|
|
600
|
+
viewBox="0 0 24 24"
|
|
601
|
+
stroke="currentColor"
|
|
602
|
+
stroke-width="2"
|
|
603
|
+
>
|
|
584
604
|
<path
|
|
585
605
|
stroke-linecap="round"
|
|
586
606
|
stroke-linejoin="round"
|
|
587
607
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"
|
|
588
608
|
/>
|
|
589
609
|
</svg>
|
|
590
|
-
|
|
610
|
+
{i18n.t('change')}
|
|
591
611
|
</button>
|
|
592
612
|
<button
|
|
593
613
|
type="button"
|
|
594
614
|
onclick={() => primaryFile && removeFile(primaryFile.id)}
|
|
595
|
-
|
|
596
|
-
class="
|
|
615
|
+
{disabled}
|
|
616
|
+
class="rounded-lg px-2.5 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40 inline-flex items-center transition-colors disabled:opacity-50"
|
|
597
617
|
>
|
|
598
|
-
|
|
618
|
+
{i18n.t('remove')}
|
|
599
619
|
</button>
|
|
600
620
|
</div>
|
|
601
621
|
{:else}
|
|
602
|
-
<p class="text-sm font-medium text-primary">
|
|
603
|
-
{#if
|
|
604
|
-
<p id={helperId} class="mt-0.5 text-xs text-secondary">{
|
|
622
|
+
<p class="text-sm font-medium text-primary">{i18n.t('addProfilePhoto')}</p>
|
|
623
|
+
{#if resolvedHelperText}
|
|
624
|
+
<p id={helperId} class="mt-0.5 text-xs text-secondary">{resolvedHelperText}</p>
|
|
605
625
|
{/if}
|
|
606
626
|
{/if}
|
|
607
627
|
</div>
|
|
608
628
|
</div>
|
|
609
629
|
|
|
610
|
-
|
|
630
|
+
<!-- Single variant: preview card with edit -->
|
|
611
631
|
{:else if showSinglePreview}
|
|
612
632
|
<div
|
|
613
633
|
class={[
|
|
614
|
-
'
|
|
615
|
-
isDragging && 'ring-
|
|
634
|
+
'rounded-xl border-border bg-surface-elevated shadow-sm overflow-hidden border',
|
|
635
|
+
isDragging && 'ring-brand-500/40 ring-2'
|
|
616
636
|
]}
|
|
617
637
|
ondragenter={handleDragEnter}
|
|
618
638
|
ondragover={handleDragOver}
|
|
619
639
|
ondragleave={handleDragLeave}
|
|
620
640
|
ondrop={handleDrop}
|
|
621
641
|
role="group"
|
|
622
|
-
aria-labelledby={
|
|
642
|
+
aria-labelledby={resolvedLabel ? labelId : undefined}
|
|
623
643
|
>
|
|
624
|
-
<div class="
|
|
644
|
+
<div class="aspect-video bg-surface-overlay relative">
|
|
625
645
|
{#if singlePreviewUrl && singleIsVideo}
|
|
626
|
-
<video
|
|
627
|
-
src={singlePreviewUrl}
|
|
628
|
-
class="h-full w-full object-cover"
|
|
629
|
-
muted
|
|
630
|
-
playsinline
|
|
646
|
+
<video src={singlePreviewUrl} class="h-full w-full object-cover" muted playsinline
|
|
631
647
|
></video>
|
|
632
648
|
{:else if singlePreviewUrl && singleIsAudio}
|
|
633
|
-
<div class="flex h-full w-full flex-col items-center justify-center
|
|
649
|
+
<div class="gap-3 px-4 flex h-full w-full flex-col items-center justify-center">
|
|
634
650
|
{@render fileBadge('audio', 'h-14 w-14 rounded-xl text-xs')}
|
|
635
|
-
<audio src={singlePreviewUrl} controls class="w-
|
|
651
|
+
<audio src={singlePreviewUrl} controls class="max-w-xs w-full"></audio>
|
|
636
652
|
</div>
|
|
637
653
|
{:else if singlePreviewUrl}
|
|
638
654
|
<img src={singlePreviewUrl} alt="" class="h-full w-full object-cover" />
|
|
639
655
|
{:else}
|
|
640
|
-
<div class="flex h-full w-full flex-col items-center justify-center
|
|
656
|
+
<div class="gap-2 flex h-full w-full flex-col items-center justify-center">
|
|
641
657
|
{@render fileBadge(primaryFile?.kind ?? 'other', 'h-14 w-14 rounded-xl text-xs')}
|
|
642
658
|
</div>
|
|
643
659
|
{/if}
|
|
644
660
|
|
|
645
661
|
{#if !disabled}
|
|
646
|
-
<div class="
|
|
662
|
+
<div class="right-3 top-3 gap-1.5 absolute flex items-center">
|
|
647
663
|
<button
|
|
648
664
|
type="button"
|
|
649
665
|
onclick={openPicker}
|
|
650
|
-
class="
|
|
666
|
+
class="gap-1.5 rounded-lg bg-white/95 px-2.5 py-1.5 text-xs font-medium text-primary shadow-sm backdrop-blur hover:bg-white dark:bg-slate-900/95 dark:hover:bg-slate-900 inline-flex items-center transition"
|
|
651
667
|
>
|
|
652
|
-
<svg
|
|
668
|
+
<svg
|
|
669
|
+
class="h-3.5 w-3.5"
|
|
670
|
+
fill="none"
|
|
671
|
+
viewBox="0 0 24 24"
|
|
672
|
+
stroke="currentColor"
|
|
673
|
+
stroke-width="2"
|
|
674
|
+
>
|
|
653
675
|
<path
|
|
654
676
|
stroke-linecap="round"
|
|
655
677
|
stroke-linejoin="round"
|
|
656
678
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"
|
|
657
679
|
/>
|
|
658
680
|
</svg>
|
|
659
|
-
|
|
681
|
+
{i18n.t('edit')}
|
|
660
682
|
</button>
|
|
661
683
|
<button
|
|
662
684
|
type="button"
|
|
663
685
|
onclick={clearSingle}
|
|
664
|
-
class="
|
|
665
|
-
aria-label={
|
|
686
|
+
class="rounded-lg bg-white/95 p-1.5 text-secondary shadow-sm backdrop-blur hover:bg-white hover:text-red-600 dark:bg-slate-900/95 dark:hover:bg-slate-900 dark:hover:text-red-400 inline-flex items-center justify-center transition"
|
|
687
|
+
aria-label={i18n.t('removeNamed', { name: singlePreviewName })}
|
|
666
688
|
>
|
|
667
|
-
<svg
|
|
689
|
+
<svg
|
|
690
|
+
class="h-3.5 w-3.5"
|
|
691
|
+
fill="none"
|
|
692
|
+
viewBox="0 0 24 24"
|
|
693
|
+
stroke="currentColor"
|
|
694
|
+
stroke-width="2"
|
|
695
|
+
>
|
|
668
696
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
669
697
|
</svg>
|
|
670
698
|
</button>
|
|
@@ -672,20 +700,20 @@
|
|
|
672
700
|
{/if}
|
|
673
701
|
</div>
|
|
674
702
|
|
|
675
|
-
<div class="
|
|
703
|
+
<div class="gap-3 border-border px-4 py-3 flex items-center justify-between border-t">
|
|
676
704
|
<div class="min-w-0">
|
|
677
|
-
<p class="
|
|
705
|
+
<p class="text-sm font-medium text-primary truncate">{singlePreviewName}</p>
|
|
678
706
|
{#if singlePreviewMeta}
|
|
679
707
|
<p class="text-xs text-secondary">{singlePreviewMeta}</p>
|
|
680
708
|
{/if}
|
|
681
709
|
</div>
|
|
682
|
-
{#if
|
|
683
|
-
<p id={helperId} class="
|
|
710
|
+
{#if resolvedHelperText}
|
|
711
|
+
<p id={helperId} class="text-xs text-secondary shrink-0">{resolvedHelperText}</p>
|
|
684
712
|
{/if}
|
|
685
713
|
</div>
|
|
686
714
|
</div>
|
|
687
715
|
|
|
688
|
-
|
|
716
|
+
<!-- Dropzone: hide the big one when multiple already has files (compact “add more” below) -->
|
|
689
717
|
{:else if !(isMultiple && hasFile && showFileList)}
|
|
690
718
|
{@render dropzone()}
|
|
691
719
|
{/if}
|
|
@@ -697,12 +725,12 @@
|
|
|
697
725
|
<!-- Multiple file cards (optional — MediaAssetBrowser owns its own grid) -->
|
|
698
726
|
{#if isMultiple && hasFile && showFileList}
|
|
699
727
|
{#if view === 'grid'}
|
|
700
|
-
<ul class="
|
|
728
|
+
<ul class="gap-2.5 sm:grid-cols-3 grid grid-cols-2" aria-label={i18n.t('selectedFiles')}>
|
|
701
729
|
{#each fileList as item (item.id)}
|
|
702
730
|
<li
|
|
703
|
-
class="group
|
|
731
|
+
class="group rounded-xl border-border bg-surface-elevated shadow-sm hover:border-border-strong relative overflow-hidden border transition"
|
|
704
732
|
>
|
|
705
|
-
<div class="relative aspect-square
|
|
733
|
+
<div class="bg-surface-overlay relative aspect-square">
|
|
706
734
|
{#if item.previewUrl}
|
|
707
735
|
<img src={item.previewUrl} alt="" class="h-full w-full object-cover" />
|
|
708
736
|
{:else}
|
|
@@ -715,51 +743,63 @@
|
|
|
715
743
|
<button
|
|
716
744
|
type="button"
|
|
717
745
|
onclick={() => removeFile(item.id)}
|
|
718
|
-
class="
|
|
719
|
-
aria-label={
|
|
746
|
+
class="right-2 top-2 h-7 w-7 rounded-lg bg-white/95 text-secondary shadow-sm backdrop-blur hover:text-red-600 dark:bg-slate-900/95 dark:hover:text-red-400 absolute inline-flex items-center justify-center opacity-0 transition group-hover:opacity-100 focus-visible:opacity-100"
|
|
747
|
+
aria-label={i18n.t('removeNamed', { name: item.name })}
|
|
720
748
|
>
|
|
721
|
-
<svg
|
|
749
|
+
<svg
|
|
750
|
+
class="h-3.5 w-3.5"
|
|
751
|
+
fill="none"
|
|
752
|
+
viewBox="0 0 24 24"
|
|
753
|
+
stroke="currentColor"
|
|
754
|
+
stroke-width="2"
|
|
755
|
+
>
|
|
722
756
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
723
757
|
</svg>
|
|
724
758
|
</button>
|
|
725
759
|
{/if}
|
|
726
760
|
</div>
|
|
727
|
-
<div class="border-
|
|
728
|
-
<p class="
|
|
761
|
+
<div class="border-border px-2.5 py-2 border-t">
|
|
762
|
+
<p class="text-xs font-medium text-primary truncate">{item.name}</p>
|
|
729
763
|
<p class="text-xs text-secondary">{item.sizeFormatted}</p>
|
|
730
764
|
</div>
|
|
731
765
|
</li>
|
|
732
766
|
{/each}
|
|
733
767
|
</ul>
|
|
734
768
|
{:else}
|
|
735
|
-
<ul class="flex flex-col
|
|
769
|
+
<ul class="gap-2 flex flex-col" aria-label={i18n.t('selectedFiles')}>
|
|
736
770
|
{#each fileList as item (item.id)}
|
|
737
771
|
<li
|
|
738
|
-
class="group
|
|
772
|
+
class="group gap-3 rounded-xl border-border bg-surface-elevated p-2.5 shadow-sm hover:border-border-strong flex items-center border transition"
|
|
739
773
|
>
|
|
740
774
|
{#if item.previewUrl}
|
|
741
775
|
<img
|
|
742
776
|
src={item.previewUrl}
|
|
743
777
|
alt=""
|
|
744
|
-
class="h-12 w-12
|
|
778
|
+
class="h-12 w-12 rounded-lg border-border shrink-0 border object-cover"
|
|
745
779
|
/>
|
|
746
780
|
{:else}
|
|
747
781
|
{@render fileBadge(item.kind, 'h-12 w-12 shrink-0 rounded-lg')}
|
|
748
782
|
{/if}
|
|
749
783
|
|
|
750
784
|
<div class="min-w-0 flex-1">
|
|
751
|
-
<p class="
|
|
785
|
+
<p class="text-sm font-medium text-primary truncate">{item.name}</p>
|
|
752
786
|
<p class="text-xs text-secondary">{item.sizeFormatted} · {kindLabel(item.kind)}</p>
|
|
753
787
|
</div>
|
|
754
788
|
|
|
755
789
|
<button
|
|
756
790
|
type="button"
|
|
757
791
|
onclick={() => removeFile(item.id)}
|
|
758
|
-
|
|
759
|
-
class="
|
|
760
|
-
aria-label={
|
|
792
|
+
{disabled}
|
|
793
|
+
class="rounded-lg p-2 text-secondary hover:bg-red-50 hover:text-red-600 focus-visible:ring-brand-500/30 dark:hover:bg-red-950/40 dark:hover:text-red-400 shrink-0 transition focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
|
|
794
|
+
aria-label={i18n.t('removeNamed', { name: item.name })}
|
|
761
795
|
>
|
|
762
|
-
<svg
|
|
796
|
+
<svg
|
|
797
|
+
class="h-4 w-4"
|
|
798
|
+
fill="none"
|
|
799
|
+
viewBox="0 0 24 24"
|
|
800
|
+
stroke="currentColor"
|
|
801
|
+
stroke-width="2"
|
|
802
|
+
>
|
|
763
803
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
764
804
|
</svg>
|
|
765
805
|
</button>
|
package/dist/index.d.ts
CHANGED
|
@@ -186,6 +186,9 @@ export { default as PasswordInput } from './components/molecules/PasswordInput/P
|
|
|
186
186
|
export { default as CopyButton } from './components/molecules/CopyButton/CopyButton.svelte';
|
|
187
187
|
export { default as SegmentedControl } from './components/molecules/SegmentedControl/SegmentedControl.svelte';
|
|
188
188
|
export type { SegmentItem } from './components/molecules/SegmentedControl/SegmentedControl.svelte';
|
|
189
|
+
export { default as Dock } from './components/molecules/Dock/Dock.svelte';
|
|
190
|
+
export type { DockSize } from './components/molecules/Dock/Dock.svelte';
|
|
191
|
+
export { default as DockItem } from './components/molecules/Dock/DockItem.svelte';
|
|
189
192
|
export { default as Popover } from './components/molecules/Popover/Popover.svelte';
|
|
190
193
|
export type { PopoverPlacement, PopoverAlign } from './components/molecules/Popover/Popover.svelte';
|
|
191
194
|
export { default as HoverCard } from './components/molecules/HoverCard/HoverCard.svelte';
|
|
@@ -212,6 +215,7 @@ export { default as DateRangePicker } from './components/molecules/DateRangePick
|
|
|
212
215
|
export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
|
|
213
216
|
export type { ComboboxOption, ComboboxItemState } from './components/molecules/Combobox/Combobox.svelte';
|
|
214
217
|
export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
|
|
218
|
+
export { default as ComboboxMenuItem } from './components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte';
|
|
215
219
|
export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
|
|
216
220
|
export type { ComboboxContext } from './components/molecules/Combobox/combobox-context.js';
|
|
217
221
|
export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
|
package/dist/index.js
CHANGED
|
@@ -150,6 +150,8 @@ export { default as SearchInput } from './components/molecules/SearchInput/Searc
|
|
|
150
150
|
export { default as PasswordInput } from './components/molecules/PasswordInput/PasswordInput.svelte';
|
|
151
151
|
export { default as CopyButton } from './components/molecules/CopyButton/CopyButton.svelte';
|
|
152
152
|
export { default as SegmentedControl } from './components/molecules/SegmentedControl/SegmentedControl.svelte';
|
|
153
|
+
export { default as Dock } from './components/molecules/Dock/Dock.svelte';
|
|
154
|
+
export { default as DockItem } from './components/molecules/Dock/DockItem.svelte';
|
|
153
155
|
export { default as Popover } from './components/molecules/Popover/Popover.svelte';
|
|
154
156
|
export { default as HoverCard } from './components/molecules/HoverCard/HoverCard.svelte';
|
|
155
157
|
export { default as Timeline } from './components/molecules/Timeline/Timeline.svelte';
|
|
@@ -167,6 +169,7 @@ export { default as DateTimePicker } from './components/molecules/DateTimePicker
|
|
|
167
169
|
export { default as DateRangePicker } from './components/molecules/DateRangePicker/DateRangePicker.svelte';
|
|
168
170
|
export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
|
|
169
171
|
export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
|
|
172
|
+
export { default as ComboboxMenuItem } from './components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte';
|
|
170
173
|
export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
|
|
171
174
|
export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
|
|
172
175
|
export { default as ConfirmDialog } from './components/molecules/ConfirmDialog/ConfirmDialog.svelte';
|