sveltekit-admin 0.1.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.
@@ -0,0 +1,328 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { page } from '$app/stores';
4
+
5
+ interface Props {
6
+ title?: string;
7
+ logo?: string;
8
+ primaryColor?: string;
9
+ models?: Array<{ name: string; label?: string; icon?: string }>;
10
+ basePath?: string;
11
+ user?: { name?: string; email?: string };
12
+ children: Snippet;
13
+ }
14
+
15
+ let {
16
+ title = 'Admin',
17
+ logo,
18
+ primaryColor = '#6366f1',
19
+ models = [],
20
+ basePath = '/admin',
21
+ user,
22
+ children
23
+ }: Props = $props();
24
+
25
+ let sidebarOpen = $state(true);
26
+
27
+ const currentPath = $derived($page.url.pathname);
28
+
29
+ function isActive(path: string): boolean {
30
+ return currentPath.startsWith(path);
31
+ }
32
+
33
+ const icons: Record<string, string> = {
34
+ users: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
35
+ default: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/></svg>`,
36
+ dashboard: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>`,
37
+ menu: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>`,
38
+ logout: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/></svg>`
39
+ };
40
+
41
+ function getIcon(name?: string): string {
42
+ if (!name) return icons.default;
43
+ const lower = name.toLowerCase();
44
+ if (lower.includes('user')) return icons.users;
45
+ return icons[lower] || icons.default;
46
+ }
47
+
48
+ function toLabel(name: string): string {
49
+ return name.replace(/([A-Z])/g, ' $1').trim();
50
+ }
51
+ </script>
52
+
53
+ <div class="ska-layout" style="--ska-primary: {primaryColor}">
54
+ <!-- Sidebar -->
55
+ <aside class="ska-sidebar" class:ska-sidebar--closed={!sidebarOpen}>
56
+ <div class="ska-sidebar__header">
57
+ {#if logo}
58
+ <img src={logo} alt={title} class="ska-sidebar__logo" />
59
+ {:else}
60
+ <span class="ska-sidebar__title">{title}</span>
61
+ {/if}
62
+ </div>
63
+
64
+ <nav class="ska-sidebar__nav">
65
+ <a
66
+ href={basePath}
67
+ class="ska-sidebar__link"
68
+ class:ska-sidebar__link--active={currentPath === basePath}
69
+ >
70
+ {@html icons.dashboard}
71
+ <span>Dashboard</span>
72
+ </a>
73
+
74
+ <div class="ska-sidebar__section">
75
+ <span class="ska-sidebar__section-title">Models</span>
76
+ </div>
77
+
78
+ {#each models as model}
79
+ <a
80
+ href="{basePath}/{model.name.toLowerCase()}"
81
+ class="ska-sidebar__link"
82
+ class:ska-sidebar__link--active={isActive(`${basePath}/${model.name.toLowerCase()}`)}
83
+ >
84
+ {@html getIcon(model.icon || model.name)}
85
+ <span>{model.label || toLabel(model.name)}</span>
86
+ </a>
87
+ {/each}
88
+ </nav>
89
+
90
+ {#if user}
91
+ <div class="ska-sidebar__footer">
92
+ <div class="ska-sidebar__user">
93
+ <div class="ska-sidebar__avatar">
94
+ {user.name?.[0] || user.email?.[0] || 'A'}
95
+ </div>
96
+ <div class="ska-sidebar__user-info">
97
+ <span class="ska-sidebar__user-name">{user.name || 'Admin'}</span>
98
+ <span class="ska-sidebar__user-email">{user.email}</span>
99
+ </div>
100
+ </div>
101
+ <a href="/api/auth/logout" class="ska-sidebar__link ska-sidebar__link--logout">
102
+ {@html icons.logout}
103
+ <span>Logout</span>
104
+ </a>
105
+ </div>
106
+ {/if}
107
+ </aside>
108
+
109
+ <!-- Main content -->
110
+ <div class="ska-main">
111
+ <header class="ska-header">
112
+ <button
113
+ class="ska-header__toggle"
114
+ onclick={() => sidebarOpen = !sidebarOpen}
115
+ aria-label="Toggle sidebar"
116
+ >
117
+ {@html icons.menu}
118
+ </button>
119
+ </header>
120
+
121
+ <main class="ska-content">
122
+ {@render children()}
123
+ </main>
124
+ </div>
125
+ </div>
126
+
127
+ <style>
128
+ .ska-layout {
129
+ --ska-bg: #f8fafc;
130
+ --ska-sidebar-bg: #1e293b;
131
+ --ska-sidebar-text: #94a3b8;
132
+ --ska-sidebar-text-active: #ffffff;
133
+ --ska-border: #e2e8f0;
134
+ --ska-text: #1e293b;
135
+ --ska-text-muted: #64748b;
136
+
137
+ display: flex;
138
+ min-height: 100vh;
139
+ background: var(--ska-bg);
140
+ font-family: system-ui, -apple-system, sans-serif;
141
+ }
142
+
143
+ .ska-sidebar {
144
+ width: 260px;
145
+ background: var(--ska-sidebar-bg);
146
+ display: flex;
147
+ flex-direction: column;
148
+ transition: width 0.2s ease, transform 0.2s ease;
149
+ position: fixed;
150
+ top: 0;
151
+ left: 0;
152
+ bottom: 0;
153
+ z-index: 40;
154
+ }
155
+
156
+ .ska-sidebar--closed {
157
+ width: 0;
158
+ transform: translateX(-100%);
159
+ }
160
+
161
+ .ska-sidebar__header {
162
+ padding: 1.25rem 1rem;
163
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
164
+ }
165
+
166
+ .ska-sidebar__title {
167
+ font-size: 1.25rem;
168
+ font-weight: 700;
169
+ color: white;
170
+ }
171
+
172
+ .ska-sidebar__logo {
173
+ max-height: 2rem;
174
+ width: auto;
175
+ }
176
+
177
+ .ska-sidebar__nav {
178
+ flex: 1;
179
+ padding: 1rem 0;
180
+ overflow-y: auto;
181
+ }
182
+
183
+ .ska-sidebar__section {
184
+ padding: 1rem 1rem 0.5rem;
185
+ }
186
+
187
+ .ska-sidebar__section-title {
188
+ font-size: 0.75rem;
189
+ font-weight: 600;
190
+ text-transform: uppercase;
191
+ letter-spacing: 0.05em;
192
+ color: var(--ska-sidebar-text);
193
+ }
194
+
195
+ .ska-sidebar__link {
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 0.75rem;
199
+ padding: 0.625rem 1rem;
200
+ color: var(--ska-sidebar-text);
201
+ text-decoration: none;
202
+ font-size: 0.875rem;
203
+ transition: all 0.15s ease;
204
+ }
205
+
206
+ .ska-sidebar__link:hover {
207
+ background: rgba(255, 255, 255, 0.05);
208
+ color: var(--ska-sidebar-text-active);
209
+ }
210
+
211
+ .ska-sidebar__link--active {
212
+ background: var(--ska-primary);
213
+ color: var(--ska-sidebar-text-active);
214
+ }
215
+
216
+ .ska-sidebar__link--logout {
217
+ margin-top: 0.5rem;
218
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
219
+ }
220
+
221
+ .ska-sidebar__footer {
222
+ padding: 1rem;
223
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
224
+ }
225
+
226
+ .ska-sidebar__user {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 0.75rem;
230
+ margin-bottom: 0.5rem;
231
+ }
232
+
233
+ .ska-sidebar__avatar {
234
+ width: 2.25rem;
235
+ height: 2.25rem;
236
+ border-radius: 50%;
237
+ background: var(--ska-primary);
238
+ color: white;
239
+ display: flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ font-weight: 600;
243
+ font-size: 0.875rem;
244
+ }
245
+
246
+ .ska-sidebar__user-info {
247
+ display: flex;
248
+ flex-direction: column;
249
+ overflow: hidden;
250
+ }
251
+
252
+ .ska-sidebar__user-name {
253
+ font-size: 0.875rem;
254
+ font-weight: 500;
255
+ color: white;
256
+ white-space: nowrap;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ }
260
+
261
+ .ska-sidebar__user-email {
262
+ font-size: 0.75rem;
263
+ color: var(--ska-sidebar-text);
264
+ white-space: nowrap;
265
+ overflow: hidden;
266
+ text-overflow: ellipsis;
267
+ }
268
+
269
+ .ska-main {
270
+ flex: 1;
271
+ margin-left: 260px;
272
+ transition: margin-left 0.2s ease;
273
+ min-height: 100vh;
274
+ }
275
+
276
+ .ska-sidebar--closed + .ska-main {
277
+ margin-left: 0;
278
+ }
279
+
280
+ .ska-header {
281
+ height: 3.5rem;
282
+ background: white;
283
+ border-bottom: 1px solid var(--ska-border);
284
+ display: flex;
285
+ align-items: center;
286
+ padding: 0 1rem;
287
+ position: sticky;
288
+ top: 0;
289
+ z-index: 30;
290
+ }
291
+
292
+ .ska-header__toggle {
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ width: 2.25rem;
297
+ height: 2.25rem;
298
+ border: none;
299
+ background: transparent;
300
+ border-radius: 0.375rem;
301
+ color: var(--ska-text-muted);
302
+ cursor: pointer;
303
+ transition: all 0.15s ease;
304
+ }
305
+
306
+ .ska-header__toggle:hover {
307
+ background: var(--ska-bg);
308
+ color: var(--ska-text);
309
+ }
310
+
311
+ .ska-content {
312
+ padding: 1.5rem;
313
+ }
314
+
315
+ @media (max-width: 768px) {
316
+ .ska-sidebar {
317
+ transform: translateX(-100%);
318
+ }
319
+
320
+ .ska-sidebar:not(.ska-sidebar--closed) {
321
+ transform: translateX(0);
322
+ }
323
+
324
+ .ska-main {
325
+ margin-left: 0;
326
+ }
327
+ }
328
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title?: string;
4
+ logo?: string;
5
+ primaryColor?: string;
6
+ models?: Array<{
7
+ name: string;
8
+ label?: string;
9
+ icon?: string;
10
+ }>;
11
+ basePath?: string;
12
+ user?: {
13
+ name?: string;
14
+ email?: string;
15
+ };
16
+ children: Snippet;
17
+ }
18
+ declare const AdminLayout: import("svelte").Component<Props, {}, "">;
19
+ type AdminLayout = ReturnType<typeof AdminLayout>;
20
+ export default AdminLayout;