@svadmin/ui 0.36.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 +34 -16
- package/dist/components/AutoForm.svelte +32 -47
- package/dist/components/AutoTable.svelte +26 -31
- package/dist/components/Header.svelte +6 -6
- package/dist/components/Layout.svelte +7 -7
- package/dist/components/PageHeader.svelte +7 -9
- package/dist/components/SettingsPage.svelte +75 -41
- package/dist/components/ShowPage.svelte +9 -9
- package/dist/components/Sidebar.svelte +189 -272
- package/dist/components/SidebarItem.svelte +43 -72
- package/package.json +2 -2
|
@@ -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
|
|
|
@@ -64,41 +78,16 @@
|
|
|
64
78
|
return menuItem.children?.some((c: MenuItem) => hasActiveChild(c)) ?? false;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
function getItemBadge(menuItem: MenuItem): string | undefined {
|
|
68
|
-
const meta = menuItem.meta as (MenuItem['meta'] & { badge?: unknown }) | undefined;
|
|
69
|
-
return typeof meta?.badge === 'string' ? meta.badge : undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function aggregateBadges(menuItems: MenuItem[] | undefined): string | undefined {
|
|
73
|
-
const total = menuItems?.reduce((sum, menuItem) => {
|
|
74
|
-
const ownBadge = Number(getItemBadge(menuItem));
|
|
75
|
-
const childBadge = Number(aggregateBadges(menuItem.children));
|
|
76
|
-
return sum + (Number.isFinite(ownBadge) ? ownBadge : 0) + (Number.isFinite(childBadge) ? childBadge : 0);
|
|
77
|
-
}, 0) ?? 0;
|
|
78
|
-
|
|
79
|
-
return total > 0 ? String(total) : undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
81
|
const hasChildren = $derived(item.children && item.children.length > 0);
|
|
83
82
|
const active = $derived(isActive(item.href));
|
|
84
83
|
const childActive = $derived(hasActiveChild(item));
|
|
85
84
|
const isExternal = $derived(item.target === '_blank' || item.href?.startsWith('http'));
|
|
86
|
-
const badge = $derived(getItemBadge(item));
|
|
87
|
-
const childrenBadge = $derived(aggregateBadges(item.children));
|
|
88
85
|
|
|
89
86
|
const finalHref = $derived(isExternal ? item.href : (item.href ? formatLink(item.href) : undefined));
|
|
90
|
-
// Auto-open the active branch once, then respect manual collapse/expand.
|
|
91
|
-
let isOpen = $state(false);
|
|
92
|
-
let touchedOpen = $state(false);
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
$effect(() => {
|
|
100
|
-
if (childActive && !touchedOpen && !isOpen) isOpen = true;
|
|
101
|
-
});
|
|
88
|
+
// Track open state — auto-open if a child is active
|
|
89
|
+
let isOpen = $state(false);
|
|
90
|
+
$effect(() => { if (childActive) isOpen = true; });
|
|
102
91
|
|
|
103
92
|
const Icon = $derived(getIcon(item.icon));
|
|
104
93
|
const label = $derived(getLabel(item));
|
|
@@ -108,33 +97,22 @@
|
|
|
108
97
|
<!-- hidden item, render nothing -->
|
|
109
98
|
{:else if hasChildren && !collapsed}
|
|
110
99
|
<!-- Parent node with children: render as collapsible group -->
|
|
111
|
-
<Collapsible.Root open={isOpen}
|
|
100
|
+
<Collapsible.Root bind:open={isOpen}>
|
|
112
101
|
<Collapsible.Trigger
|
|
113
|
-
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
|
|
114
103
|
{childActive
|
|
115
|
-
? 'text-
|
|
116
|
-
: 'text-sidebar-foreground/
|
|
117
|
-
style="padding-left: {
|
|
118
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
119
|
-
data-active={childActive ? "true" : "false"}
|
|
104
|
+
? 'text-sidebar-foreground bg-sidebar-accent/30'
|
|
105
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
106
|
+
style="padding-left: {12 + depth * 12}px"
|
|
120
107
|
>
|
|
121
108
|
<span class="flex items-center gap-3">
|
|
122
109
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
123
110
|
<span>{label}</span>
|
|
124
111
|
</span>
|
|
125
|
-
<
|
|
126
|
-
{#if childrenBadge}
|
|
127
|
-
<span class="rounded-full {childActive ? 'bg-primary/12 text-primary' : 'bg-sidebar-accent text-sidebar-foreground/60'} px-2 py-0.5 text-[10px] font-semibold">{childrenBadge}</span>
|
|
128
|
-
{/if}
|
|
129
|
-
<ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
|
|
130
|
-
</span>
|
|
112
|
+
<ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
|
|
131
113
|
</Collapsible.Trigger>
|
|
132
114
|
<Collapsible.Content>
|
|
133
|
-
<div
|
|
134
|
-
class="mt-0.5 {depth >= 1 ? 'divide-y divide-border/45' : 'space-y-0'}"
|
|
135
|
-
style="padding-left: {depth > 0 ? 0 : 0}px"
|
|
136
|
-
data-sidebar="menu-sub"
|
|
137
|
-
>
|
|
115
|
+
<div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
|
|
138
116
|
{#each item.children as child, _i (_i)}
|
|
139
117
|
<SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
|
|
140
118
|
{/each}
|
|
@@ -151,12 +129,10 @@
|
|
|
151
129
|
href={finalHref}
|
|
152
130
|
target={isExternal ? '_blank' : undefined}
|
|
153
131
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
154
|
-
class="
|
|
132
|
+
class="flex items-center justify-center rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
155
133
|
{active
|
|
156
|
-
? 'text-primary'
|
|
157
|
-
: 'text-sidebar-foreground/
|
|
158
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
159
|
-
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'}"
|
|
160
136
|
>
|
|
161
137
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
162
138
|
</a>
|
|
@@ -172,21 +148,16 @@
|
|
|
172
148
|
href={finalHref}
|
|
173
149
|
target={isExternal ? '_blank' : undefined}
|
|
174
150
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
175
|
-
class="
|
|
151
|
+
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
176
152
|
{active
|
|
177
|
-
? 'text-primary font-semibold'
|
|
178
|
-
: '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'}"
|
|
179
155
|
style="padding-left: {12 + depth * 12}px"
|
|
180
|
-
data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
|
|
181
|
-
data-active={active ? "true" : "false"}
|
|
182
156
|
>
|
|
183
157
|
<Icon class="h-4 w-4 flex-shrink-0" />
|
|
184
158
|
<span class="flex-1">{label}</span>
|
|
185
159
|
{#if isExternal}
|
|
186
160
|
<ExternalLink class="h-3 w-3 opacity-50" />
|
|
187
161
|
{/if}
|
|
188
|
-
{#if badge}
|
|
189
|
-
<span class="rounded-full {active ? 'bg-primary/12 text-primary' : 'bg-sidebar-accent text-sidebar-foreground/60'} px-2 py-0.5 text-[10px] font-semibold">{badge}</span>
|
|
190
|
-
{/if}
|
|
191
162
|
</a>
|
|
192
163
|
{/if}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"./components/*": "./dist/components/*"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@svadmin/core": "^0.
|
|
36
|
+
"@svadmin/core": "^0.30.0",
|
|
37
37
|
"@tanstack/svelte-query": "^6.1.29",
|
|
38
38
|
"highlight.js": "^11.11.1",
|
|
39
39
|
"isomorphic-dompurify": "^3.15.0",
|