@svadmin/ui 0.35.0 → 0.37.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 +436 -66
- package/dist/components/AutoForm.svelte +33 -48
- package/dist/components/AutoTable.svelte +24 -29
- package/dist/components/ErrorPage.svelte +1 -1
- package/dist/components/Header.svelte +6 -6
- package/dist/components/Layout.svelte +8 -8
- package/dist/components/PageHeader.svelte +7 -9
- package/dist/components/ResourceOperationsPage.svelte +12 -14
- package/dist/components/SettingsPage.svelte +14 -14
- package/dist/components/ShowPage.svelte +9 -9
- package/dist/components/Sidebar.svelte +73 -115
- package/dist/components/SidebarItem.svelte +37 -32
- package/dist/components/TwoFactorAuthPage.svelte +1 -1
- package/dist/components/account/CompanyProfilePage.svelte +1 -1
- package/dist/components/account/GetStartedPage.svelte +1 -1
- package/dist/components/account/ImportMembersPage.svelte +1 -1
- package/dist/components/account/MembersStarterPage.svelte +1 -1
- package/dist/components/account/SecurityLogPage.svelte +29 -2
- package/dist/components/account/SettingsEnterprisePage.svelte +1 -1
- package/dist/components/account/SettingsPlainPage.svelte +1 -1
- package/dist/components/account/SettingsSidebarPage.svelte +1 -1
- package/dist/components/account/TeamMembersPage.svelte +1 -1
- package/dist/components/network/TeamCrewTablePage.svelte +35 -2
- package/dist/components/network/UserCardsNFTPage.svelte +1 -1
- package/dist/components/profile/PublicProfilePage.svelte +1 -1
- package/dist/components/ui/pagination/pagination-ellipsis.svelte +1 -1
- package/package.json +2 -2
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
</PageHeader>
|
|
39
39
|
|
|
40
40
|
{#if query.isLoading}
|
|
41
|
-
<Card.Root class="overflow-hidden
|
|
42
|
-
<Card.Content class="
|
|
41
|
+
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
42
|
+
<Card.Content class="p-0">
|
|
43
43
|
{#each showFields.slice(0, 6) as _, i (i)}
|
|
44
|
-
<div class="flex flex-col
|
|
44
|
+
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
45
45
|
<Skeleton class="h-4 w-1/2 sm:w-1/4" />
|
|
46
46
|
<Skeleton class="h-4 w-3/4 sm:w-2/5 mt-1 sm:mt-0 sm:ml-auto" />
|
|
47
47
|
</div>
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
</Card.Content>
|
|
50
50
|
</Card.Root>
|
|
51
51
|
{:else if query.data?.data}
|
|
52
|
-
<Card.Root class="overflow-hidden
|
|
53
|
-
<Card.Content class="
|
|
52
|
+
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
53
|
+
<Card.Content class="p-0">
|
|
54
54
|
{#each showFields as field, i (i)}
|
|
55
55
|
{@const value = ((query.data as { data: Record<string, unknown> }).data as Record<string, unknown>)[field.key]}
|
|
56
56
|
{@const DisplayComponent = getDisplayComponent(field.type)}
|
|
57
|
-
<div class="flex flex-col
|
|
58
|
-
<div class="
|
|
59
|
-
<div class="text-sm text-foreground
|
|
57
|
+
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
58
|
+
<div class="sm:w-1/3 text-xs sm:text-sm font-medium text-muted-foreground mb-1 sm:mb-0">{field.label}</div>
|
|
59
|
+
<div class="sm:w-2/3 text-sm text-foreground">
|
|
60
60
|
{#if DisplayComponent && value != null}
|
|
61
61
|
<DisplayComponent
|
|
62
62
|
{value}
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
</Card.Content>
|
|
73
73
|
</Card.Root>
|
|
74
74
|
{:else}
|
|
75
|
-
<Card.Root class="overflow-hidden
|
|
75
|
+
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
76
76
|
<Card.Content class="p-8 text-center">
|
|
77
77
|
<p class="text-muted-foreground">{t('common.noData')}</p>
|
|
78
78
|
{#if query.isError}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
3
2
|
import { getResources, canAccessAsync } from '@svadmin/core';
|
|
4
3
|
import type { Identity, MenuItem } from '@svadmin/core';
|
|
5
4
|
import { navigate } from '@svadmin/core/router';
|
|
@@ -12,14 +11,15 @@
|
|
|
12
11
|
import SidebarItem from './SidebarItem.svelte';
|
|
13
12
|
|
|
14
13
|
import { ScrollArea } from './ui/scroll-area/index.js';
|
|
14
|
+
import * as Collapsible from './ui/collapsible/index.js';
|
|
15
15
|
import { Avatar } from './ui/avatar/index.js';
|
|
16
16
|
import {
|
|
17
17
|
LayoutDashboard, FileText, Users, Settings, Home,
|
|
18
|
-
ChevronLeft, ChevronRight, LogOut, Sun, Moon, Palette,
|
|
18
|
+
ChevronLeft, ChevronRight, ChevronDown, LogOut, Sun, Moon, Palette,
|
|
19
19
|
Image as ImageIcon, Layout, Folder, Type, Video,
|
|
20
20
|
Download, ListTodo, TrendingUp, Sparkles, Images, Bot, Key, KeyRound, CreditCard, BookOpen, Wrench,
|
|
21
|
-
Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle,
|
|
22
|
-
|
|
21
|
+
Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle, Mail, Send, ShoppingBag, Briefcase, Building2,
|
|
22
|
+
Calendar, Bell, Clock, Trash2, Shield
|
|
23
23
|
} from '@lucide/svelte';
|
|
24
24
|
|
|
25
25
|
let { collapsed, identity, title, onToggle, onLogout, menu, routeMode = 'auto' }: {
|
|
@@ -39,11 +39,10 @@
|
|
|
39
39
|
: routeMode
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
const _resources = getResources();
|
|
43
|
-
|
|
44
42
|
const iconMap: Record<string, typeof LayoutDashboard> = {
|
|
45
43
|
'dashboard': LayoutDashboard,
|
|
46
44
|
'posts': FileText,
|
|
45
|
+
'file': FileText,
|
|
47
46
|
'users': Users,
|
|
48
47
|
'settings': Settings,
|
|
49
48
|
'home': Home,
|
|
@@ -68,14 +67,16 @@
|
|
|
68
67
|
'clipboard-check': ClipboardCheck,
|
|
69
68
|
'sliders-horizontal': SlidersHorizontal,
|
|
70
69
|
'alert-triangle': AlertTriangle,
|
|
70
|
+
'mail': Mail,
|
|
71
|
+
'send': Send,
|
|
72
|
+
'shopping-bag': ShoppingBag,
|
|
73
|
+
'briefcase': Briefcase,
|
|
74
|
+
'building': Building2,
|
|
75
|
+
'calendar': Calendar,
|
|
71
76
|
'bell': Bell,
|
|
72
|
-
'
|
|
73
|
-
'
|
|
77
|
+
'clock': Clock,
|
|
78
|
+
'trash': Trash2,
|
|
74
79
|
'shield': Shield,
|
|
75
|
-
'barcode': Barcode,
|
|
76
|
-
'folder-tree': FolderTree,
|
|
77
|
-
'building-2': Building2,
|
|
78
|
-
'warehouse': Warehouse,
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
interface NavItem {
|
|
@@ -83,7 +84,6 @@
|
|
|
83
84
|
label: string;
|
|
84
85
|
Icon: typeof LayoutDashboard;
|
|
85
86
|
group?: string;
|
|
86
|
-
badge?: string;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
interface NavGroup {
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
let navItems = $state.raw<NavItem[]>([]);
|
|
96
96
|
|
|
97
97
|
$effect(() => {
|
|
98
|
-
|
|
98
|
+
getLocale();
|
|
99
99
|
const currentResources = getResources();
|
|
100
100
|
let cancelled = false;
|
|
101
101
|
|
|
@@ -116,7 +116,6 @@
|
|
|
116
116
|
label: r.label,
|
|
117
117
|
Icon: (typeof r.icon === 'string' ? iconMap[r.icon] : r.icon) ?? iconMap[r.name] ?? Settings,
|
|
118
118
|
group: r.group,
|
|
119
|
-
badge: typeof r.meta?.badge === 'string' ? r.meta.badge : undefined,
|
|
120
119
|
});
|
|
121
120
|
}
|
|
122
121
|
}
|
|
@@ -146,15 +145,6 @@
|
|
|
146
145
|
return path === itemPath || path.startsWith(itemPath + "/");
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
/** Prefetch resource data on hover for instant navigation */
|
|
150
|
-
const prefetchedResources = new Set<string>();
|
|
151
|
-
function _prefetchResource(resourceName: string) {
|
|
152
|
-
if (resourceName === '/' || prefetchedResources.has(resourceName)) return;
|
|
153
|
-
prefetchedResources.add(resourceName);
|
|
154
|
-
// Trigger a background fetch by navigating the browser cache
|
|
155
|
-
// The actual prefetch happens via the #key block re-creating useList in AdminApp
|
|
156
|
-
}
|
|
157
|
-
|
|
158
148
|
// Group nav items by group field (null = ungrouped)
|
|
159
149
|
const navGroups = $derived.by((): NavGroup[] => {
|
|
160
150
|
const groups: NavGroup[] = [];
|
|
@@ -172,6 +162,8 @@
|
|
|
172
162
|
return groups;
|
|
173
163
|
});
|
|
174
164
|
|
|
165
|
+
// Track which groups are open
|
|
166
|
+
let openGroups = $state<Set<string>>(new Set());
|
|
175
167
|
let colorPickerOpen = $state(false);
|
|
176
168
|
let colorPickerRef = $state<HTMLDivElement | null>(null);
|
|
177
169
|
let colorPickerOpenedAt = 0;
|
|
@@ -195,6 +187,7 @@
|
|
|
195
187
|
|
|
196
188
|
const pyClass = $derived(density === 'compact' ? 'py-1.5' : 'py-2.5');
|
|
197
189
|
const pyClassGroupItem = $derived(density === 'compact' ? 'py-1.5' : 'py-2');
|
|
190
|
+
const footerIconClass = 'h-9 w-9 rounded-lg text-sidebar-foreground/60 transition-all duration-200 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground';
|
|
198
191
|
|
|
199
192
|
// Click-outside to close color picker
|
|
200
193
|
$effect(() => {
|
|
@@ -217,42 +210,34 @@
|
|
|
217
210
|
|
|
218
211
|
<aside
|
|
219
212
|
aria-label="Sidebar navigation"
|
|
220
|
-
class="fixed inset-y-0 left-0 z-30 flex flex-col
|
|
221
|
-
|
|
222
|
-
class:w-
|
|
223
|
-
|
|
213
|
+
class="fixed inset-y-0 left-0 z-30 flex flex-col backdrop-blur-xl transition-all duration-300"
|
|
214
|
+
style="background-color: var(--sidebar);"
|
|
215
|
+
class:w-64={!collapsed}
|
|
216
|
+
class:w-16={collapsed}
|
|
224
217
|
>
|
|
225
218
|
<!-- Logo -->
|
|
226
|
-
<div class="flex h-16 items-center justify-between
|
|
219
|
+
<div class="flex h-16 items-center justify-between px-4">
|
|
227
220
|
{#if !collapsed}
|
|
228
221
|
<div class="flex items-center gap-3">
|
|
229
|
-
<div class="
|
|
230
|
-
|
|
231
|
-
</div>
|
|
232
|
-
<div class="min-w-0">
|
|
233
|
-
<span class="block truncate text-[0.95rem] font-semibold tracking-tight text-sidebar-foreground">{title}</span>
|
|
234
|
-
<span class="block truncate text-[11px] font-medium text-sidebar-foreground/50">{getLocale() === 'zh-CN' ? '管理控制台' : 'Admin Console'}</span>
|
|
235
|
-
</div>
|
|
222
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
|
|
223
|
+
<span class="font-semibold text-lg tracking-tight text-sidebar-foreground">{title}</span>
|
|
236
224
|
</div>
|
|
237
225
|
{:else}
|
|
238
|
-
<div class="
|
|
239
|
-
{title.slice(0, 1).toUpperCase()}
|
|
240
|
-
</div>
|
|
226
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 mx-auto" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
|
|
241
227
|
{/if}
|
|
242
228
|
{#if !collapsed}
|
|
243
|
-
<TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle}
|
|
229
|
+
<TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle}>
|
|
244
230
|
<ChevronLeft class="h-4 w-4" />
|
|
245
231
|
</TooltipButton>
|
|
246
232
|
{:else}
|
|
247
|
-
<TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle} class="mx-auto
|
|
233
|
+
<TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle} class="mx-auto mt-2 mb-2 block">
|
|
248
234
|
<ChevronRight class="h-4 w-4" />
|
|
249
235
|
</TooltipButton>
|
|
250
236
|
{/if}
|
|
251
237
|
</div>
|
|
252
238
|
|
|
253
|
-
<
|
|
254
|
-
<
|
|
255
|
-
<nav aria-label="Main menu" class="space-y-5 px-3 py-5">
|
|
239
|
+
<ScrollArea class="flex-1">
|
|
240
|
+
<nav aria-label="Main menu" class="py-4 px-3 space-y-1">
|
|
256
241
|
{#if menu && menu.length > 0}
|
|
257
242
|
<!-- Custom multi-level menu -->
|
|
258
243
|
{#each menu as item, _i (_i)}
|
|
@@ -262,41 +247,37 @@
|
|
|
262
247
|
<!-- Auto-generated menu from resources (fallback) -->
|
|
263
248
|
{#each navGroups as group, _i (_i)}
|
|
264
249
|
{#if group.name && !collapsed}
|
|
265
|
-
<!-- Grouped section
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
250
|
+
<!-- Grouped section with Collapsible -->
|
|
251
|
+
<Collapsible.Root open={openGroups.has(group.name)} onOpenChange={(isOpen) => {
|
|
252
|
+
const next = new Set(openGroups);
|
|
253
|
+
if (isOpen) next.add(group.name as string); else next.delete(group.name as string);
|
|
254
|
+
openGroups = next;
|
|
255
|
+
}}>
|
|
256
|
+
<Collapsible.Trigger
|
|
257
|
+
class="flex w-full items-center justify-between px-3 py-1.5 text-sm font-semibold tracking-wider text-sidebar-foreground/60 hover:text-sidebar-foreground transition-colors"
|
|
270
258
|
>
|
|
271
259
|
<span>{group.name}</span>
|
|
272
|
-
|
|
273
|
-
|
|
260
|
+
<ChevronDown class="h-3 w-3 transition-transform {openGroups.has(group.name) ? 'rotate-180' : ''}" />
|
|
261
|
+
</Collapsible.Trigger>
|
|
262
|
+
<Collapsible.Content>
|
|
263
|
+
<div class="mt-1 space-y-1">
|
|
274
264
|
{#each group.items as item, _i (_i)}
|
|
275
265
|
{@const active = isActive(item.path)}
|
|
276
266
|
<a
|
|
277
267
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
278
268
|
onclick={(e) => { e.preventDefault(); navigate(item.path); }}
|
|
279
|
-
class="
|
|
269
|
+
class="flex items-center gap-3 rounded-xl px-3 {pyClassGroupItem} text-sm font-medium transition-all duration-300
|
|
280
270
|
{active
|
|
281
|
-
? 'bg-
|
|
282
|
-
: 'text-sidebar-foreground/
|
|
283
|
-
data-sidebar="menu-sub-button"
|
|
284
|
-
data-active={active ? "true" : "false"}
|
|
271
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active shadow-[0_2px_12px_rgb(0,0,0,0.08)] ring-1 ring-border/20 translate-x-1'
|
|
272
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
285
273
|
>
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
{/if}
|
|
289
|
-
<span data-sidebar="menu-icon" class="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg {active ? 'bg-primary-foreground/15' : 'bg-muted/60 text-sidebar-foreground/55 group-hover:bg-primary/10 group-hover:text-primary'}">
|
|
290
|
-
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
291
|
-
</span>
|
|
292
|
-
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
293
|
-
{#if item.badge}
|
|
294
|
-
<span class="rounded-full {active ? 'bg-primary-foreground/20 text-primary-foreground' : 'bg-primary/10 text-primary'} px-2 py-0.5 text-[10px] font-semibold">{item.badge}</span>
|
|
295
|
-
{/if}
|
|
274
|
+
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
275
|
+
<span>{item.label}</span>
|
|
296
276
|
</a>
|
|
297
277
|
{/each}
|
|
298
|
-
|
|
299
|
-
|
|
278
|
+
</div>
|
|
279
|
+
</Collapsible.Content>
|
|
280
|
+
</Collapsible.Root>
|
|
300
281
|
{:else}
|
|
301
282
|
<!-- Ungrouped items (flat) -->
|
|
302
283
|
{#each group.items as item, _i (_i)}
|
|
@@ -309,12 +290,10 @@
|
|
|
309
290
|
{...props}
|
|
310
291
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
311
292
|
onclick={(e) => { e.preventDefault(); navigate(item.path); }}
|
|
312
|
-
class="flex items-center justify-center gap-3 rounded-xl px-3 {pyClass} text-sm font-medium transition-all duration-
|
|
293
|
+
class="flex items-center justify-center gap-3 rounded-xl px-3 {pyClass} text-sm font-medium transition-all duration-300
|
|
313
294
|
{active
|
|
314
|
-
? 'bg-
|
|
315
|
-
: 'text-sidebar-foreground/60 hover:bg-sidebar-accent/
|
|
316
|
-
data-sidebar="menu-button"
|
|
317
|
-
data-active={active ? "true" : "false"}
|
|
295
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[0_2px_12px_rgb(0,0,0,0.08)] ring-1 ring-border/20'
|
|
296
|
+
: 'text-sidebar-foreground/60 hover:bg-sidebar-accent/40 hover:text-sidebar-foreground'}"
|
|
318
297
|
>
|
|
319
298
|
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
320
299
|
</a>
|
|
@@ -328,23 +307,13 @@
|
|
|
328
307
|
<a
|
|
329
308
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
330
309
|
onclick={(e) => { e.preventDefault(); navigate(item.path); }}
|
|
331
|
-
class="
|
|
310
|
+
class="flex items-center gap-3 rounded-xl px-3 {pyClass} text-sm font-medium transition-all duration-300
|
|
332
311
|
{active
|
|
333
|
-
? 'bg-
|
|
334
|
-
: 'text-sidebar-foreground/
|
|
335
|
-
data-sidebar="menu-button"
|
|
336
|
-
data-active={active ? "true" : "false"}
|
|
312
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active shadow-[0_2px_12px_rgb(0,0,0,0.08)] ring-1 ring-border/20 translate-x-1'
|
|
313
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
337
314
|
>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
{/if}
|
|
341
|
-
<span data-sidebar="menu-icon" class="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg {active ? 'bg-primary-foreground/15' : 'bg-muted/60 text-sidebar-foreground/55 group-hover:bg-primary/10 group-hover:text-primary'}">
|
|
342
|
-
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
343
|
-
</span>
|
|
344
|
-
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
345
|
-
{#if item.badge}
|
|
346
|
-
<span class="rounded-full {active ? 'bg-primary-foreground/20 text-primary-foreground' : 'bg-primary/10 text-primary'} px-2 py-0.5 text-[10px] font-semibold">{item.badge}</span>
|
|
347
|
-
{/if}
|
|
315
|
+
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
316
|
+
<span>{item.label}</span>
|
|
348
317
|
</a>
|
|
349
318
|
{/if}
|
|
350
319
|
{/each}
|
|
@@ -353,17 +322,16 @@
|
|
|
353
322
|
{/if}
|
|
354
323
|
</nav>
|
|
355
324
|
</ScrollArea>
|
|
356
|
-
</div>
|
|
357
325
|
|
|
358
326
|
<!-- Footer -->
|
|
359
|
-
<div
|
|
327
|
+
<div>
|
|
360
328
|
<!-- Color theme picker -->
|
|
361
329
|
{#if !collapsed}
|
|
362
330
|
<div class="px-3 pt-3 pb-1">
|
|
363
331
|
<div class="relative" bind:this={colorPickerRef}>
|
|
364
332
|
<Button
|
|
365
333
|
variant="ghost"
|
|
366
|
-
class="flex
|
|
334
|
+
class="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors h-auto justify-start"
|
|
367
335
|
onclick={() => { if (!colorPickerOpen) colorPickerOpenedAt = Date.now(); colorPickerOpen = !colorPickerOpen; }}
|
|
368
336
|
>
|
|
369
337
|
<Palette class="h-4 w-4" />
|
|
@@ -395,8 +363,8 @@
|
|
|
395
363
|
|
|
396
364
|
<!-- User info -->
|
|
397
365
|
{#if !collapsed && identity}
|
|
398
|
-
<div class="px-3 pb-3 pt-1">
|
|
399
|
-
<div class="flex items-center gap-3 rounded-
|
|
366
|
+
<div class="px-3 pb-3 pt-1 transition-all duration-200">
|
|
367
|
+
<div class="flex items-center gap-3 rounded-lg p-2 transition-colors hover:bg-sidebar-accent/50">
|
|
400
368
|
<Avatar
|
|
401
369
|
src={(identity as Record<string, unknown>).avatar as string | undefined}
|
|
402
370
|
alt={identity.name ?? 'User'}
|
|
@@ -408,31 +376,31 @@
|
|
|
408
376
|
<p class="truncate text-[10px] text-sidebar-foreground/50">{((identity as Record<string, unknown>).role || (identity as Record<string, unknown>).roleName) ?? 'User'}</p>
|
|
409
377
|
</div>
|
|
410
378
|
</div>
|
|
411
|
-
<div class="
|
|
412
|
-
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon-sm" onclick={toggleLocale} class=
|
|
379
|
+
<div class="mt-1 grid grid-cols-4 place-items-center gap-1 px-1">
|
|
380
|
+
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon-sm" onclick={toggleLocale} class={footerIconClass}>
|
|
413
381
|
<span class="text-xs font-bold">{localeLabel}</span>
|
|
414
382
|
</TooltipButton>
|
|
415
|
-
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class=
|
|
383
|
+
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class={footerIconClass}>
|
|
416
384
|
{#if getResolvedTheme() === 'dark'}
|
|
417
385
|
<Sun class="h-4 w-4" />
|
|
418
386
|
{:else}
|
|
419
387
|
<Moon class="h-4 w-4" />
|
|
420
388
|
{/if}
|
|
421
389
|
</TooltipButton>
|
|
422
|
-
<TooltipButton tooltip={t('settings.title')} variant="ghost" size="icon-sm" onclick={() => navigate('/settings')} class=
|
|
390
|
+
<TooltipButton tooltip={t('settings.title')} variant="ghost" size="icon-sm" onclick={() => navigate('/settings')} class={footerIconClass}>
|
|
423
391
|
<Settings class="h-4 w-4" />
|
|
424
392
|
</TooltipButton>
|
|
425
|
-
<TooltipButton tooltip={t('common.logout')} variant="ghost" size="icon-sm" onclick={onLogout} class="
|
|
393
|
+
<TooltipButton tooltip={t('common.logout')} variant="ghost" size="icon-sm" onclick={onLogout} class="{footerIconClass} hover:text-destructive">
|
|
426
394
|
<LogOut class="h-4 w-4" />
|
|
427
395
|
</TooltipButton>
|
|
428
396
|
</div>
|
|
429
397
|
</div>
|
|
430
398
|
{:else if collapsed}
|
|
431
|
-
<div class="
|
|
399
|
+
<div class="flex flex-col items-center gap-1 px-3 py-3 transition-all duration-200">
|
|
432
400
|
<Tooltip.Root>
|
|
433
401
|
<Tooltip.Trigger>
|
|
434
402
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
435
|
-
<Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class=
|
|
403
|
+
<Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class={footerIconClass}>
|
|
436
404
|
<span class="text-xs font-bold">{localeLabel}</span>
|
|
437
405
|
</Button>
|
|
438
406
|
{/snippet}
|
|
@@ -442,7 +410,7 @@
|
|
|
442
410
|
<Tooltip.Root>
|
|
443
411
|
<Tooltip.Trigger>
|
|
444
412
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
445
|
-
<Button {...props} variant="ghost" size="icon" onclick={toggleTheme} class=
|
|
413
|
+
<Button {...props} variant="ghost" size="icon" onclick={toggleTheme} class={footerIconClass}>
|
|
446
414
|
{#if getResolvedTheme() === 'dark'}
|
|
447
415
|
<Sun class="h-4 w-4" />
|
|
448
416
|
{:else}
|
|
@@ -456,17 +424,7 @@
|
|
|
456
424
|
<Tooltip.Root>
|
|
457
425
|
<Tooltip.Trigger>
|
|
458
426
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
459
|
-
<Button {...props} variant="ghost" size="icon" onclick={
|
|
460
|
-
<Settings class="h-4 w-4" />
|
|
461
|
-
</Button>
|
|
462
|
-
{/snippet}
|
|
463
|
-
</Tooltip.Trigger>
|
|
464
|
-
<Tooltip.Content side="right">{t('settings.title')}</Tooltip.Content>
|
|
465
|
-
</Tooltip.Root>
|
|
466
|
-
<Tooltip.Root>
|
|
467
|
-
<Tooltip.Trigger>
|
|
468
|
-
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
469
|
-
<Button {...props} variant="ghost" size="icon" onclick={onLogout} class="w-full text-sidebar-foreground/60 hover:text-destructive">
|
|
427
|
+
<Button {...props} variant="ghost" size="icon" onclick={onLogout} class="{footerIconClass} hover:text-destructive">
|
|
470
428
|
<LogOut class="h-4 w-4" />
|
|
471
429
|
</Button>
|
|
472
430
|
{/snippet}
|
|
@@ -475,11 +433,11 @@
|
|
|
475
433
|
</Tooltip.Root>
|
|
476
434
|
</div>
|
|
477
435
|
{:else}
|
|
478
|
-
<div class="flex gap-
|
|
479
|
-
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon" onclick={toggleLocale} class=
|
|
436
|
+
<div class="flex justify-center gap-2 p-3 transition-all duration-200">
|
|
437
|
+
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon" onclick={toggleLocale} class={footerIconClass}>
|
|
480
438
|
<span class="text-xs font-bold">{localeLabel}</span>
|
|
481
439
|
</TooltipButton>
|
|
482
|
-
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class=
|
|
440
|
+
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class={footerIconClass}>
|
|
483
441
|
{#if getResolvedTheme() === 'dark'}
|
|
484
442
|
<Sun class="h-4 w-4" />
|
|
485
443
|
{:else}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import {
|
|
7
7
|
LayoutDashboard, FileText, Users, Settings, Home,
|
|
8
8
|
ChevronDown, Folder, ExternalLink, Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Mail, Send, ShoppingBag, Briefcase, Building2, Calendar, Bell, Clock, Trash2, Palette, Layout,
|
|
10
|
+
Download, ListTodo, TrendingUp, Sparkles, Images, Bot, Key, KeyRound, CreditCard, BookOpen, Wrench, Shield,
|
|
11
11
|
} from '@lucide/svelte';
|
|
12
12
|
|
|
13
13
|
import { formatLink } from '@svadmin/core/router';
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
const iconMap: Record<string, typeof LayoutDashboard> = {
|
|
23
23
|
dashboard: LayoutDashboard,
|
|
24
24
|
posts: FileText,
|
|
25
|
+
file: FileText,
|
|
25
26
|
users: Users,
|
|
26
27
|
settings: Settings,
|
|
27
28
|
home: Home,
|
|
@@ -30,21 +31,34 @@
|
|
|
30
31
|
'clipboard-check': ClipboardCheck,
|
|
31
32
|
'sliders-horizontal': SlidersHorizontal,
|
|
32
33
|
'alert-triangle': AlertTriangle,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
mail: Mail,
|
|
35
|
+
send: Send,
|
|
36
|
+
'shopping-bag': ShoppingBag,
|
|
37
|
+
briefcase: Briefcase,
|
|
38
|
+
building: Building2,
|
|
39
|
+
calendar: Calendar,
|
|
40
|
+
bell: Bell,
|
|
41
|
+
clock: Clock,
|
|
42
|
+
trash: Trash2,
|
|
43
|
+
palette: Palette,
|
|
44
|
+
layout: Layout,
|
|
45
|
+
download: Download,
|
|
46
|
+
'list-todo': ListTodo,
|
|
47
|
+
'trending-up': TrendingUp,
|
|
48
|
+
sparkles: Sparkles,
|
|
49
|
+
images: Images,
|
|
50
|
+
bot: Bot,
|
|
51
|
+
key: Key,
|
|
41
52
|
'key-round': KeyRound,
|
|
42
|
-
'
|
|
53
|
+
'credit-card': CreditCard,
|
|
54
|
+
'book-open': BookOpen,
|
|
55
|
+
wrench: Wrench,
|
|
56
|
+
shield: Shield,
|
|
43
57
|
};
|
|
44
58
|
|
|
45
|
-
function getIcon(name?:
|
|
59
|
+
function getIcon(name?: unknown): typeof LayoutDashboard {
|
|
46
60
|
if (!name) return depth === 0 ? Settings : Folder;
|
|
47
|
-
if (typeof name !== 'string') return name
|
|
61
|
+
if (typeof name !== 'string') return name as typeof LayoutDashboard;
|
|
48
62
|
return iconMap[name] ?? Settings;
|
|
49
63
|
}
|
|
50
64
|
|
|
@@ -85,13 +99,11 @@
|
|
|
85
99
|
<!-- Parent node with children: render as collapsible group -->
|
|
86
100
|
<Collapsible.Root bind:open={isOpen}>
|
|
87
101
|
<Collapsible.Trigger
|
|
88
|
-
class="flex w-full items-center justify-between rounded-
|
|
102
|
+
class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
89
103
|
{childActive
|
|
90
|
-
? '
|
|
91
|
-
: 'text-sidebar-foreground/
|
|
104
|
+
? 'text-sidebar-foreground bg-sidebar-accent/30'
|
|
105
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
92
106
|
style="padding-left: {12 + depth * 12}px"
|
|
93
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
94
|
-
data-active={childActive ? "true" : "false"}
|
|
95
107
|
>
|
|
96
108
|
<span class="flex items-center gap-3">
|
|
97
109
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
@@ -100,7 +112,7 @@
|
|
|
100
112
|
<ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
|
|
101
113
|
</Collapsible.Trigger>
|
|
102
114
|
<Collapsible.Content>
|
|
103
|
-
<div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px"
|
|
115
|
+
<div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
|
|
104
116
|
{#each item.children as child, _i (_i)}
|
|
105
117
|
<SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
|
|
106
118
|
{/each}
|
|
@@ -117,12 +129,10 @@
|
|
|
117
129
|
href={finalHref}
|
|
118
130
|
target={isExternal ? '_blank' : undefined}
|
|
119
131
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
120
|
-
class="flex items-center justify-center rounded-
|
|
132
|
+
class="flex items-center justify-center rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
121
133
|
{active
|
|
122
|
-
? 'bg-
|
|
123
|
-
: 'text-sidebar-foreground/
|
|
124
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
125
|
-
data-active={active ? "true" : "false"}
|
|
134
|
+
? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5'
|
|
135
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
126
136
|
>
|
|
127
137
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
128
138
|
</a>
|
|
@@ -138,17 +148,12 @@
|
|
|
138
148
|
href={finalHref}
|
|
139
149
|
target={isExternal ? '_blank' : undefined}
|
|
140
150
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
141
|
-
class="
|
|
151
|
+
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
142
152
|
{active
|
|
143
|
-
? 'bg-
|
|
144
|
-
: 'text-sidebar-foreground/
|
|
153
|
+
? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5 font-semibold before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3px] before:bg-primary before:rounded-r-md overflow-hidden relative'
|
|
154
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
145
155
|
style="padding-left: {12 + depth * 12}px"
|
|
146
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
147
|
-
data-active={active ? "true" : "false"}
|
|
148
156
|
>
|
|
149
|
-
{#if active && !collapsed}
|
|
150
|
-
<div class="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-1 rounded-r-full bg-primary"></div>
|
|
151
|
-
{/if}
|
|
152
157
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
153
158
|
<span class="flex-1">{label}</span>
|
|
154
159
|
{#if isExternal}
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
}
|
|
80
80
|
</script>
|
|
81
81
|
|
|
82
|
-
<div class="flex min-h-[70vh] items-center justify-center p-4">
|
|
82
|
+
<div class="flex min-h-[70vh] items-center justify-center p-4" data-svadmin-content-page="auth-2fa">
|
|
83
83
|
<div class="w-full max-w-lg space-y-6">
|
|
84
84
|
{#if currentStep === 'intro' && !is2faEnabled}
|
|
85
85
|
<!-- Intro -->
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
|
-
<div class="space-y-6">
|
|
24
|
+
<div class="space-y-6" data-svadmin-content-page="account">
|
|
25
25
|
<div>
|
|
26
26
|
<h2 class="text-xl font-semibold text-foreground">{t('account.companyProfile')}</h2>
|
|
27
27
|
<p class="mt-1 text-sm text-muted-foreground">{t('account.companyProfileDescription')}</p>
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
</script>
|
|
61
61
|
|
|
62
|
-
<div class="space-y-6">
|
|
62
|
+
<div class="space-y-6" data-svadmin-content-page="account">
|
|
63
63
|
<div>
|
|
64
64
|
<h2 class="text-xl font-semibold text-foreground">{t('account.importMembers')}</h2>
|
|
65
65
|
<p class="mt-1 text-sm text-muted-foreground">{t('account.importMembersDescription')}</p>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
const initials = (name: string) => name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
44
44
|
</script>
|
|
45
45
|
|
|
46
|
-
<div class="space-y-6">
|
|
46
|
+
<div class="space-y-6" data-svadmin-content-page="account">
|
|
47
47
|
<div class="flex items-start justify-between gap-4">
|
|
48
48
|
<div>
|
|
49
49
|
<h2 class="text-xl font-semibold text-foreground">{t('account.membersStarter')}</h2>
|