@svadmin/ui 0.60.0 → 0.63.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/app.css +4 -6
- package/dist/app.theme.css +4 -6
- package/dist/components/ApiSettings.svelte +1 -1
- package/dist/components/ConfigErrorScreen.svelte +2 -2
- package/dist/components/InferencerPanel.svelte +68 -8
- package/dist/components/InsightCard.svelte +1 -1
- package/dist/components/MarkdownRenderer.svelte +25 -2
- package/dist/components/PermissionMatrix.svelte +1 -1
- package/dist/components/Sidebar.svelte +10 -6
- package/dist/components/TaskStatusBadge.svelte +1 -1
- package/dist/components/fields/AvatarField.svelte +8 -8
- package/dist/components/fields/JsonField.svelte +1 -1
- package/dist/components/fields/RatingField.svelte +3 -3
- package/dist/components/profile/ProfileVariantSections.svelte +9 -8
- package/package.json +16 -16
package/dist/app.css
CHANGED
|
@@ -596,13 +596,11 @@ main {
|
|
|
596
596
|
50% { opacity: 0; }
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
-
/* Handle copied state for dynamically injected buttons
|
|
599
|
+
/* Handle copied state for dynamically injected buttons.
|
|
600
|
+
* The check icon itself is swapped in by MarkdownRenderer (createCheckIcon)
|
|
601
|
+
* with stroke="currentColor" so it inherits this semantic token color. */
|
|
600
602
|
.copy-btn[data-copied-id] {
|
|
601
|
-
color:
|
|
602
|
-
}
|
|
603
|
-
.copy-btn[data-copied-id] svg {
|
|
604
|
-
/* Swap to Check icon */
|
|
605
|
-
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="%2310b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>');
|
|
603
|
+
color: var(--success);
|
|
606
604
|
}
|
|
607
605
|
|
|
608
606
|
/* ChatDialog Styles */
|
package/dist/app.theme.css
CHANGED
|
@@ -595,13 +595,11 @@ main {
|
|
|
595
595
|
50% { opacity: 0; }
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
-
/* Handle copied state for dynamically injected buttons
|
|
598
|
+
/* Handle copied state for dynamically injected buttons.
|
|
599
|
+
* The check icon itself is swapped in by MarkdownRenderer (createCheckIcon)
|
|
600
|
+
* with stroke="currentColor" so it inherits this semantic token color. */
|
|
599
601
|
.copy-btn[data-copied-id] {
|
|
600
|
-
color:
|
|
601
|
-
}
|
|
602
|
-
.copy-btn[data-copied-id] svg {
|
|
603
|
-
/* Swap to Check icon */
|
|
604
|
-
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="%2310b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>');
|
|
602
|
+
color: var(--success);
|
|
605
603
|
}
|
|
606
604
|
|
|
607
605
|
/* ChatDialog Styles */
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
{#if loading}<DataState state="loading" />{:else if error}<DataState state="error" description={error} retry={loadCredentials} />{:else}<ApiKeyList class="rounded-none border-0" keys={keys.map((key) => ({ id: key.id, name: key.name, prefix: key.prefix, createdAt: new Date(key.createdAt).toLocaleString(), lastUsedAt: key.lastUsedAt ? new Date(key.lastUsedAt).toLocaleString() : undefined, permissions: key.permissions }))} stateTitle={i18n.t('api.noKeys')} stateDescription={credentialProvider ? i18n.t('api.generateDesc') : (isZh ? '配置 CredentialProvider 后可管理真实凭据。' : 'Configure CredentialProvider to manage real credentials.')} onrevoke={credentialProvider ? (key) => revokeKey(key.id) : undefined} />{/if}
|
|
173
173
|
</SettingsGroup>
|
|
174
174
|
<SettingsGroup title={i18n.t('api.webhooks')} description={i18n.t('api.webhooksDesc')} bodyClass="space-y-5">
|
|
175
|
-
<form class="grid min-w-0 gap-4 md:grid-cols-2" onsubmit={handleAddWebhook}><div class="space-y-2"><Label for="webhook-name">{i18n.t('api.webhookName')}</Label><Input id="webhook-name" bind:value={newWebhookName} placeholder="Order Events" disabled={!credentialProvider || submitting} /></div><div class="space-y-2"><Label for="webhook-url">{i18n.t('api.webhookUrl')}</Label><Input id="webhook-url" type="url" bind:value={newWebhookUrl} placeholder="https://api.example.com/hooks" disabled={!credentialProvider || submitting} /></div><div class="space-y-2"><Label for="webhook-event">{i18n.t('api.eventType')}</Label><select id="webhook-event" bind:value={newWebhookEvent} disabled={!credentialProvider || submitting} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:opacity-50"><option value="resource.created">resource.created</option><option value="resource.updated">resource.updated</option><option value="resource.deleted">resource.deleted</option><option value="auth.login">auth.login</option></select></div><div class="flex items-end"><Button type="submit" class="w-full" disabled={!credentialProvider || submitting || !newWebhookName.trim() || !newWebhookUrl.trim()}><Plus class="size-4" />{i18n.t('api.addWebhook')}</Button></div></form>
|
|
175
|
+
<form class="grid min-w-0 gap-4 md:grid-cols-2" onsubmit={handleAddWebhook}><div class="space-y-2"><Label for="webhook-name">{i18n.t('api.webhookName')}</Label><Input id="webhook-name" bind:value={newWebhookName} placeholder="Order Events" disabled={!credentialProvider || submitting} /></div><div class="space-y-2 md:col-span-2"><Label for="webhook-url">{i18n.t('api.webhookUrl')}</Label><Input id="webhook-url" type="url" bind:value={newWebhookUrl} placeholder="https://api.example.com/hooks" disabled={!credentialProvider || submitting} /></div><div class="space-y-2"><Label for="webhook-event">{i18n.t('api.eventType')}</Label><select id="webhook-event" bind:value={newWebhookEvent} disabled={!credentialProvider || submitting} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:opacity-50"><option value="resource.created">resource.created</option><option value="resource.updated">resource.updated</option><option value="resource.deleted">resource.deleted</option><option value="auth.login">auth.login</option></select></div><div class="flex items-end"><Button type="submit" class="w-full" disabled={!credentialProvider || submitting || !newWebhookName.trim() || !newWebhookUrl.trim()}><Plus class="size-4" />{i18n.t('api.addWebhook')}</Button></div></form>
|
|
176
176
|
{#if webhooks.length === 0}<DataState state="empty" title={i18n.t('api.noWebhooks')} description={credentialProvider ? i18n.t('api.webhooksDesc') : (isZh ? '配置 CredentialProvider 后可管理真实 Webhook。' : 'Configure CredentialProvider to manage real webhooks.')} />{:else}<div class="divide-y divide-border border-t border-border">{#each webhooks as hook (hook.id)}<div class="flex flex-col gap-3 py-4 sm:flex-row sm:items-center sm:justify-between"><div class="min-w-0"><p class="text-sm font-medium text-foreground">{hook.name}</p><p class="truncate font-mono text-xs text-muted-foreground">{hook.url}</p></div><div class="flex items-center justify-between gap-3 sm:justify-end"><span class="rounded bg-muted px-2 py-1 font-mono text-xs text-muted-foreground">{hook.eventType}</span><Button variant="ghost" size="icon-sm" onclick={() => deleteWebhook(hook.id)} aria-label={i18n.t('common.delete') + ' ' + hook.name}><Trash2 class="size-4" /></Button></div></div>{/each}</div>{/if}
|
|
177
177
|
</SettingsGroup>
|
|
178
178
|
</div>
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
onclick={() => copyToClipboard(`${v.key}=`, v.key)}
|
|
82
82
|
>
|
|
83
83
|
{#if copied[v.key]}
|
|
84
|
-
<CheckCircle class="h-3.5 w-3.5 text-
|
|
84
|
+
<CheckCircle class="h-3.5 w-3.5 text-success" />
|
|
85
85
|
{:else}
|
|
86
86
|
<Copy class="h-3.5 w-3.5" />
|
|
87
87
|
{/if}
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
onclick={copyAll}
|
|
104
104
|
>
|
|
105
105
|
{#if copied['__all__']}
|
|
106
|
-
<CheckCircle class="h-3.5 w-3.5 text-
|
|
106
|
+
<CheckCircle class="h-3.5 w-3.5 text-success" />
|
|
107
107
|
<span class="text-xs">{i18n.t('common.copied')}</span>
|
|
108
108
|
{:else}
|
|
109
109
|
<Copy class="h-3.5 w-3.5" />
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
let selectedResource = $state('');
|
|
20
20
|
let inferResult = $state<InferResult | null>(null);
|
|
21
|
+
let codeTarget = $state<'resource' | 'typebox' | 'list' | 'create' | 'edit' | 'show'>('resource');
|
|
21
22
|
let loading = $state(false);
|
|
22
23
|
let error = $state<string | null>(null);
|
|
23
24
|
let copied = $state(false);
|
|
@@ -78,9 +79,19 @@
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
const activeCode = $derived.by(() => {
|
|
83
|
+
if (!inferResult) return '';
|
|
84
|
+
if (codeTarget === 'typebox') return inferResult.typeboxCode;
|
|
85
|
+
if (codeTarget === 'list') return inferResult.componentCode.list;
|
|
86
|
+
if (codeTarget === 'create') return inferResult.componentCode.create;
|
|
87
|
+
if (codeTarget === 'edit') return inferResult.componentCode.edit;
|
|
88
|
+
if (codeTarget === 'show') return inferResult.componentCode.show;
|
|
89
|
+
return inferResult.code;
|
|
90
|
+
});
|
|
91
|
+
|
|
81
92
|
function copyCode() {
|
|
82
|
-
if (!
|
|
83
|
-
navigator.clipboard.writeText(
|
|
93
|
+
if (!activeCode) return;
|
|
94
|
+
navigator.clipboard.writeText(activeCode);
|
|
84
95
|
copied = true;
|
|
85
96
|
if (copyTimer) clearTimeout(copyTimer);
|
|
86
97
|
copyTimer = setTimeout(() => { copied = false; }, 2000);
|
|
@@ -190,23 +201,72 @@
|
|
|
190
201
|
|
|
191
202
|
<!-- Generated code -->
|
|
192
203
|
<div class="rounded-lg border overflow-hidden">
|
|
193
|
-
<div class="flex items-center justify-between bg-muted px-3 py-1.5 text-xs text-muted-foreground">
|
|
194
|
-
<
|
|
204
|
+
<div class="flex flex-wrap items-center justify-between gap-1 bg-muted px-3 py-1.5 text-xs text-muted-foreground border-b">
|
|
205
|
+
<div class="flex items-center gap-1">
|
|
206
|
+
<Button
|
|
207
|
+
variant={codeTarget === 'resource' ? 'secondary' : 'ghost'}
|
|
208
|
+
size="sm"
|
|
209
|
+
class="h-6 text-xs px-2"
|
|
210
|
+
onclick={() => { codeTarget = 'resource'; }}
|
|
211
|
+
>
|
|
212
|
+
Resource (TS)
|
|
213
|
+
</Button>
|
|
214
|
+
<Button
|
|
215
|
+
variant={codeTarget === 'typebox' ? 'secondary' : 'ghost'}
|
|
216
|
+
size="sm"
|
|
217
|
+
class="h-6 text-xs px-2"
|
|
218
|
+
onclick={() => { codeTarget = 'typebox'; }}
|
|
219
|
+
>
|
|
220
|
+
TypeBox Schema
|
|
221
|
+
</Button>
|
|
222
|
+
<Button
|
|
223
|
+
variant={codeTarget === 'list' ? 'secondary' : 'ghost'}
|
|
224
|
+
size="sm"
|
|
225
|
+
class="h-6 text-xs px-2"
|
|
226
|
+
onclick={() => { codeTarget = 'list'; }}
|
|
227
|
+
>
|
|
228
|
+
List Page
|
|
229
|
+
</Button>
|
|
230
|
+
<Button
|
|
231
|
+
variant={codeTarget === 'create' ? 'secondary' : 'ghost'}
|
|
232
|
+
size="sm"
|
|
233
|
+
class="h-6 text-xs px-2"
|
|
234
|
+
onclick={() => { codeTarget = 'create'; }}
|
|
235
|
+
>
|
|
236
|
+
Create Form
|
|
237
|
+
</Button>
|
|
238
|
+
<Button
|
|
239
|
+
variant={codeTarget === 'edit' ? 'secondary' : 'ghost'}
|
|
240
|
+
size="sm"
|
|
241
|
+
class="h-6 text-xs px-2"
|
|
242
|
+
onclick={() => { codeTarget = 'edit'; }}
|
|
243
|
+
>
|
|
244
|
+
Edit Form
|
|
245
|
+
</Button>
|
|
246
|
+
<Button
|
|
247
|
+
variant={codeTarget === 'show' ? 'secondary' : 'ghost'}
|
|
248
|
+
size="sm"
|
|
249
|
+
class="h-6 text-xs px-2"
|
|
250
|
+
onclick={() => { codeTarget = 'show'; }}
|
|
251
|
+
>
|
|
252
|
+
Show Page
|
|
253
|
+
</Button>
|
|
254
|
+
</div>
|
|
195
255
|
<Button variant="ghost" size="sm" class="h-6 text-xs gap-1" onclick={copyCode}>
|
|
196
256
|
{#if copied}
|
|
197
|
-
<Check class="h-3 w-3 text-
|
|
198
|
-
<span class="text-
|
|
257
|
+
<Check class="h-3 w-3 text-success" />
|
|
258
|
+
<span class="text-success">Copied!</span>
|
|
199
259
|
{:else}
|
|
200
260
|
<Copy class="h-3 w-3" />
|
|
201
261
|
Copy
|
|
202
262
|
{/if}
|
|
203
263
|
</Button>
|
|
204
264
|
</div>
|
|
205
|
-
<pre class="max-h-64 overflow-auto bg-muted/30 p-3 text-xs text-foreground font-mono">{
|
|
265
|
+
<pre class="max-h-64 overflow-auto bg-muted/30 p-3 text-xs text-foreground font-mono">{activeCode}</pre>
|
|
206
266
|
</div>
|
|
207
267
|
|
|
208
268
|
<p class="text-xs text-muted-foreground">
|
|
209
|
-
Inferred {inferResult.fields.length} fields from sample data. Copy the code above into your
|
|
269
|
+
Inferred {inferResult.fields.length} fields from sample data. Copy the code above into your Svelte 5 application files.
|
|
210
270
|
</p>
|
|
211
271
|
{/if}
|
|
212
272
|
</Card.CardContent>
|
|
@@ -102,7 +102,7 @@ ${scopedContext}`;
|
|
|
102
102
|
>
|
|
103
103
|
<div class="flex items-center justify-between p-4 border-b">
|
|
104
104
|
<div class="flex items-center gap-2 font-medium">
|
|
105
|
-
<Sparkles class="h-4 w-4 text-
|
|
105
|
+
<Sparkles class="h-4 w-4 text-warning" aria-hidden="true" />
|
|
106
106
|
{title}
|
|
107
107
|
</div>
|
|
108
108
|
<Button
|
|
@@ -106,12 +106,35 @@
|
|
|
106
106
|
const id = Math.random().toString(36);
|
|
107
107
|
copiedBlock = id;
|
|
108
108
|
button.setAttribute("data-copied-id", id);
|
|
109
|
+
// Swap to a semantic check icon; color comes from the .copy-btn CSS token.
|
|
110
|
+
button.querySelector("svg")?.replaceWith(createCheckIcon());
|
|
109
111
|
|
|
110
112
|
setTimeout(() => {
|
|
111
113
|
if (copiedBlock === id) copiedBlock = null;
|
|
112
114
|
}, 2000);
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
function createCheckIcon(): SVGSVGElement {
|
|
118
|
+
const svgNamespace = "http://www.w3.org/2000/svg";
|
|
119
|
+
const svg = document.createElementNS(svgNamespace, "svg");
|
|
120
|
+
svg.setAttribute("width", "14");
|
|
121
|
+
svg.setAttribute("height", "14");
|
|
122
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
123
|
+
svg.setAttribute("fill", "none");
|
|
124
|
+
svg.setAttribute("stroke", "currentColor");
|
|
125
|
+
svg.setAttribute("stroke-width", "2");
|
|
126
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
127
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
128
|
+
svg.setAttribute("aria-hidden", "true");
|
|
129
|
+
svg.classList.add("lucide", "lucide-check");
|
|
130
|
+
|
|
131
|
+
const polyline = document.createElementNS(svgNamespace, "polyline");
|
|
132
|
+
polyline.setAttribute("points", "20 6 9 17 4 12");
|
|
133
|
+
svg.appendChild(polyline);
|
|
134
|
+
|
|
135
|
+
return svg;
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
function createCopyIcon(): SVGSVGElement {
|
|
116
139
|
const svgNamespace = "http://www.w3.org/2000/svg";
|
|
117
140
|
const svg = document.createElementNS(svgNamespace, "svg");
|
|
@@ -161,7 +184,7 @@
|
|
|
161
184
|
|
|
162
185
|
const header = document.createElement("div");
|
|
163
186
|
header.className =
|
|
164
|
-
"flex items-center justify-between px-4 py-2 text-xs text-
|
|
187
|
+
"flex items-center justify-between px-4 py-2 text-xs text-background/70 border-b border-background/15";
|
|
165
188
|
|
|
166
189
|
const lang =
|
|
167
190
|
Array.from(pre.querySelector("code")?.classList || [])
|
|
@@ -173,7 +196,7 @@
|
|
|
173
196
|
const copyButton = document.createElement("button");
|
|
174
197
|
copyButton.type = "button";
|
|
175
198
|
copyButton.className =
|
|
176
|
-
"copy-btn hover:text-
|
|
199
|
+
"copy-btn hover:text-background transition-colors flex items-center gap-1";
|
|
177
200
|
copyButton.setAttribute("aria-label", "Copy code");
|
|
178
201
|
copyButton.appendChild(createCopyIcon());
|
|
179
202
|
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
|
|
68
68
|
<!-- Toast Feedback -->
|
|
69
69
|
{#if message}
|
|
70
|
-
<div class={`px-4 py-2 rounded-lg text-sm font-medium shadow-sm transition-all flex items-center gap-2 ${messageType === 'success' ? 'bg-
|
|
70
|
+
<div class={`px-4 py-2 rounded-lg text-sm font-medium shadow-sm transition-all flex items-center gap-2 ${messageType === 'success' ? 'bg-success/10 text-success ring-1 ring-success/20' : 'bg-destructive/10 text-destructive ring-1 ring-destructive/20'}`}>
|
|
71
71
|
{#if messageType === 'success'}
|
|
72
72
|
<Check class="w-4 h-4" />
|
|
73
73
|
{:else}
|
|
@@ -285,12 +285,14 @@
|
|
|
285
285
|
});
|
|
286
286
|
</script>
|
|
287
287
|
|
|
288
|
-
{#snippet languageMenu()}
|
|
288
|
+
{#snippet languageMenu(collapsed: boolean)}
|
|
289
289
|
<select
|
|
290
290
|
aria-label={i18n.t('common.switchLanguage')}
|
|
291
291
|
value={i18n.locale}
|
|
292
292
|
onchange={(event) => selectLocale((event.target as HTMLSelectElement).value)}
|
|
293
|
-
class=
|
|
293
|
+
class={collapsed
|
|
294
|
+
? 'h-8 w-auto min-w-0 max-w-full appearance-none rounded-md border border-border bg-sidebar px-1 text-center text-[10px] font-semibold text-sidebar-foreground/70 outline-none focus:ring-2 focus:ring-ring'
|
|
295
|
+
: 'h-8 w-auto min-w-0 max-w-full rounded-md border border-border bg-sidebar px-1.5 text-[11px] font-semibold text-sidebar-foreground/70 outline-none focus:ring-2 focus:ring-ring'}
|
|
294
296
|
>
|
|
295
297
|
{#each i18n.getAvailableLocales() as locale (locale)}
|
|
296
298
|
<option value={locale}>{localeName(locale)}</option>
|
|
@@ -425,6 +427,8 @@
|
|
|
425
427
|
class="h-3 w-3 rounded-full ring-1 ring-offset-1 ring-offset-sidebar"
|
|
426
428
|
style="background-color: {getColorThemes().find(c => c.id === getColorTheme())?.color ?? '#6366f1'}; --tw-ring-color: {getColorThemes().find(c => c.id === getColorTheme())?.color ?? '#6366f1'}"
|
|
427
429
|
></span>
|
|
430
|
+
<!-- The dot previews the user-selected runtime theme swatch; the hex
|
|
431
|
+
fallback only covers the case where no registered theme matches. -->
|
|
428
432
|
</button>
|
|
429
433
|
{#if colorPickerOpen}
|
|
430
434
|
<div class="absolute bottom-full left-0 mb-1 z-50 w-40 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95">
|
|
@@ -468,7 +472,7 @@
|
|
|
468
472
|
</div>
|
|
469
473
|
<div class="mt-1 flex items-center justify-between px-1">
|
|
470
474
|
<div class="flex items-center gap-0.5">
|
|
471
|
-
{@render languageMenu()}
|
|
475
|
+
{@render languageMenu(false)}
|
|
472
476
|
<TooltipButton tooltip={i18n.t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
|
|
473
477
|
{#if getResolvedTheme() === 'dark'}
|
|
474
478
|
<Sun class="h-3.5 w-3.5" />
|
|
@@ -486,8 +490,8 @@
|
|
|
486
490
|
</div>
|
|
487
491
|
</div>
|
|
488
492
|
{:else if collapsed}
|
|
489
|
-
<div class="flex flex-col items-center gap-0.5 px-
|
|
490
|
-
{@render languageMenu()}
|
|
493
|
+
<div class="flex flex-col items-center gap-0.5 px-1 py-3">
|
|
494
|
+
{@render languageMenu(true)}
|
|
491
495
|
<Tooltip.Root>
|
|
492
496
|
<Tooltip.Trigger>
|
|
493
497
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
@@ -525,7 +529,7 @@
|
|
|
525
529
|
</div>
|
|
526
530
|
{:else}
|
|
527
531
|
<div class="flex justify-center gap-1 p-3">
|
|
528
|
-
{@render languageMenu()}
|
|
532
|
+
{@render languageMenu(false)}
|
|
529
533
|
<TooltipButton tooltip={i18n.t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
|
|
530
534
|
{#if getResolvedTheme() === 'dark'}
|
|
531
535
|
<Sun class="h-3.5 w-3.5" />
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const statusColors: Record<AvatarStatus, string> = {
|
|
48
|
-
online: 'bg-
|
|
49
|
-
success: 'bg-
|
|
50
|
-
busy: 'bg-
|
|
51
|
-
error: 'bg-
|
|
52
|
-
warning: 'bg-
|
|
53
|
-
away: 'bg-
|
|
54
|
-
offline: 'bg-
|
|
55
|
-
neutral: 'bg-
|
|
48
|
+
online: 'bg-success',
|
|
49
|
+
success: 'bg-success',
|
|
50
|
+
busy: 'bg-destructive',
|
|
51
|
+
error: 'bg-destructive',
|
|
52
|
+
warning: 'bg-warning',
|
|
53
|
+
away: 'bg-warning',
|
|
54
|
+
offline: 'bg-muted-foreground',
|
|
55
|
+
neutral: 'bg-muted-foreground',
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
function getInitials(value?: string | null): string {
|
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
{:else}
|
|
64
64
|
<div class={cn('field-rating inline-flex items-center gap-1.5', className)}>
|
|
65
65
|
<div
|
|
66
|
-
class="inline-flex items-center gap-0.5 text-
|
|
66
|
+
class="inline-flex items-center gap-0.5 text-warning"
|
|
67
67
|
role="img"
|
|
68
68
|
aria-label={`${numericValue} out of ${normalizedMax}`}
|
|
69
69
|
>
|
|
70
70
|
{#each stars as starType, idx (idx)}
|
|
71
71
|
{#if starType === 'full'}
|
|
72
|
-
<Star aria-hidden="true" class={cn(sizeClasses[size].icon, 'fill-
|
|
72
|
+
<Star aria-hidden="true" class={cn(sizeClasses[size].icon, 'fill-warning text-warning')} />
|
|
73
73
|
{:else if starType === 'half'}
|
|
74
|
-
<StarHalf aria-hidden="true" class={cn(sizeClasses[size].icon, 'fill-
|
|
74
|
+
<StarHalf aria-hidden="true" class={cn(sizeClasses[size].icon, 'fill-warning text-warning')} />
|
|
75
75
|
{:else}
|
|
76
76
|
<Star aria-hidden="true" class={cn(sizeClasses[size].icon, 'text-muted-foreground/30')} />
|
|
77
77
|
{/if}
|
|
@@ -32,15 +32,16 @@
|
|
|
32
32
|
];
|
|
33
33
|
const skills = ['TypeScript', 'Svelte', 'Node.js', 'GraphQL', 'Design Systems', 'Accessibility'];
|
|
34
34
|
const badges: { name: string; Icon: Component; tone: string }[] = [
|
|
35
|
-
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: '
|
|
38
|
-
{ name: '
|
|
35
|
+
// Decorative demo tones — use the chart palette, not status tokens.
|
|
36
|
+
{ name: 'Top Contributor', Icon: Medal, tone: 'bg-chart-1/10 text-chart-1' },
|
|
37
|
+
{ name: 'Early Adopter', Icon: Star, tone: 'bg-chart-2/10 text-chart-2' },
|
|
38
|
+
{ name: 'Community Mentor', Icon: Award, tone: 'bg-chart-3/10 text-chart-3' },
|
|
39
|
+
{ name: 'Launch Partner', Icon: Crown, tone: 'bg-chart-4/10 text-chart-4' },
|
|
39
40
|
];
|
|
40
41
|
const recentUploads: { name: string; size: string; Icon: Component; tone: string }[] = [
|
|
41
|
-
{ name: 'dashboard-spec.fig', size: '4.2 MB', Icon: Image, tone: 'bg-
|
|
42
|
-
{ name: 'release-notes.md', size: '18 KB', Icon: FileText, tone: 'bg-
|
|
43
|
-
{ name: 'assets-bundle.zip', size: '24 MB', Icon: FileArchive, tone: 'bg-
|
|
42
|
+
{ name: 'dashboard-spec.fig', size: '4.2 MB', Icon: Image, tone: 'bg-chart-5/10 text-chart-5' },
|
|
43
|
+
{ name: 'release-notes.md', size: '18 KB', Icon: FileText, tone: 'bg-chart-2/10 text-chart-2' },
|
|
44
|
+
{ name: 'assets-bundle.zip', size: '24 MB', Icon: FileArchive, tone: 'bg-chart-3/10 text-chart-3' },
|
|
44
45
|
];
|
|
45
46
|
const contributors = ['Mia Torres', 'Chen Wei', 'Ava Novak', 'Liam Ortiz', 'Sofia Petrova', '+8'];
|
|
46
47
|
|
|
@@ -252,7 +253,7 @@
|
|
|
252
253
|
<p class="text-xs text-muted-foreground">{item.label}</p>
|
|
253
254
|
<div class="mt-1 flex items-baseline gap-2">
|
|
254
255
|
<span class="text-xl font-bold text-foreground">{item.value}</span>
|
|
255
|
-
<span class="flex items-center gap-0.5 text-xs font-medium text-
|
|
256
|
+
<span class="flex items-center gap-0.5 text-xs font-medium text-success"><TrendingUp class="h-3 w-3" />{item.delta}</span>
|
|
256
257
|
</div>
|
|
257
258
|
</Card.CardContent>
|
|
258
259
|
</Card.Card>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
"./components/*": "./dist/components/*"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@svadmin/core": "^0.
|
|
43
|
-
"@tanstack/svelte-query": "^6.1.
|
|
44
|
-
"highlight.js": "^11.
|
|
45
|
-
"isomorphic-dompurify": "^3.
|
|
46
|
-
"marked": "^18.0.
|
|
42
|
+
"@svadmin/core": "^0.45.0",
|
|
43
|
+
"@tanstack/svelte-query": "^6.1.48",
|
|
44
|
+
"highlight.js": "^11.12.0",
|
|
45
|
+
"isomorphic-dompurify": "^3.23.0",
|
|
46
|
+
"marked": "^18.0.11",
|
|
47
47
|
"marked-highlight": "^2.2.4",
|
|
48
|
-
"svelte": "^5.56.
|
|
48
|
+
"svelte": "^5.56.10"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"marked": {
|
|
@@ -75,13 +75,13 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@floating-ui/dom": "^1.8.0",
|
|
78
|
-
"@lucide/svelte": "^1.
|
|
78
|
+
"@lucide/svelte": "^1.35.0",
|
|
79
79
|
"@tanstack/svelte-store": "^0.12.1",
|
|
80
|
-
"@tanstack/svelte-table": "9.
|
|
81
|
-
"@tanstack/table-core": "9.
|
|
82
|
-
"bits-ui": "^2.
|
|
80
|
+
"@tanstack/svelte-table": "^9.2.3",
|
|
81
|
+
"@tanstack/table-core": "^9.2.3",
|
|
82
|
+
"bits-ui": "^2.19.0",
|
|
83
83
|
"clsx": "^2.1.1",
|
|
84
|
-
"svelte-sonner": "^1.
|
|
84
|
+
"svelte-sonner": "^1.2.1",
|
|
85
85
|
"tailwind-merge": "^3.6.0",
|
|
86
86
|
"tailwind-variants": "^3.3.1"
|
|
87
87
|
},
|
|
@@ -100,13 +100,13 @@
|
|
|
100
100
|
"access": "public"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@happy-dom/global-registrator": "^20.11.
|
|
103
|
+
"@happy-dom/global-registrator": "^20.11.12",
|
|
104
104
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
105
105
|
"@testing-library/dom": "^10.4.1",
|
|
106
106
|
"@testing-library/svelte": "^5.4.2",
|
|
107
|
-
"@testing-library/user-event": "^14.6.
|
|
108
|
-
"happy-dom": "^20.11.
|
|
109
|
-
"vitest": "^4.1.
|
|
107
|
+
"@testing-library/user-event": "^14.6.6",
|
|
108
|
+
"happy-dom": "^20.11.12",
|
|
109
|
+
"vitest": "^4.1.11"
|
|
110
110
|
},
|
|
111
111
|
"scripts": {
|
|
112
112
|
"build": "node ../../node_modules/@sveltejs/package/svelte-package.js -i src -o dist && node scripts/postbuild-css.mjs",
|