@svadmin/lite 0.3.24 → 0.3.25

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.
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ src?: string | null;
4
+ alt?: string;
5
+ height?: number;
6
+ muted?: boolean;
7
+ emptyLabel?: string;
8
+ errorLabel?: string;
9
+ }
10
+
11
+ let { src = null, alt = '', height = 100, muted = false, emptyLabel = 'No image', errorLabel = 'Image unavailable' }: Props = $props();
12
+ let state = $state<'loading' | 'loaded' | 'error'>('loading');
13
+
14
+ $effect(() => {
15
+ if (src) state = 'loading';
16
+ });
17
+ </script>
18
+
19
+ {#if src}
20
+ <span class="lite-media-thumbnail" data-lite-media-state={state} style={`height:${height}px;${muted ? 'opacity:0.6;' : ''}`}>
21
+ <img {src} {alt} onload={() => state = 'loaded'} onerror={() => state = 'error'} class:lite-media-hidden={state !== 'loaded'} />
22
+ {#if state === 'loading'}<span class="lite-media-status" role="status">Loading image</span>{/if}
23
+ {#if state === 'error'}<span class="lite-media-status" role="img" aria-label={errorLabel}>{errorLabel}</span>{/if}
24
+ </span>
25
+ {:else}
26
+ <span class="lite-media-empty" data-lite-media-state="empty">{emptyLabel}</span>
27
+ {/if}
28
+
29
+ <style>
30
+ .lite-media-thumbnail { position: relative; display: inline-flex; max-width: 300px; align-items: center; justify-content: center; overflow: hidden; border: 1px solid #e2e8f0; border-radius: 6px; background: #f8fafc; }
31
+ .lite-media-thumbnail img { display: block; width: auto; max-width: 100%; height: 100%; object-fit: contain; }
32
+ .lite-media-hidden { visibility: hidden; }
33
+ .lite-media-status { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 8px; color: #64748b; font-size: 12px; text-align: center; }
34
+ .lite-media-empty { color: #64748b; font-size: 12px; }
35
+ </style>
@@ -0,0 +1,11 @@
1
+ interface Props {
2
+ src?: string | null;
3
+ alt?: string;
4
+ height?: number;
5
+ muted?: boolean;
6
+ emptyLabel?: string;
7
+ errorLabel?: string;
8
+ }
9
+ declare const LiteMediaThumbnail: import("svelte").Component<Props, {}, "">;
10
+ type LiteMediaThumbnail = ReturnType<typeof LiteMediaThumbnail>;
11
+ export default LiteMediaThumbnail;
@@ -6,6 +6,7 @@
6
6
  import type { FieldDefinition } from '@svadmin/core';
7
7
  import { toSafeHref, toSafeText } from '../security';
8
8
  import { isExplicitBooleanTrue } from '../value-normalization';
9
+ import LiteMediaThumbnail from './LiteMediaThumbnail.svelte';
9
10
 
10
11
  interface Props {
11
12
  field: FieldDefinition;
@@ -52,7 +53,7 @@
52
53
  {toSafeText(value)}
53
54
  {/if}
54
55
  {:else if field.type === 'image' && value}
55
- <img src={String(value)} alt={field.label} style="max-width:300px;max-height:200px;border-radius:6px;border:1px solid #e2e8f0;" />
56
+ <LiteMediaThumbnail src={String(value)} alt={field.label} height={200} />
56
57
  {:else if field.type === 'tags' && Array.isArray(value)}
57
58
  {#each value as tag, _i (_i)}
58
59
  <span class="lite-badge">{tag}</span>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { FieldDefinition } from '@svadmin/core';
3
+ import LiteMediaThumbnail from '../LiteMediaThumbnail.svelte';
3
4
 
4
5
  interface Props {
5
6
  field: FieldDefinition;
@@ -23,7 +24,7 @@
23
24
  {@const urls = getUrls(value)}
24
25
  <div class="lite-inline-md lite-flex-wrap">
25
26
  {#each urls as url, _i (_i)}
26
- <img src={url} alt={field.label} style="max-height: 100px; border-radius: 4px; border: 1px solid #e5e7eb;" />
27
+ <LiteMediaThumbnail src={url} alt={field.label} height={100} />
27
28
  {:else}
28
29
  <span>—</span>
29
30
  {/each}
@@ -34,7 +35,7 @@
34
35
  {#if urls.length > 0}
35
36
  <div class="lite-inline-md lite-flex-wrap" style="margin-bottom: 8px;">
36
37
  {#each urls as url, _i (_i)}
37
- <img src={url} alt="Current" style="height: 60px; border-radius: 4px; border: 1px solid #e5e7eb; opacity: 0.6;" />
38
+ <LiteMediaThumbnail src={url} alt="Current" height={60} muted />
38
39
  {/each}
39
40
  </div>
40
41
  {/if}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { default as LiteConfirmDialog } from './components/LiteConfirmDialog.sve
18
18
  export { default as LiteEmptyState } from './components/LiteEmptyState.svelte';
19
19
  export { default as LiteTabs } from './components/LiteTabs.svelte';
20
20
  export { default as LiteShowField } from './components/LiteShowField.svelte';
21
+ export { default as LiteMediaThumbnail } from './components/LiteMediaThumbnail.svelte';
21
22
  export { default as LiteChatDialog } from './components/LiteChatDialog.svelte';
22
23
  export * from './components/buttons/index';
23
24
  export * from './components/fields/index';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export { default as LiteConfirmDialog } from './components/LiteConfirmDialog.sve
22
22
  export { default as LiteEmptyState } from './components/LiteEmptyState.svelte';
23
23
  export { default as LiteTabs } from './components/LiteTabs.svelte';
24
24
  export { default as LiteShowField } from './components/LiteShowField.svelte';
25
+ export { default as LiteMediaThumbnail } from './components/LiteMediaThumbnail.svelte';
25
26
  export { default as LiteChatDialog } from './components/LiteChatDialog.svelte';
26
27
  // Action Buttons
27
28
  export * from './components/buttons/index';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/lite",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "SSR-first lightweight admin UI for @svadmin with optional progressive enhancement",
5
5
  "type": "module",
6
6
  "sideEffects": [