@r2digisolutions/components 0.17.6 → 0.17.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/DropZone/DropZone.svelte +5 -2
- package/dist/components/molecules/DropZone/DropZone.svelte.d.ts +2 -0
- package/dist/components/molecules/ImageGallery/ImageGallery.stories.js +4 -1
- package/dist/components/molecules/ImageGallery/ImageGallery.svelte +94 -14
- package/dist/components/molecules/ImageGallery/ImageGallery.svelte.d.ts +6 -1
- package/dist/components/molecules/ImageGallery/ImageGalleryStory.svelte +3 -1
- package/dist/components/molecules/ImageGallery/ImageGalleryStory.svelte.d.ts +1 -1
- package/dist/components/molecules/StatCard/StatCard.svelte +46 -10
- package/dist/components/molecules/StatCard/StatCard.svelte.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
maxSizeMb?: number;
|
|
26
26
|
/** Show image thumbnails / file cards for selected files. */
|
|
27
27
|
showPreview?: boolean;
|
|
28
|
+
/** Append “multiple · max N MB” under the hint. Default true. */
|
|
29
|
+
showConstraints?: boolean;
|
|
28
30
|
class?: string;
|
|
29
31
|
ondropfiles?: (files: File[]) => void;
|
|
30
32
|
onchange?: (files: File[]) => void;
|
|
@@ -41,6 +43,7 @@
|
|
|
41
43
|
disabled = false,
|
|
42
44
|
maxSizeMb = 25,
|
|
43
45
|
showPreview = true,
|
|
46
|
+
showConstraints = true,
|
|
44
47
|
class: className = '',
|
|
45
48
|
ondropfiles,
|
|
46
49
|
onchange,
|
|
@@ -239,8 +242,8 @@
|
|
|
239
242
|
<div>
|
|
240
243
|
<p class="text-sm font-medium text-primary">{label}</p>
|
|
241
244
|
<p class="mt-0.5 text-xs text-muted">
|
|
242
|
-
{hint}{#if multiple} · multiple{/if}
|
|
243
|
-
{#if maxSizeMb} · max {maxSizeMb}MB{/if}
|
|
245
|
+
{hint}{#if showConstraints && multiple} · multiple{/if}
|
|
246
|
+
{#if showConstraints && maxSizeMb} · max {maxSizeMb}MB{/if}
|
|
244
247
|
</p>
|
|
245
248
|
</div>
|
|
246
249
|
<input
|
|
@@ -16,6 +16,8 @@ interface DropZoneProps {
|
|
|
16
16
|
maxSizeMb?: number;
|
|
17
17
|
/** Show image thumbnails / file cards for selected files. */
|
|
18
18
|
showPreview?: boolean;
|
|
19
|
+
/** Append “multiple · max N MB” under the hint. Default true. */
|
|
20
|
+
showConstraints?: boolean;
|
|
19
21
|
class?: string;
|
|
20
22
|
ondropfiles?: (files: File[]) => void;
|
|
21
23
|
onchange?: (files: File[]) => void;
|
|
@@ -9,7 +9,7 @@ const meta = {
|
|
|
9
9
|
argTypes: {
|
|
10
10
|
variant: {
|
|
11
11
|
control: 'select',
|
|
12
|
-
options: ['default', 'featured', 'masonry']
|
|
12
|
+
options: ['default', 'featured', 'masonry', 'hero']
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
};
|
|
@@ -23,3 +23,6 @@ export const Featured = {
|
|
|
23
23
|
export const Masonry = {
|
|
24
24
|
args: { variant: 'masonry' }
|
|
25
25
|
};
|
|
26
|
+
export const Hero = {
|
|
27
|
+
args: { variant: 'hero' }
|
|
28
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import AspectRatio from '../../atoms/AspectRatio/AspectRatio.svelte';
|
|
3
4
|
import ImageLightbox from '../ImageLightbox/ImageLightbox.svelte';
|
|
4
5
|
import type { LightboxImage } from '../ImageLightbox/ImageLightbox.svelte';
|
|
@@ -11,14 +12,18 @@
|
|
|
11
12
|
caption?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
type GalleryLayout = 'grid' | 'featured' | 'masonry';
|
|
15
|
+
export type GalleryLayout = 'grid' | 'featured' | 'masonry' | 'hero';
|
|
15
16
|
|
|
16
17
|
interface ImageGalleryProps {
|
|
17
18
|
images?: GalleryImage[];
|
|
18
19
|
cols?: 2 | 3 | 4;
|
|
19
20
|
layout?: GalleryLayout;
|
|
20
21
|
showCaptions?: boolean;
|
|
22
|
+
/** Footer line under the gallery. Default true. */
|
|
23
|
+
showHint?: boolean;
|
|
24
|
+
hint?: string;
|
|
21
25
|
class?: string;
|
|
26
|
+
empty?: Snippet;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
const {
|
|
@@ -26,7 +31,10 @@
|
|
|
26
31
|
cols = 3,
|
|
27
32
|
layout = 'grid',
|
|
28
33
|
showCaptions = false,
|
|
29
|
-
|
|
34
|
+
showHint = true,
|
|
35
|
+
hint,
|
|
36
|
+
class: className = '',
|
|
37
|
+
empty
|
|
30
38
|
}: ImageGalleryProps = $props();
|
|
31
39
|
|
|
32
40
|
let open = $state(false);
|
|
@@ -51,6 +59,12 @@
|
|
|
51
59
|
: 'grid-cols-2 lg:grid-cols-3'
|
|
52
60
|
);
|
|
53
61
|
|
|
62
|
+
const heroVisible = $derived(images.slice(0, layout === 'hero' && images.length > 4 ? 4 : images.length));
|
|
63
|
+
const heroExtra = $derived(Math.max(0, images.length - heroVisible.length));
|
|
64
|
+
const hintText = $derived(
|
|
65
|
+
hint ?? `${images.length} photos · click to open lightbox`
|
|
66
|
+
);
|
|
67
|
+
|
|
54
68
|
const tones = [
|
|
55
69
|
'from-slate-600 to-slate-800',
|
|
56
70
|
'from-sky-500 to-indigo-700',
|
|
@@ -68,6 +82,19 @@
|
|
|
68
82
|
function markFailed(id: string) {
|
|
69
83
|
failed = { ...failed, [id]: true };
|
|
70
84
|
}
|
|
85
|
+
|
|
86
|
+
const heroGridClass = $derived.by(() => {
|
|
87
|
+
const n = heroVisible.length;
|
|
88
|
+
if (n <= 1) return 'grid-cols-1';
|
|
89
|
+
if (n === 2) return 'grid-cols-2';
|
|
90
|
+
return 'grid-cols-2 grid-rows-2';
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const heroMainSpan = $derived.by(() => {
|
|
94
|
+
const n = heroVisible.length;
|
|
95
|
+
if (n === 3) return 'row-span-2';
|
|
96
|
+
return '';
|
|
97
|
+
});
|
|
71
98
|
</script>
|
|
72
99
|
|
|
73
100
|
{#snippet tile(img: GalleryImage, i: number, ratio = 1, featured = false)}
|
|
@@ -82,10 +109,7 @@
|
|
|
82
109
|
<AspectRatio {ratio}>
|
|
83
110
|
{#if failed[img.id]}
|
|
84
111
|
<div
|
|
85
|
-
class={[
|
|
86
|
-
'flex h-full w-full items-end bg-gradient-to-br p-4',
|
|
87
|
-
tones[i % tones.length]
|
|
88
|
-
]}
|
|
112
|
+
class={['flex h-full w-full items-end bg-gradient-to-br p-4', tones[i % tones.length]]}
|
|
89
113
|
>
|
|
90
114
|
<span class="text-sm font-medium text-white/90">{img.alt}</span>
|
|
91
115
|
</div>
|
|
@@ -119,14 +143,70 @@
|
|
|
119
143
|
</button>
|
|
120
144
|
{/snippet}
|
|
121
145
|
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
{#snippet fillTile(img: GalleryImage, i: number, spanClass = '', extra = 0)}
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
class={[
|
|
150
|
+
'group relative min-h-0 overflow-hidden rounded-xl border border-border bg-surface-overlay text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/40',
|
|
151
|
+
spanClass
|
|
152
|
+
]}
|
|
153
|
+
onclick={() => openAt(i)}
|
|
154
|
+
>
|
|
155
|
+
{#if failed[img.id]}
|
|
156
|
+
<div class={['flex h-full w-full items-end bg-gradient-to-br p-4', tones[i % tones.length]]}>
|
|
157
|
+
<span class="text-sm font-medium text-white/90">{img.alt}</span>
|
|
158
|
+
</div>
|
|
159
|
+
{:else}
|
|
160
|
+
<img
|
|
161
|
+
src={img.src}
|
|
162
|
+
alt={img.alt}
|
|
163
|
+
loading="eager"
|
|
164
|
+
decoding="async"
|
|
165
|
+
class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]"
|
|
166
|
+
onerror={() => markFailed(img.id)}
|
|
167
|
+
/>
|
|
168
|
+
{/if}
|
|
124
169
|
<div
|
|
125
|
-
class="
|
|
170
|
+
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/45 via-transparent to-transparent opacity-0 transition group-hover:opacity-100"
|
|
171
|
+
></div>
|
|
172
|
+
<span
|
|
173
|
+
class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-full bg-black/45 text-white opacity-0 backdrop-blur-sm transition group-hover:opacity-100"
|
|
126
174
|
>
|
|
127
|
-
|
|
175
|
+
<Expand class="h-4 w-4" />
|
|
176
|
+
</span>
|
|
177
|
+
{#if extra > 0}
|
|
178
|
+
<span
|
|
179
|
+
class="absolute inset-0 flex items-center justify-center bg-neutral-950/55 text-lg font-semibold text-white"
|
|
180
|
+
>
|
|
181
|
+
+{extra}
|
|
182
|
+
</span>
|
|
183
|
+
{/if}
|
|
184
|
+
</button>
|
|
185
|
+
{/snippet}
|
|
186
|
+
|
|
187
|
+
<div class={['w-full space-y-3', className]}>
|
|
188
|
+
{#if images.length === 0}
|
|
189
|
+
{#if empty}
|
|
190
|
+
{@render empty()}
|
|
191
|
+
{:else}
|
|
192
|
+
<div
|
|
193
|
+
class="flex min-h-64 w-full items-center justify-center rounded-2xl border border-dashed border-border bg-surface-overlay px-6 text-sm text-muted"
|
|
194
|
+
>
|
|
195
|
+
No images yet
|
|
196
|
+
</div>
|
|
197
|
+
{/if}
|
|
198
|
+
{:else if layout === 'hero'}
|
|
199
|
+
<div class={['grid h-72 gap-2 sm:h-80', heroGridClass]}>
|
|
200
|
+
{#each heroVisible as img, i (img.id)}
|
|
201
|
+
{@render fillTile(
|
|
202
|
+
img,
|
|
203
|
+
i,
|
|
204
|
+
i === 0 ? heroMainSpan : '',
|
|
205
|
+
i === heroVisible.length - 1 ? heroExtra : 0
|
|
206
|
+
)}
|
|
207
|
+
{/each}
|
|
128
208
|
</div>
|
|
129
|
-
{:else if layout === 'featured'
|
|
209
|
+
{:else if layout === 'featured'}
|
|
130
210
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
131
211
|
{@render tile(images[0], 0, 1, true)}
|
|
132
212
|
{#each images.slice(1) as img, i (img.id)}
|
|
@@ -162,7 +242,7 @@
|
|
|
162
242
|
/>
|
|
163
243
|
{/if}
|
|
164
244
|
<span
|
|
165
|
-
class="absolute
|
|
245
|
+
class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded-full bg-black/45 text-white opacity-0 backdrop-blur-sm transition group-hover:opacity-100"
|
|
166
246
|
>
|
|
167
247
|
<Expand class="h-4 w-4" />
|
|
168
248
|
</span>
|
|
@@ -177,8 +257,8 @@
|
|
|
177
257
|
</div>
|
|
178
258
|
{/if}
|
|
179
259
|
|
|
180
|
-
{#if images.length}
|
|
181
|
-
<p class="text-xs text-muted">{
|
|
260
|
+
{#if images.length && showHint}
|
|
261
|
+
<p class="text-xs text-muted">{hintText}</p>
|
|
182
262
|
{/if}
|
|
183
263
|
|
|
184
264
|
{#if lightboxImages.length}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
export interface GalleryImage {
|
|
2
3
|
id: string;
|
|
3
4
|
src: string;
|
|
4
5
|
alt: string;
|
|
5
6
|
caption?: string;
|
|
6
7
|
}
|
|
7
|
-
type GalleryLayout = 'grid' | 'featured' | 'masonry';
|
|
8
|
+
export type GalleryLayout = 'grid' | 'featured' | 'masonry' | 'hero';
|
|
8
9
|
interface ImageGalleryProps {
|
|
9
10
|
images?: GalleryImage[];
|
|
10
11
|
cols?: 2 | 3 | 4;
|
|
11
12
|
layout?: GalleryLayout;
|
|
12
13
|
showCaptions?: boolean;
|
|
14
|
+
/** Footer line under the gallery. Default true. */
|
|
15
|
+
showHint?: boolean;
|
|
16
|
+
hint?: string;
|
|
13
17
|
class?: string;
|
|
18
|
+
empty?: Snippet;
|
|
14
19
|
}
|
|
15
20
|
declare const ImageGallery: import("svelte").Component<ImageGalleryProps, {}, "">;
|
|
16
21
|
type ImageGallery = ReturnType<typeof ImageGallery>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { GalleryImage } from './ImageGallery.svelte';
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
|
-
variant?: 'default' | 'featured' | 'masonry';
|
|
6
|
+
variant?: 'default' | 'featured' | 'masonry' | 'hero';
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
let { variant = 'default' }: Props = $props();
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
<ImageGallery {images} layout="featured" showCaptions cols={4} class="w-full" />
|
|
61
61
|
{:else if variant === 'masonry'}
|
|
62
62
|
<ImageGallery {images} layout="masonry" showCaptions class="w-full" />
|
|
63
|
+
{:else if variant === 'hero'}
|
|
64
|
+
<ImageGallery {images} layout="hero" class="w-full" />
|
|
63
65
|
{:else}
|
|
64
66
|
<ImageGallery {images} cols={3} showCaptions class="w-full" />
|
|
65
67
|
{/if}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import Badge from '../../atoms/Badge/Badge.svelte';
|
|
6
6
|
|
|
7
7
|
export type StatCardTrend = 'up' | 'down' | 'neutral';
|
|
8
|
+
export type StatCardSize = 'sm' | 'md';
|
|
8
9
|
|
|
9
10
|
interface StatCardProps {
|
|
10
11
|
label: string;
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
badge?: string;
|
|
16
17
|
badgeVariant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
17
18
|
variant?: CardVariant;
|
|
19
|
+
size?: StatCardSize;
|
|
18
20
|
class?: string;
|
|
19
21
|
icon?: Snippet;
|
|
20
22
|
onclick?: (e: MouseEvent) => void;
|
|
@@ -29,39 +31,70 @@
|
|
|
29
31
|
badge,
|
|
30
32
|
badgeVariant = 'primary',
|
|
31
33
|
variant = 'default',
|
|
34
|
+
size = 'md',
|
|
32
35
|
class: className = '',
|
|
33
36
|
icon,
|
|
34
37
|
onclick
|
|
35
38
|
}: StatCardProps = $props();
|
|
39
|
+
|
|
40
|
+
const compact = $derived(size === 'sm');
|
|
36
41
|
</script>
|
|
37
42
|
|
|
38
|
-
<Card
|
|
39
|
-
|
|
43
|
+
<Card
|
|
44
|
+
{variant}
|
|
45
|
+
padding={compact ? 'sm' : 'md'}
|
|
46
|
+
hoverable={!!onclick}
|
|
47
|
+
{onclick}
|
|
48
|
+
class={className}
|
|
49
|
+
chrome={false}
|
|
50
|
+
>
|
|
51
|
+
<div class="gap-3 flex items-start justify-between">
|
|
40
52
|
<div class="min-w-0 space-y-1">
|
|
41
|
-
<div class="flex flex-wrap items-center
|
|
42
|
-
<p class=
|
|
53
|
+
<div class="gap-2 flex flex-wrap items-center">
|
|
54
|
+
<p class={['font-medium text-secondary', compact ? 'text-xs' : 'text-sm']}>{label}</p>
|
|
43
55
|
{#if badge}
|
|
44
56
|
<Badge variant={badgeVariant} size="sm">{badge}</Badge>
|
|
45
57
|
{/if}
|
|
46
58
|
</div>
|
|
47
|
-
<p
|
|
59
|
+
<p
|
|
60
|
+
class={[
|
|
61
|
+
'min-w-0 font-semibold tracking-tight text-primary',
|
|
62
|
+
compact ? 'text-lg leading-snug' : 'text-2xl'
|
|
63
|
+
]}
|
|
64
|
+
>
|
|
65
|
+
{value}
|
|
66
|
+
</p>
|
|
48
67
|
{#if delta || description}
|
|
49
|
-
<div class="flex flex-wrap items-center
|
|
68
|
+
<div class="gap-2 text-xs flex flex-wrap items-center">
|
|
50
69
|
{#if delta}
|
|
51
70
|
<span
|
|
52
71
|
class={[
|
|
53
|
-
'
|
|
72
|
+
'gap-0.5 font-medium inline-flex items-center',
|
|
54
73
|
trend === 'up' && 'text-green-600 dark:text-green-400',
|
|
55
74
|
trend === 'down' && 'text-red-600 dark:text-red-400',
|
|
56
75
|
trend === 'neutral' && 'text-muted'
|
|
57
76
|
]}
|
|
58
77
|
>
|
|
59
78
|
{#if trend === 'up'}
|
|
60
|
-
<svg
|
|
79
|
+
<svg
|
|
80
|
+
class="h-3.5 w-3.5"
|
|
81
|
+
viewBox="0 0 24 24"
|
|
82
|
+
fill="none"
|
|
83
|
+
stroke="currentColor"
|
|
84
|
+
stroke-width="2.5"
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
>
|
|
61
87
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7" />
|
|
62
88
|
</svg>
|
|
63
89
|
{:else if trend === 'down'}
|
|
64
|
-
<svg
|
|
90
|
+
<svg
|
|
91
|
+
class="h-3.5 w-3.5"
|
|
92
|
+
viewBox="0 0 24 24"
|
|
93
|
+
fill="none"
|
|
94
|
+
stroke="currentColor"
|
|
95
|
+
stroke-width="2.5"
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
>
|
|
65
98
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
|
66
99
|
</svg>
|
|
67
100
|
{/if}
|
|
@@ -76,7 +109,10 @@
|
|
|
76
109
|
</div>
|
|
77
110
|
{#if icon}
|
|
78
111
|
<div
|
|
79
|
-
class=
|
|
112
|
+
class={[
|
|
113
|
+
'rounded-xl bg-brand-50 text-brand-600 dark:bg-brand-950/60 dark:text-brand-400 flex shrink-0 items-center justify-center',
|
|
114
|
+
compact ? 'h-8 w-8' : 'h-10 w-10'
|
|
115
|
+
]}
|
|
80
116
|
>
|
|
81
117
|
{@render icon()}
|
|
82
118
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { CardVariant } from '../Card/Card.svelte';
|
|
3
3
|
export type StatCardTrend = 'up' | 'down' | 'neutral';
|
|
4
|
+
export type StatCardSize = 'sm' | 'md';
|
|
4
5
|
interface StatCardProps {
|
|
5
6
|
label: string;
|
|
6
7
|
value: string | number;
|
|
@@ -10,6 +11,7 @@ interface StatCardProps {
|
|
|
10
11
|
badge?: string;
|
|
11
12
|
badgeVariant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
12
13
|
variant?: CardVariant;
|
|
14
|
+
size?: StatCardSize;
|
|
13
15
|
class?: string;
|
|
14
16
|
icon?: Snippet;
|
|
15
17
|
onclick?: (e: MouseEvent) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -360,7 +360,7 @@ export { default as OfflineBanner } from './components/molecules/OfflineBanner/O
|
|
|
360
360
|
export { default as UnsavedChanges } from './components/molecules/UnsavedChanges/UnsavedChanges.svelte';
|
|
361
361
|
export { default as ErrorBoundary } from './components/molecules/ErrorBoundary/ErrorBoundary.svelte';
|
|
362
362
|
export { default as StatCard } from './components/molecules/StatCard/StatCard.svelte';
|
|
363
|
-
export type { StatCardTrend } from './components/molecules/StatCard/StatCard.svelte';
|
|
363
|
+
export type { StatCardTrend, StatCardSize } from './components/molecules/StatCard/StatCard.svelte';
|
|
364
364
|
export { default as MediaCard } from './components/molecules/MediaCard/MediaCard.svelte';
|
|
365
365
|
export { default as FormActions } from './components/molecules/FormActions/FormActions.svelte';
|
|
366
366
|
export { default as FormSection } from './components/molecules/FormSection/FormSection.svelte';
|
|
@@ -465,7 +465,7 @@ export type { CalendarEvent, CalendarEventColor } from './components/molecules/E
|
|
|
465
465
|
export { default as CompareSlider } from './components/molecules/CompareSlider/CompareSlider.svelte';
|
|
466
466
|
export { default as PdfViewer } from './components/molecules/PdfViewer/PdfViewer.svelte';
|
|
467
467
|
export { default as ImageGallery } from './components/molecules/ImageGallery/ImageGallery.svelte';
|
|
468
|
-
export type { GalleryImage } from './components/molecules/ImageGallery/ImageGallery.svelte';
|
|
468
|
+
export type { GalleryImage, GalleryLayout } from './components/molecules/ImageGallery/ImageGallery.svelte';
|
|
469
469
|
export { default as LogViewer } from './components/molecules/LogViewer/LogViewer.svelte';
|
|
470
470
|
export type { LogEntry, LogLevel } from './components/molecules/LogViewer/LogViewer.svelte';
|
|
471
471
|
export { default as PriceTag } from './components/molecules/PriceTag/PriceTag.svelte';
|