@svadmin/lite 0.25.7 → 0.27.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.
Files changed (61) hide show
  1. package/README.md +11 -5
  2. package/dist/components/LiteAnnouncementBanner.svelte +88 -0
  3. package/dist/components/LiteAnnouncementBanner.svelte.d.ts +12 -0
  4. package/dist/components/LiteAsyncExportModal.svelte +113 -0
  5. package/dist/components/LiteAsyncExportModal.svelte.d.ts +14 -0
  6. package/dist/components/LiteAuditLog.svelte +102 -36
  7. package/dist/components/LiteAuditLog.svelte.d.ts +6 -3
  8. package/dist/components/LiteConcurrentConflictModal.svelte +110 -0
  9. package/dist/components/LiteConcurrentConflictModal.svelte.d.ts +21 -0
  10. package/dist/components/LiteContextMenu.svelte +114 -0
  11. package/dist/components/LiteContextMenu.svelte.d.ts +18 -0
  12. package/dist/components/LiteCronExpressionBuilder.svelte +78 -0
  13. package/dist/components/LiteCronExpressionBuilder.svelte.d.ts +17 -0
  14. package/dist/components/LiteDualApprovalGate.svelte +58 -0
  15. package/dist/components/LiteDualApprovalGate.svelte.d.ts +11 -0
  16. package/dist/components/LiteJsonViewer.svelte +88 -0
  17. package/dist/components/LiteJsonViewer.svelte.d.ts +9 -0
  18. package/dist/components/LiteKeyboardShortcutsModal.svelte +56 -0
  19. package/dist/components/LiteKeyboardShortcutsModal.svelte.d.ts +13 -0
  20. package/dist/components/LiteKpiCard.svelte +169 -0
  21. package/dist/components/LiteKpiCard.svelte.d.ts +25 -0
  22. package/dist/components/LiteMetricGrid.svelte +143 -0
  23. package/dist/components/LiteMetricGrid.svelte.d.ts +31 -0
  24. package/dist/components/LiteMicroAppFrame.svelte +57 -0
  25. package/dist/components/LiteMicroAppFrame.svelte.d.ts +15 -0
  26. package/dist/components/LiteOrgChart.svelte +78 -0
  27. package/dist/components/LiteOrgChart.svelte.d.ts +17 -0
  28. package/dist/components/LitePresenceAvatarGroup.svelte +118 -15
  29. package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +8 -3
  30. package/dist/components/LiteResultPage.svelte +135 -0
  31. package/dist/components/LiteResultPage.svelte.d.ts +17 -0
  32. package/dist/components/LiteSafeDeleteConfirm.svelte +86 -0
  33. package/dist/components/LiteSafeDeleteConfirm.svelte.d.ts +21 -0
  34. package/dist/components/LiteSensitiveDataMask.svelte +34 -2
  35. package/dist/components/LiteSensitiveDataMask.svelte.d.ts +1 -2
  36. package/dist/components/LiteSignaturePad.svelte +3 -1
  37. package/dist/components/LiteSignaturePad.svelte.d.ts +1 -0
  38. package/dist/components/LiteStepWizard.svelte +98 -0
  39. package/dist/components/LiteStepWizard.svelte.d.ts +16 -0
  40. package/dist/components/LiteTaskCenter.svelte +133 -0
  41. package/dist/components/LiteTaskCenter.svelte.d.ts +24 -0
  42. package/dist/components/LiteTextDiffViewer.svelte +111 -0
  43. package/dist/components/LiteTextDiffViewer.svelte.d.ts +10 -0
  44. package/dist/components/LiteTour.svelte +4 -0
  45. package/dist/components/LiteTour.svelte.d.ts +4 -0
  46. package/dist/components/LiteTrendIndicator.svelte +119 -0
  47. package/dist/components/LiteTrendIndicator.svelte.d.ts +19 -0
  48. package/dist/components/LiteUnsavedChangesGuard.svelte +53 -0
  49. package/dist/components/LiteUnsavedChangesGuard.svelte.d.ts +10 -0
  50. package/dist/components/LiteVirtualTreeSelect.svelte +92 -0
  51. package/dist/components/LiteVirtualTreeSelect.svelte.d.ts +20 -0
  52. package/dist/components/LiteWatermarkContainer.svelte +58 -0
  53. package/dist/components/LiteWatermarkContainer.svelte.d.ts +9 -0
  54. package/dist/components/LiteWorkflowStepper.svelte +56 -0
  55. package/dist/components/LiteWorkflowStepper.svelte.d.ts +14 -0
  56. package/dist/components/widgets/LiteGaugeChart.svelte +50 -3
  57. package/dist/components/widgets/LiteGaugeChart.svelte.d.ts +12 -0
  58. package/dist/index.d.ts +31 -0
  59. package/dist/index.js +24 -0
  60. package/dist/rich-content.js +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,143 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import LiteStatsCard from './LiteStatsCard.svelte';
4
+
5
+ type MetricColumns = 1 | 2 | 3 | 4 | 5 | 6 | 'auto';
6
+ type MetricGap = 'sm' | 'md' | 'lg';
7
+
8
+ interface LiteMetricGridItem {
9
+ id?: string;
10
+ label: string;
11
+ value: string | number;
12
+ description?: string;
13
+ trend?: 'up' | 'down' | 'neutral';
14
+ trendValue?: string;
15
+ icon?: string;
16
+ href?: string;
17
+ }
18
+
19
+ interface Props {
20
+ items?: LiteMetricGridItem[];
21
+ cols?: MetricColumns;
22
+ gap?: MetricGap;
23
+ title?: string;
24
+ description?: string;
25
+ loading?: boolean;
26
+ skeletonCount?: number;
27
+ empty?: boolean;
28
+ emptyMessage?: string;
29
+ class?: string;
30
+ actions?: Snippet;
31
+ children?: Snippet;
32
+ emptyContent?: Snippet;
33
+ }
34
+
35
+ let {
36
+ items = [],
37
+ cols = 'auto',
38
+ gap = 'md',
39
+ title,
40
+ description,
41
+ loading = false,
42
+ skeletonCount = 4,
43
+ empty = false,
44
+ emptyMessage = 'No metrics available',
45
+ class: className = '',
46
+ actions,
47
+ children,
48
+ emptyContent,
49
+ }: Props = $props();
50
+
51
+ const skeletonItems = $derived(Array.from({ length: Math.max(1, skeletonCount) }, (_, index) => index));
52
+ </script>
53
+
54
+ <section
55
+ class="lite-metric-grid lite-metric-grid-{cols} lite-metric-grid-gap-{gap} {className}"
56
+ aria-label={title ?? 'Dashboard metrics'}
57
+ data-columns={cols}
58
+ >
59
+ {#if title || description || actions}
60
+ <header class="lite-metric-grid-header">
61
+ <div>
62
+ {#if title}<h2>{title}</h2>{/if}
63
+ {#if description}<p>{description}</p>{/if}
64
+ </div>
65
+ {#if actions}<div class="lite-metric-grid-actions">{@render actions()}</div>{/if}
66
+ </header>
67
+ {/if}
68
+
69
+ {#if loading}
70
+ <div class="lite-metric-grid-items">
71
+ {#each skeletonItems as item (item)}
72
+ <div class="lite-metric-grid-cell">
73
+ <div class="lite-metric-grid-skeleton" aria-hidden="true">
74
+ <span></span><span></span><span></span>
75
+ </div>
76
+ </div>
77
+ {/each}
78
+ </div>
79
+ {:else if empty}
80
+ <div class="lite-metric-grid-empty" role="status">
81
+ {#if emptyContent}{@render emptyContent()}{:else}<p>{emptyMessage}</p>{/if}
82
+ </div>
83
+ {:else if children}
84
+ {@render children()}
85
+ {:else if items.length === 0}
86
+ <div class="lite-metric-grid-empty" role="status"><p>{emptyMessage}</p></div>
87
+ {:else}
88
+ <div class="lite-metric-grid-items">
89
+ {#each items as item (item.id ?? item.label)}
90
+ <div class="lite-metric-grid-cell">
91
+ <LiteStatsCard
92
+ label={item.label}
93
+ value={item.value}
94
+ description={item.description}
95
+ trend={item.trend}
96
+ trendValue={item.trendValue}
97
+ icon={item.icon}
98
+ href={item.href}
99
+ />
100
+ </div>
101
+ {/each}
102
+ </div>
103
+ {/if}
104
+ </section>
105
+
106
+ <style>
107
+ .lite-metric-grid { display: block; }
108
+ .lite-metric-grid-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 12px; }
109
+ .lite-metric-grid-header > div + div { margin-left: 12px; }
110
+ .lite-metric-grid-header h2 { margin: 0; color: #1d2129; font-size: 16px; }
111
+ .lite-metric-grid-header p { margin: 4px 0 0; color: #4e5969; font-size: 12px; }
112
+ .lite-metric-grid-actions { display: flex; }
113
+ .lite-metric-grid-actions > * + * { margin-left: 8px; }
114
+ .lite-metric-grid-items { display: flex; flex-wrap: wrap; margin: 0 -8px; }
115
+ .lite-metric-grid-cell { box-sizing: border-box; width: 100%; padding: 0 8px; margin-bottom: 16px; }
116
+ .lite-metric-grid-gap-sm .lite-metric-grid-items { margin: 0 -6px; }
117
+ .lite-metric-grid-gap-sm .lite-metric-grid-cell { padding: 0 6px; margin-bottom: 12px; }
118
+ .lite-metric-grid-gap-lg .lite-metric-grid-items { margin: 0 -12px; }
119
+ .lite-metric-grid-gap-lg .lite-metric-grid-cell { padding: 0 12px; margin-bottom: 24px; }
120
+ .lite-metric-grid-auto .lite-metric-grid-cell { width: 25%; }
121
+ .lite-metric-grid-1 .lite-metric-grid-cell { width: 100%; }
122
+ .lite-metric-grid-2 .lite-metric-grid-cell { width: 50%; }
123
+ .lite-metric-grid-3 .lite-metric-grid-cell { width: 33.3333%; }
124
+ .lite-metric-grid-4 .lite-metric-grid-cell { width: 25%; }
125
+ .lite-metric-grid-5 .lite-metric-grid-cell { width: 20%; }
126
+ .lite-metric-grid-6 .lite-metric-grid-cell { width: 16.6666%; }
127
+ .lite-metric-grid-skeleton { min-height: 96px; border: 1px solid #e5e6eb; border-radius: 6px; padding: 16px; background: #f7f8fa; }
128
+ .lite-metric-grid-skeleton span { display: block; height: 10px; margin-bottom: 10px; border-radius: 3px; background: #e5e6eb; }
129
+ .lite-metric-grid-skeleton span:first-child { width: 45%; }
130
+ .lite-metric-grid-skeleton span:nth-child(2) { width: 70%; height: 20px; }
131
+ .lite-metric-grid-skeleton span:last-child { width: 35%; margin-bottom: 0; }
132
+ .lite-metric-grid-empty { border: 1px dashed #e5e6eb; border-radius: 6px; padding: 24px; color: #86909c; text-align: center; background: #f7f8fa; }
133
+ @media (max-width: 900px) {
134
+ .lite-metric-grid-auto .lite-metric-grid-cell,
135
+ .lite-metric-grid-3 .lite-metric-grid-cell,
136
+ .lite-metric-grid-4 .lite-metric-grid-cell,
137
+ .lite-metric-grid-5 .lite-metric-grid-cell,
138
+ .lite-metric-grid-6 .lite-metric-grid-cell { width: 50%; }
139
+ }
140
+ @media (max-width: 560px) {
141
+ .lite-metric-grid-cell { width: 100% !important; }
142
+ }
143
+ </style>
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from 'svelte';
2
+ type MetricColumns = 1 | 2 | 3 | 4 | 5 | 6 | 'auto';
3
+ type MetricGap = 'sm' | 'md' | 'lg';
4
+ interface LiteMetricGridItem {
5
+ id?: string;
6
+ label: string;
7
+ value: string | number;
8
+ description?: string;
9
+ trend?: 'up' | 'down' | 'neutral';
10
+ trendValue?: string;
11
+ icon?: string;
12
+ href?: string;
13
+ }
14
+ interface Props {
15
+ items?: LiteMetricGridItem[];
16
+ cols?: MetricColumns;
17
+ gap?: MetricGap;
18
+ title?: string;
19
+ description?: string;
20
+ loading?: boolean;
21
+ skeletonCount?: number;
22
+ empty?: boolean;
23
+ emptyMessage?: string;
24
+ class?: string;
25
+ actions?: Snippet;
26
+ children?: Snippet;
27
+ emptyContent?: Snippet;
28
+ }
29
+ declare const LiteMetricGrid: import("svelte").Component<Props, {}, "">;
30
+ type LiteMetricGrid = ReturnType<typeof LiteMetricGrid>;
31
+ export default LiteMetricGrid;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import type { HTMLIframeAttributes } from 'svelte/elements';
3
+
4
+ interface Props {
5
+ src: string;
6
+ title?: string;
7
+ height?: string | number;
8
+ sandbox?: string;
9
+ allow?: string;
10
+ referrerpolicy?: HTMLIframeAttributes['referrerpolicy'];
11
+ fallbackHref?: string;
12
+ fallbackLabel?: string;
13
+ class?: string;
14
+ }
15
+
16
+ let {
17
+ src,
18
+ title = 'Embedded application',
19
+ height = '650px',
20
+ sandbox = 'allow-scripts allow-forms allow-same-origin',
21
+ allow,
22
+ referrerpolicy = 'strict-origin-when-cross-origin',
23
+ fallbackHref,
24
+ fallbackLabel = 'Open in a new tab',
25
+ class: className = '',
26
+ }: Props = $props();
27
+
28
+ const frameHeight = $derived(typeof height === 'number' ? `${height}px` : height);
29
+ </script>
30
+
31
+ <section class="lite-micro-app-frame lite-card {className}" aria-label={title}>
32
+ <header>
33
+ <h2>{title}</h2>
34
+ {#if fallbackHref}<a href={fallbackHref} target="_blank" rel="noopener noreferrer">{fallbackLabel}</a>{/if}
35
+ </header>
36
+
37
+ <iframe
38
+ {src}
39
+ {title}
40
+ {sandbox}
41
+ {allow}
42
+ {referrerpolicy}
43
+ style={`height:${frameHeight};`}
44
+ loading="lazy"
45
+ ></iframe>
46
+ <p class="lite-micro-app-help">Embedded messaging, theme sync and automatic height are client-side enhancements.</p>
47
+ </section>
48
+
49
+ <style>
50
+ .lite-micro-app-frame { padding: 12px; }
51
+ .lite-micro-app-frame header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
52
+ .lite-micro-app-frame header a { margin-left: 12px; }
53
+ .lite-micro-app-frame h2 { margin: 0; color: #1d2129; font-size: 15px; }
54
+ .lite-micro-app-frame header a { color: #165dff; font-size: 12px; }
55
+ .lite-micro-app-frame iframe { display: block; width: 100%; border: 1px solid #e5e6eb; border-radius: 4px; background: #fff; }
56
+ .lite-micro-app-help { margin: 6px 0 0; color: #86909c; font-size: 11px; }
57
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { HTMLIframeAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ src: string;
4
+ title?: string;
5
+ height?: string | number;
6
+ sandbox?: string;
7
+ allow?: string;
8
+ referrerpolicy?: HTMLIframeAttributes['referrerpolicy'];
9
+ fallbackHref?: string;
10
+ fallbackLabel?: string;
11
+ class?: string;
12
+ }
13
+ declare const LiteMicroAppFrame: import("svelte").Component<Props, {}, "">;
14
+ type LiteMicroAppFrame = ReturnType<typeof LiteMicroAppFrame>;
15
+ export default LiteMicroAppFrame;
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ export interface LiteOrgNode {
3
+ id: string;
4
+ name: string;
5
+ title?: string;
6
+ department?: string;
7
+ href?: string;
8
+ children?: LiteOrgNode[];
9
+ }
10
+
11
+ interface Props {
12
+ data?: LiteOrgNode | LiteOrgNode[];
13
+ title?: string;
14
+ emptyText?: string;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ data = [],
20
+ title = 'Organization chart',
21
+ emptyText = 'No organization data',
22
+ class: className = '',
23
+ }: Props = $props();
24
+
25
+ interface FlatNode {
26
+ node: LiteOrgNode;
27
+ depth: number;
28
+ }
29
+
30
+ function flatten(nodes: LiteOrgNode[], depth = 0, out: FlatNode[] = []): FlatNode[] {
31
+ for (const node of nodes) {
32
+ out.push({ node, depth });
33
+ if (node.children?.length) flatten(node.children, depth + 1, out);
34
+ }
35
+ return out;
36
+ }
37
+
38
+ const rows = $derived(flatten(Array.isArray(data) ? data : [data]));
39
+ </script>
40
+
41
+ <section class="lite-org-chart lite-card {className}" aria-label={title}>
42
+ <h2>{title}</h2>
43
+ {#if rows.length === 0}
44
+ <p class="lite-org-empty">{emptyText}</p>
45
+ {:else}
46
+ <table class="lite-table">
47
+ <thead>
48
+ <tr>
49
+ <th>Name</th>
50
+ <th>Title</th>
51
+ <th>Department</th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ {#each rows as row (row.node.id)}
56
+ <tr data-depth={row.depth}>
57
+ <td>
58
+ <span class="lite-org-indent" style={`padding-left:${row.depth * 16}px`}>
59
+ {#if row.depth > 0}<span class="lite-org-branch" aria-hidden="true">└</span>{/if}
60
+ {#if row.node.href}<a href={row.node.href}>{row.node.name}</a>{:else}{row.node.name}{/if}
61
+ </span>
62
+ </td>
63
+ <td>{row.node.title ?? '—'}</td>
64
+ <td>{row.node.department ?? '—'}</td>
65
+ </tr>
66
+ {/each}
67
+ </tbody>
68
+ </table>
69
+ {/if}
70
+ </section>
71
+
72
+ <style>
73
+ .lite-org-chart { padding: 16px; }
74
+ .lite-org-chart h2 { margin: 0 0 12px; color: #1d2129; font-size: 16px; }
75
+ .lite-org-empty { margin: 0; padding: 20px; color: #86909c; text-align: center; }
76
+ .lite-org-indent { display: inline-block; }
77
+ .lite-org-branch { display: inline-block; margin-right: 4px; color: #86909c; }
78
+ </style>
@@ -0,0 +1,17 @@
1
+ export interface LiteOrgNode {
2
+ id: string;
3
+ name: string;
4
+ title?: string;
5
+ department?: string;
6
+ href?: string;
7
+ children?: LiteOrgNode[];
8
+ }
9
+ interface Props {
10
+ data?: LiteOrgNode | LiteOrgNode[];
11
+ title?: string;
12
+ emptyText?: string;
13
+ class?: string;
14
+ }
15
+ declare const LiteOrgChart: import("svelte").Component<Props, {}, "">;
16
+ type LiteOrgChart = ReturnType<typeof LiteOrgChart>;
17
+ export default LiteOrgChart;
@@ -1,55 +1,158 @@
1
1
  <script lang="ts">
2
- interface LitePresenceUser {
2
+ export interface LitePresenceUser {
3
3
  id: string;
4
4
  name: string;
5
- status?: 'online' | 'idle' | 'editing';
5
+ avatar?: string;
6
+ role?: string;
7
+ status?: 'online' | 'viewing' | 'idle' | 'editing';
6
8
  }
7
9
 
8
10
  interface Props {
9
11
  users?: LitePresenceUser[];
10
12
  label?: string;
11
- /** Caller class merged with the built-in class list. */
13
+ maxVisible?: number;
14
+ maxCount?: number;
15
+ size?: 'sm' | 'md' | 'lg';
16
+ showStatusDot?: boolean;
12
17
  class?: string;
13
18
  }
14
19
 
15
20
  let {
16
21
  users = [],
17
22
  label = 'Viewing now',
18
- class: className = ''
23
+ maxVisible,
24
+ maxCount,
25
+ size = 'md',
26
+ showStatusDot = true,
27
+ class: className = '',
19
28
  }: Props = $props();
29
+
30
+ const limit = $derived(Math.max(0, maxVisible ?? maxCount ?? 4));
31
+ const visible = $derived(users.slice(0, limit));
32
+ const hidden = $derived(users.slice(limit));
33
+
34
+ function initials(name: string): string {
35
+ return name
36
+ .trim()
37
+ .split(/\s+/)
38
+ .map((part) => part[0] ?? '')
39
+ .join('')
40
+ .slice(0, 2)
41
+ .toUpperCase() || 'U';
42
+ }
43
+
44
+ function statusLabel(status: LitePresenceUser['status']): string {
45
+ return status ?? 'online';
46
+ }
20
47
  </script>
21
48
 
22
- <div class="lite-presence-container {className}">
49
+ <div class="lite-presence-container lite-presence-{size} {className}">
23
50
  <span class="lite-presence-label">{label}:</span>
24
- {#each users as user (user.id)}
25
- <span class="lite-presence-badge" title="{user.name} ({user.status ?? 'online'})">
26
- 👤 {user.name}
51
+
52
+ {#each visible as user (user.id)}
53
+ <span
54
+ class="lite-presence-badge"
55
+ title={`${user.name}${user.role ? ` (${user.role})` : ''} · ${statusLabel(user.status)}`}
56
+ data-presence-user={user.id}
57
+ data-status={statusLabel(user.status)}
58
+ >
59
+ {#if user.avatar}
60
+ <img src={user.avatar} alt="" class="lite-presence-avatar" />
61
+ {:else}
62
+ <span class="lite-presence-initials" aria-hidden="true">{initials(user.name)}</span>
63
+ {/if}
64
+ <span class="lite-presence-name">{user.name}</span>
65
+ {#if showStatusDot && user.status}
66
+ <span class="lite-presence-dot" aria-hidden="true"></span>
67
+ {/if}
27
68
  </span>
28
69
  {/each}
70
+
71
+ {#if hidden.length > 0}
72
+ <span
73
+ class="lite-presence-overflow"
74
+ title={hidden.map((user) => user.name).join(', ')}
75
+ aria-label={`+${hidden.length} more users`}
76
+ >
77
+ +{hidden.length}
78
+ </span>
79
+ {/if}
80
+
29
81
  {#if users.length === 0}
30
- <span style="color: #86909C; font-size: 11px;">No active viewers</span>
82
+ <span class="lite-presence-empty">No active viewers</span>
31
83
  {/if}
32
84
  </div>
33
85
 
34
86
  <style>
35
87
  .lite-presence-container {
36
88
  display: inline-flex;
89
+ flex-wrap: wrap;
37
90
  align-items: center;
38
91
  font-size: 12px;
39
92
  margin-bottom: 8px;
40
93
  }
41
94
  .lite-presence-label {
42
- color: #4E5969;
43
- margin-right: 6px;
95
+ color: #4e5969;
96
+ margin-right: 2px;
44
97
  font-weight: 500;
45
98
  }
46
99
  .lite-presence-badge {
100
+ position: relative;
101
+ display: inline-flex;
102
+ align-items: center;
103
+ padding: 2px 6px 2px 3px;
104
+ background: #f2f3f5;
105
+ border: 1px solid #e5e6eb;
106
+ border-radius: 999px;
107
+ margin-right: 2px;
108
+ font-size: 11px;
109
+ line-height: 1.4;
110
+ }
111
+ .lite-presence-avatar,
112
+ .lite-presence-initials {
47
113
  display: inline-block;
48
- padding: 2px 6px;
49
- background: #F2F3F5;
50
- border: 1px solid #E5E6EB;
51
- border-radius: 4px;
114
+ width: 20px;
115
+ height: 20px;
116
+ flex: 0 0 20px;
52
117
  margin-right: 4px;
118
+ border-radius: 999px;
119
+ text-align: center;
120
+ line-height: 20px;
121
+ object-fit: cover;
122
+ background: #e8f3ff;
123
+ color: #165dff;
124
+ font-size: 9px;
125
+ font-weight: 700;
126
+ }
127
+ .lite-presence-name { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
128
+ .lite-presence-dot {
129
+ margin-left: 4px;
130
+ width: 6px;
131
+ height: 6px;
132
+ border-radius: 999px;
133
+ background: #00b42a;
134
+ box-shadow: 0 0 0 1px #fff;
135
+ }
136
+ .lite-presence-badge[data-status='idle'] .lite-presence-dot { background: #86909c; }
137
+ .lite-presence-badge[data-status='editing'] .lite-presence-dot { background: #ff7d00; }
138
+ .lite-presence-badge[data-status='viewing'] .lite-presence-dot { background: #00b42a; }
139
+ .lite-presence-overflow {
140
+ display: inline-block;
141
+ min-width: 24px;
142
+ line-height: 24px;
143
+ text-align: center;
144
+ height: 24px;
145
+ padding: 0 5px;
146
+ border: 1px solid #e5e6eb;
147
+ border-radius: 999px;
148
+ background: #fff;
149
+ color: #4e5969;
53
150
  font-size: 11px;
151
+ font-weight: 600;
54
152
  }
153
+ .lite-presence-empty { color: #86909c; font-size: 11px; }
154
+ .lite-presence-sm .lite-presence-avatar,
155
+ .lite-presence-sm .lite-presence-initials { width: 16px; height: 16px; flex-basis: 16px; font-size: 8px; }
156
+ .lite-presence-lg .lite-presence-avatar,
157
+ .lite-presence-lg .lite-presence-initials { width: 26px; height: 26px; flex-basis: 26px; font-size: 10px; }
55
158
  </style>
@@ -1,12 +1,17 @@
1
- interface LitePresenceUser {
1
+ export interface LitePresenceUser {
2
2
  id: string;
3
3
  name: string;
4
- status?: 'online' | 'idle' | 'editing';
4
+ avatar?: string;
5
+ role?: string;
6
+ status?: 'online' | 'viewing' | 'idle' | 'editing';
5
7
  }
6
8
  interface Props {
7
9
  users?: LitePresenceUser[];
8
10
  label?: string;
9
- /** Caller class merged with the built-in class list. */
11
+ maxVisible?: number;
12
+ maxCount?: number;
13
+ size?: 'sm' | 'md' | 'lg';
14
+ showStatusDot?: boolean;
10
15
  class?: string;
11
16
  }
12
17
  declare const LitePresenceAvatarGroup: import("svelte").Component<Props, {}, "">;
@@ -0,0 +1,135 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type ResultStatus = 'success' | 'processing' | 'warning' | 'error' | '403' | '404' | '500';
5
+
6
+ interface Props {
7
+ status?: ResultStatus;
8
+ title: string;
9
+ description?: string;
10
+ primaryActionHref?: string;
11
+ primaryActionLabel?: string;
12
+ secondaryActionHref?: string;
13
+ secondaryActionLabel?: string;
14
+ class?: string;
15
+ actions?: Snippet;
16
+ children?: Snippet;
17
+ }
18
+
19
+ let {
20
+ status = 'success',
21
+ title,
22
+ description,
23
+ primaryActionHref,
24
+ primaryActionLabel = 'Back to Home',
25
+ secondaryActionHref,
26
+ secondaryActionLabel,
27
+ class: className = '',
28
+ actions,
29
+ children,
30
+ }: Props = $props();
31
+
32
+ const iconSymbol = $derived.by(() => {
33
+ switch (status) {
34
+ case 'success':
35
+ return '✓';
36
+ case 'processing':
37
+ return '⏳';
38
+ case 'warning':
39
+ case '403':
40
+ return '!';
41
+ case 'error':
42
+ case '500':
43
+ return '✕';
44
+ case '404':
45
+ default:
46
+ return '?';
47
+ }
48
+ });
49
+ </script>
50
+
51
+ <div
52
+ class="lite-result-page lite-result-{status} {className}"
53
+ data-svadmin-component="LiteResultPage"
54
+ role="status"
55
+ >
56
+ <div class="lite-result-icon-wrapper" aria-hidden="true">
57
+ <span class="lite-result-icon">{iconSymbol}</span>
58
+ </div>
59
+
60
+ <h2 class="lite-result-title">{title}</h2>
61
+
62
+ {#if description}
63
+ <p class="lite-result-description">{description}</p>
64
+ {/if}
65
+
66
+ {#if actions}
67
+ <div class="lite-result-actions">{@render actions()}</div>
68
+ {:else if primaryActionHref || secondaryActionHref}
69
+ <div class="lite-result-actions">
70
+ {#if primaryActionHref}
71
+ <a href={primaryActionHref} class="lite-result-btn-primary">{primaryActionLabel}</a>
72
+ {/if}
73
+ {#if secondaryActionHref && secondaryActionLabel}
74
+ <a href={secondaryActionHref} class="lite-result-btn-secondary">{secondaryActionLabel}</a>
75
+ {/if}
76
+ </div>
77
+ {/if}
78
+
79
+ {#if children}
80
+ <div class="lite-result-extra">{@render children()}</div>
81
+ {/if}
82
+ </div>
83
+
84
+ <style>
85
+ .lite-result-page {
86
+ text-align: center;
87
+ padding: 48px 24px;
88
+ background: #ffffff;
89
+ border-radius: 8px;
90
+ border: 1px solid #e5e6eb;
91
+ box-sizing: border-box;
92
+ }
93
+ .lite-result-icon-wrapper {
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ width: 64px;
98
+ height: 64px;
99
+ border-radius: 50%;
100
+ margin-bottom: 16px;
101
+ font-size: 28px;
102
+ font-weight: 700;
103
+ }
104
+ .lite-result-success .lite-result-icon-wrapper { background: #e8ffea; color: #00b42a; }
105
+ .lite-result-processing .lite-result-icon-wrapper { background: #e8f3ff; color: #165dff; }
106
+ .lite-result-warning .lite-result-icon-wrapper { background: #fff7e8; color: #ff7d00; }
107
+ .lite-result-error .lite-result-icon-wrapper { background: #ffece8; color: #f53f3f; }
108
+ .lite-result-403 .lite-result-icon-wrapper { background: #fff7e8; color: #ff7d00; }
109
+ .lite-result-404 .lite-result-icon-wrapper { background: #f2f3f5; color: #86909c; }
110
+ .lite-result-500 .lite-result-icon-wrapper { background: #ffece8; color: #f53f3f; }
111
+ .lite-result-title { margin: 0 0 8px; font-size: 20px; font-weight: 600; color: #1d2129; }
112
+ .lite-result-description { margin: 0 0 24px; font-size: 14px; color: #4e5969; max-width: 480px; margin-left: auto; margin-right: auto; }
113
+ .lite-result-actions { display: flex; justify-content: center; }
114
+ .lite-result-actions > * + * { margin-left: 12px; }
115
+ .lite-result-btn-primary {
116
+ display: inline-block;
117
+ padding: 8px 18px;
118
+ background: #165dff;
119
+ color: #ffffff;
120
+ border-radius: 4px;
121
+ text-decoration: none;
122
+ font-size: 13px;
123
+ font-weight: 500;
124
+ }
125
+ .lite-result-btn-secondary {
126
+ display: inline-block;
127
+ padding: 8px 18px;
128
+ background: #f2f3f5;
129
+ color: #4e5969;
130
+ border-radius: 4px;
131
+ text-decoration: none;
132
+ font-size: 13px;
133
+ }
134
+ .lite-result-extra { margin-top: 24px; padding-top: 16px; border-top: 1px solid #f2f3f5; }
135
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ type ResultStatus = 'success' | 'processing' | 'warning' | 'error' | '403' | '404' | '500';
3
+ interface Props {
4
+ status?: ResultStatus;
5
+ title: string;
6
+ description?: string;
7
+ primaryActionHref?: string;
8
+ primaryActionLabel?: string;
9
+ secondaryActionHref?: string;
10
+ secondaryActionLabel?: string;
11
+ class?: string;
12
+ actions?: Snippet;
13
+ children?: Snippet;
14
+ }
15
+ declare const LiteResultPage: import("svelte").Component<Props, {}, "">;
16
+ type LiteResultPage = ReturnType<typeof LiteResultPage>;
17
+ export default LiteResultPage;