@svadmin/ui 0.37.0 → 0.38.1

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 (48) hide show
  1. package/dist/app.css +42 -88
  2. package/dist/components/AdminApp.svelte +38 -35
  3. package/dist/components/CanAccess.svelte +3 -2
  4. package/dist/components/CanAccess.svelte.d.ts +1 -0
  5. package/dist/components/CreateInferencer.svelte +13 -0
  6. package/dist/components/CreateInferencer.svelte.d.ts +7 -0
  7. package/dist/components/EditInferencer.svelte +21 -0
  8. package/dist/components/EditInferencer.svelte.d.ts +9 -0
  9. package/dist/components/Header.svelte +4 -4
  10. package/dist/components/Layout.svelte +26 -13
  11. package/dist/components/ListInferencer.svelte +14 -0
  12. package/dist/components/ListInferencer.svelte.d.ts +7 -0
  13. package/dist/components/ResourceInferencer.svelte +28 -0
  14. package/dist/components/ResourceInferencer.svelte.d.ts +7 -0
  15. package/dist/components/ResourceOperationsPage.svelte +168 -21
  16. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -0
  17. package/dist/components/SettingsPage.svelte +3 -2
  18. package/dist/components/ShowInferencer.svelte +21 -0
  19. package/dist/components/ShowInferencer.svelte.d.ts +9 -0
  20. package/dist/components/ShowPage.svelte +2 -2
  21. package/dist/components/ShowPage.svelte.d.ts +1 -0
  22. package/dist/components/Sidebar.svelte +101 -104
  23. package/dist/components/Sidebar.svelte.d.ts +0 -1
  24. package/dist/components/SidebarItem.svelte +114 -52
  25. package/dist/components/StepsForm.svelte +2 -1
  26. package/dist/components/UndoableNotification.svelte +2 -1
  27. package/dist/components/account/TeamMembersPage.svelte +9 -3
  28. package/dist/components/buttons/CloneButton.svelte +10 -2
  29. package/dist/components/buttons/CloneButton.svelte.d.ts +2 -4
  30. package/dist/components/buttons/CreateButton.svelte +9 -2
  31. package/dist/components/buttons/CreateButton.svelte.d.ts +2 -4
  32. package/dist/components/buttons/DeleteButton.svelte +10 -2
  33. package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -4
  34. package/dist/components/buttons/EditButton.svelte +10 -2
  35. package/dist/components/buttons/EditButton.svelte.d.ts +2 -4
  36. package/dist/components/buttons/ExportButton.svelte +9 -2
  37. package/dist/components/buttons/ExportButton.svelte.d.ts +2 -4
  38. package/dist/components/buttons/ImportButton.svelte +9 -2
  39. package/dist/components/buttons/ImportButton.svelte.d.ts +2 -4
  40. package/dist/components/buttons/ShowButton.svelte +10 -2
  41. package/dist/components/buttons/ShowButton.svelte.d.ts +2 -4
  42. package/dist/components/buttons/access-control.d.ts +7 -0
  43. package/dist/components/buttons/access-control.js +5 -0
  44. package/dist/components/ui/sheet/sheet.svelte +3 -3
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.js +5 -0
  47. package/dist/router-state.svelte.js +15 -0
  48. package/package.json +2 -2
@@ -18,8 +18,8 @@
18
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, Mail, Send, ShoppingBag, Briefcase, Building2,
22
- Calendar, Bell, Clock, Trash2, Shield
21
+ Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle, Mail, Map as MapIcon, MessageSquare, Send, ShoppingBag, Star, Briefcase, Building2,
22
+ Calendar, Bell, Clock, Tag, Trash2, Shield
23
23
  } from '@lucide/svelte';
24
24
 
25
25
  let { collapsed, identity, title, onToggle, onLogout, menu, routeMode = 'auto' }: {
@@ -29,7 +29,6 @@
29
29
  onToggle: () => void;
30
30
  onLogout: () => void;
31
31
  menu?: MenuItem[];
32
- /** 'hash' for SPA hash routing, 'path' for SvelteKit/filesystem routing, 'auto' to detect */
33
32
  routeMode?: 'hash' | 'path' | 'auto';
34
33
  } = $props();
35
34
 
@@ -68,13 +67,17 @@
68
67
  'sliders-horizontal': SlidersHorizontal,
69
68
  'alert-triangle': AlertTriangle,
70
69
  'mail': Mail,
70
+ 'map': MapIcon,
71
+ 'message-square': MessageSquare,
71
72
  'send': Send,
72
73
  'shopping-bag': ShoppingBag,
74
+ 'star': Star,
73
75
  'briefcase': Briefcase,
74
76
  'building': Building2,
75
77
  'calendar': Calendar,
76
78
  'bell': Bell,
77
79
  'clock': Clock,
80
+ 'tag': Tag,
78
81
  'trash': Trash2,
79
82
  'shield': Shield,
80
83
  };
@@ -87,11 +90,10 @@
87
90
  }
88
91
 
89
92
  interface NavGroup {
90
- name: string | null; // null = ungrouped
93
+ name: string | null;
91
94
  items: NavItem[];
92
95
  }
93
96
 
94
- // Use effect to build nav items, taking access control into account
95
97
  let navItems = $state.raw<NavItem[]>([]);
96
98
 
97
99
  $effect(() => {
@@ -125,7 +127,6 @@
125
127
  return () => { cancelled = true; };
126
128
  });
127
129
 
128
- /** Toggle between available locales */
129
130
  function toggleLocale() {
130
131
  const locales = getAvailableLocales();
131
132
  const current = getLocale();
@@ -134,10 +135,8 @@
134
135
  setLocale(next);
135
136
  }
136
137
 
137
- /** Short display label for current locale */
138
138
  const localeLabel = $derived(getLocale() === 'zh-CN' ? '中' : 'EN');
139
139
 
140
- // Track current hash for active state
141
140
  const path = $derived(getPath());
142
141
 
143
142
  function isActive(itemPath: string): boolean {
@@ -145,7 +144,6 @@
145
144
  return path === itemPath || path.startsWith(itemPath + "/");
146
145
  }
147
146
 
148
- // Group nav items by group field (null = ungrouped)
149
147
  const navGroups = $derived.by((): NavGroup[] => {
150
148
  const groups: NavGroup[] = [];
151
149
  const groupMap = new Map<string | null, NavItem[]>();
@@ -162,13 +160,11 @@
162
160
  return groups;
163
161
  });
164
162
 
165
- // Track which groups are open
166
163
  let openGroups = $state<Set<string>>(new Set());
167
164
  let colorPickerOpen = $state(false);
168
165
  let colorPickerRef = $state<HTMLDivElement | null>(null);
169
166
  let colorPickerOpenedAt = 0;
170
167
 
171
- // Density state
172
168
  let density = $state<'compact' | 'standard'>('standard');
173
169
 
174
170
  $effect(() => {
@@ -185,15 +181,12 @@
185
181
  }
186
182
  });
187
183
 
188
- const pyClass = $derived(density === 'compact' ? 'py-1.5' : 'py-2.5');
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';
184
+ const pyClass = $derived(density === 'compact' ? 'py-1' : 'py-[7px]');
185
+ const pyClassGroupItem = $derived(density === 'compact' ? 'py-1' : 'py-[6px]');
191
186
 
192
- // Click-outside to close color picker
193
187
  $effect(() => {
194
188
  if (!colorPickerOpen) return;
195
189
  function handleMouseDown(e: MouseEvent) {
196
- // Ignore if opened within last 200ms (same interaction)
197
190
  if (Date.now() - colorPickerOpenedAt < 200) return;
198
191
  if (colorPickerRef && !colorPickerRef.contains(e.target as Node)) {
199
192
  colorPickerOpen = false;
@@ -210,68 +203,59 @@
210
203
 
211
204
  <aside
212
205
  aria-label="Sidebar navigation"
213
- class="fixed inset-y-0 left-0 z-30 flex flex-col backdrop-blur-xl transition-all duration-300"
206
+ class="fixed inset-y-0 left-0 z-30 flex flex-col border-r border-border/40 transition-all duration-300"
214
207
  style="background-color: var(--sidebar);"
215
- class:w-64={!collapsed}
216
- class:w-16={collapsed}
208
+ class:w-[252px]={!collapsed}
209
+ class:w-[70px]={collapsed}
217
210
  >
218
- <!-- Logo -->
219
- <div class="flex h-16 items-center justify-between px-4">
211
+ <div class="flex h-[70px] items-center shrink-0" class:px-5={!collapsed} class:justify-center={collapsed}>
220
212
  {#if !collapsed}
221
- <div class="flex items-center gap-3">
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>
224
- </div>
213
+ <a href="#/" class="flex items-center gap-2.5 group" onclick={(e) => { e.preventDefault(); navigate('/'); }}>
214
+ <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
215
+ <span class="font-semibold text-[15px] tracking-[-0.01em] text-sidebar-foreground">{title}</span>
216
+ </a>
225
217
  {:else}
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>
227
- {/if}
228
- {#if !collapsed}
229
- <TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle}>
230
- <ChevronLeft class="h-4 w-4" />
231
- </TooltipButton>
232
- {:else}
233
- <TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle} class="mx-auto mt-2 mb-2 block">
234
- <ChevronRight class="h-4 w-4" />
235
- </TooltipButton>
218
+ <a href="#/" class="group" aria-label={title} onclick={(e) => { e.preventDefault(); navigate('/'); }}>
219
+ <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
220
+ </a>
236
221
  {/if}
237
222
  </div>
238
223
 
239
- <ScrollArea class="flex-1">
240
- <nav aria-label="Main menu" class="py-4 px-3 space-y-1">
224
+ <ScrollArea class="flex-1 sidebar-scroll">
225
+ <nav aria-label="Main menu" class="pb-4" class:px-[10px]={!collapsed} class:px-2={collapsed}>
241
226
  {#if menu && menu.length > 0}
242
- <!-- Custom multi-level menu -->
243
- {#each menu as item, _i (_i)}
244
- <SidebarItem {item} currentPath={path} {collapsed} depth={0} />
245
- {/each}
227
+ <div class="space-y-[2px]">
228
+ {#each menu as item, _i (_i)}
229
+ <SidebarItem {item} currentPath={path} {collapsed} depth={0} />
230
+ {/each}
231
+ </div>
246
232
  {:else}
247
- <!-- Auto-generated menu from resources (fallback) -->
248
233
  {#each navGroups as group, _i (_i)}
249
234
  {#if group.name && !collapsed}
250
- <!-- Grouped section with Collapsible -->
251
235
  <Collapsible.Root open={openGroups.has(group.name)} onOpenChange={(isOpen) => {
252
236
  const next = new Set(openGroups);
253
237
  if (isOpen) next.add(group.name as string); else next.delete(group.name as string);
254
238
  openGroups = next;
255
239
  }}>
256
240
  <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"
241
+ class="flex w-full items-center justify-between px-[10px] py-[6px] mt-4 mb-[2px] text-[10.5px] font-semibold tracking-[0.06em] uppercase text-sidebar-foreground/45 hover:text-sidebar-foreground/70 transition-colors"
258
242
  >
259
243
  <span>{group.name}</span>
260
- <ChevronDown class="h-3 w-3 transition-transform {openGroups.has(group.name) ? 'rotate-180' : ''}" />
244
+ <ChevronDown class="h-3 w-3 transition-transform duration-200 {openGroups.has(group.name) ? 'rotate-180' : ''}" />
261
245
  </Collapsible.Trigger>
262
246
  <Collapsible.Content>
263
- <div class="mt-1 space-y-1">
247
+ <div class="space-y-[2px]">
264
248
  {#each group.items as item, _i (_i)}
265
249
  {@const active = isActive(item.path)}
266
250
  <a
267
251
  href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
268
252
  onclick={(e) => { e.preventDefault(); navigate(item.path); }}
269
- class="flex items-center gap-3 rounded-xl px-3 {pyClassGroupItem} text-sm font-medium transition-all duration-300
253
+ class="sidebar-menu-item flex items-center gap-2.5 rounded-md px-[10px] {pyClassGroupItem} text-[13px] font-medium transition-colors duration-150
270
254
  {active
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'}"
255
+ ? 'sidebar-menu-item-active bg-sidebar-accent text-primary'
256
+ : 'text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent/50'}"
273
257
  >
274
- <item.Icon class="h-4 w-4 flex-shrink-0" />
258
+ <item.Icon class="h-[18px] w-[18px] flex-shrink-0 {active ? 'text-primary' : 'text-sidebar-foreground/50'}" />
275
259
  <span>{item.label}</span>
276
260
  </a>
277
261
  {/each}
@@ -279,7 +263,6 @@
279
263
  </Collapsible.Content>
280
264
  </Collapsible.Root>
281
265
  {:else}
282
- <!-- Ungrouped items (flat) -->
283
266
  {#each group.items as item, _i (_i)}
284
267
  {@const active = isActive(item.path)}
285
268
  {#if collapsed}
@@ -290,12 +273,12 @@
290
273
  {...props}
291
274
  href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
292
275
  onclick={(e) => { e.preventDefault(); navigate(item.path); }}
293
- class="flex items-center justify-center gap-3 rounded-xl px-3 {pyClass} text-sm font-medium transition-all duration-300
276
+ class="sidebar-menu-item flex items-center justify-center rounded-md px-2 {pyClass} transition-colors duration-150
294
277
  {active
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'}"
278
+ ? 'sidebar-menu-item-active bg-sidebar-accent text-primary'
279
+ : 'text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
297
280
  >
298
- <item.Icon class="h-4 w-4 flex-shrink-0" />
281
+ <item.Icon class="h-[18px] w-[18px] flex-shrink-0" />
299
282
  </a>
300
283
  {/snippet}
301
284
  </Tooltip.Trigger>
@@ -307,12 +290,12 @@
307
290
  <a
308
291
  href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
309
292
  onclick={(e) => { e.preventDefault(); navigate(item.path); }}
310
- class="flex items-center gap-3 rounded-xl px-3 {pyClass} text-sm font-medium transition-all duration-300
293
+ class="sidebar-menu-item flex items-center gap-2.5 rounded-md px-[10px] {pyClass} text-[13px] font-medium transition-colors duration-150
311
294
  {active
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'
295
+ ? 'sidebar-menu-item-active bg-sidebar-accent text-primary'
313
296
  : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
314
297
  >
315
- <item.Icon class="h-4 w-4 flex-shrink-0" />
298
+ <item.Icon class="h-[18px] w-[18px] flex-shrink-0 {active ? 'text-primary' : 'text-sidebar-foreground/50'}" />
316
299
  <span>{item.label}</span>
317
300
  </a>
318
301
  {/if}
@@ -323,24 +306,21 @@
323
306
  </nav>
324
307
  </ScrollArea>
325
308
 
326
- <!-- Footer -->
327
- <div>
328
- <!-- Color theme picker -->
309
+ <div class="shrink-0 border-t border-border/40">
329
310
  {#if !collapsed}
330
311
  <div class="px-3 pt-3 pb-1">
331
312
  <div class="relative" bind:this={colorPickerRef}>
332
- <Button
333
- variant="ghost"
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"
313
+ <button
314
+ class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-[12px] text-sidebar-foreground/60 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground transition-colors"
335
315
  onclick={() => { if (!colorPickerOpen) colorPickerOpenedAt = Date.now(); colorPickerOpen = !colorPickerOpen; }}
336
316
  >
337
- <Palette class="h-4 w-4" />
338
- <span class="flex-1 text-left text-xs">{t('common.toggleTheme')}</span>
317
+ <Palette class="h-3.5 w-3.5" />
318
+ <span class="flex-1 text-left">{t('common.toggleTheme')}</span>
339
319
  <span
340
- class="h-3.5 w-3.5 rounded-full ring-1 ring-offset-1 ring-offset-sidebar"
320
+ class="h-3 w-3 rounded-full ring-1 ring-offset-1 ring-offset-sidebar"
341
321
  style="background-color: {getColorThemes().find(c => c.id === getColorTheme())?.color ?? '#6366f1'}; --tw-ring-color: {getColorThemes().find(c => c.id === getColorTheme())?.color ?? '#6366f1'}"
342
322
  ></span>
343
- </Button>
323
+ </button>
344
324
  {#if colorPickerOpen}
345
325
  <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">
346
326
  {#each getColorThemes() as ct, _i (_i)}
@@ -349,7 +329,7 @@
349
329
  onclick={() => { setColorTheme(ct.id as typeof ct.id & import('@svadmin/core').ColorTheme); colorPickerOpen = false; }}
350
330
  >
351
331
  <span
352
- class="h-3.5 w-3.5 rounded-full {getColorTheme() === ct.id ? 'ring-2 ring-offset-1 scale-110' : 'opacity-70'}"
332
+ class="h-3 w-3 rounded-full {getColorTheme() === ct.id ? 'ring-2 ring-offset-1 scale-110' : 'opacity-70'}"
353
333
  style="background-color: {ct.color}; {getColorTheme() === ct.id ? `--tw-ring-color: ${ct.color}` : ''}"
354
334
  ></span>
355
335
  <span class="text-xs">{ct.label}</span>
@@ -361,10 +341,9 @@
361
341
  </div>
362
342
  {/if}
363
343
 
364
- <!-- User info -->
365
344
  {#if !collapsed && identity}
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">
345
+ <div class="px-3 pb-3 pt-1">
346
+ <div class="flex items-center gap-2.5 rounded-lg p-2 transition-colors hover:bg-sidebar-accent/40 cursor-pointer">
368
347
  <Avatar
369
348
  src={(identity as Record<string, unknown>).avatar as string | undefined}
370
349
  alt={identity.name ?? 'User'}
@@ -372,36 +351,44 @@
372
351
  size="sm"
373
352
  />
374
353
  <div class="flex-1 min-w-0">
375
- <p class="truncate text-sm font-medium text-sidebar-foreground">{identity.name}</p>
376
- <p class="truncate text-[10px] text-sidebar-foreground/50">{((identity as Record<string, unknown>).role || (identity as Record<string, unknown>).roleName) ?? 'User'}</p>
354
+ <p class="truncate text-[13px] font-medium text-sidebar-foreground">{identity.name}</p>
355
+ <p class="truncate text-[11px] text-sidebar-foreground/45">{((identity as Record<string, unknown>).role || (identity as Record<string, unknown>).roleName) ?? 'User'}</p>
377
356
  </div>
357
+ <button
358
+ class="h-7 w-7 flex items-center justify-center rounded-md text-sidebar-foreground/40 hover:text-sidebar-foreground hover:bg-sidebar-accent/60 transition-colors"
359
+ onclick={onLogout}
360
+ >
361
+ <LogOut class="h-3.5 w-3.5" />
362
+ </button>
378
363
  </div>
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}>
381
- <span class="text-xs font-bold">{localeLabel}</span>
382
- </TooltipButton>
383
- <TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class={footerIconClass}>
384
- {#if getResolvedTheme() === 'dark'}
385
- <Sun class="h-4 w-4" />
386
- {:else}
387
- <Moon class="h-4 w-4" />
388
- {/if}
389
- </TooltipButton>
390
- <TooltipButton tooltip={t('settings.title')} variant="ghost" size="icon-sm" onclick={() => navigate('/settings')} class={footerIconClass}>
391
- <Settings class="h-4 w-4" />
392
- </TooltipButton>
393
- <TooltipButton tooltip={t('common.logout')} variant="ghost" size="icon-sm" onclick={onLogout} class="{footerIconClass} hover:text-destructive">
394
- <LogOut class="h-4 w-4" />
364
+ <div class="mt-1 flex items-center justify-between px-1">
365
+ <div class="flex items-center gap-0.5">
366
+ <TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon-sm" onclick={toggleLocale} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
367
+ <span class="text-[11px] font-semibold">{localeLabel}</span>
368
+ </TooltipButton>
369
+ <TooltipButton tooltip={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">
370
+ {#if getResolvedTheme() === 'dark'}
371
+ <Sun class="h-3.5 w-3.5" />
372
+ {:else}
373
+ <Moon class="h-3.5 w-3.5" />
374
+ {/if}
375
+ </TooltipButton>
376
+ <TooltipButton tooltip={t('settings.title')} variant="ghost" size="icon-sm" onclick={() => navigate('/settings')} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
377
+ <Settings class="h-3.5 w-3.5" />
378
+ </TooltipButton>
379
+ </div>
380
+ <TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
381
+ <ChevronLeft class="h-3.5 w-3.5" />
395
382
  </TooltipButton>
396
383
  </div>
397
384
  </div>
398
385
  {:else if collapsed}
399
- <div class="flex flex-col items-center gap-1 px-3 py-3 transition-all duration-200">
386
+ <div class="flex flex-col items-center gap-0.5 px-2 py-3">
400
387
  <Tooltip.Root>
401
388
  <Tooltip.Trigger>
402
389
  {#snippet child({ props }: { props: Record<string, unknown> })}
403
- <Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class={footerIconClass}>
404
- <span class="text-xs font-bold">{localeLabel}</span>
390
+ <Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
391
+ <span class="text-[11px] font-semibold">{localeLabel}</span>
405
392
  </Button>
406
393
  {/snippet}
407
394
  </Tooltip.Trigger>
@@ -410,11 +397,11 @@
410
397
  <Tooltip.Root>
411
398
  <Tooltip.Trigger>
412
399
  {#snippet child({ props }: { props: Record<string, unknown> })}
413
- <Button {...props} variant="ghost" size="icon" onclick={toggleTheme} class={footerIconClass}>
400
+ <Button {...props} 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">
414
401
  {#if getResolvedTheme() === 'dark'}
415
- <Sun class="h-4 w-4" />
402
+ <Sun class="h-3.5 w-3.5" />
416
403
  {:else}
417
- <Moon class="h-4 w-4" />
404
+ <Moon class="h-3.5 w-3.5" />
418
405
  {/if}
419
406
  </Button>
420
407
  {/snippet}
@@ -424,8 +411,18 @@
424
411
  <Tooltip.Root>
425
412
  <Tooltip.Trigger>
426
413
  {#snippet child({ props }: { props: Record<string, unknown> })}
427
- <Button {...props} variant="ghost" size="icon" onclick={onLogout} class="{footerIconClass} hover:text-destructive">
428
- <LogOut class="h-4 w-4" />
414
+ <Button {...props} variant="ghost" size="icon" onclick={onToggle} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
415
+ <ChevronRight class="h-3.5 w-3.5" />
416
+ </Button>
417
+ {/snippet}
418
+ </Tooltip.Trigger>
419
+ <Tooltip.Content side="right">{t('common.toggleSidebar')}</Tooltip.Content>
420
+ </Tooltip.Root>
421
+ <Tooltip.Root>
422
+ <Tooltip.Trigger>
423
+ {#snippet child({ props }: { props: Record<string, unknown> })}
424
+ <Button {...props} variant="ghost" size="icon" onclick={onLogout} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-destructive">
425
+ <LogOut class="h-3.5 w-3.5" />
429
426
  </Button>
430
427
  {/snippet}
431
428
  </Tooltip.Trigger>
@@ -433,15 +430,15 @@
433
430
  </Tooltip.Root>
434
431
  </div>
435
432
  {:else}
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}>
438
- <span class="text-xs font-bold">{localeLabel}</span>
433
+ <div class="flex justify-center gap-1 p-3">
434
+ <TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon" onclick={toggleLocale} class="h-8 w-8 rounded-md text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground">
435
+ <span class="text-[11px] font-semibold">{localeLabel}</span>
439
436
  </TooltipButton>
440
- <TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class={footerIconClass}>
437
+ <TooltipButton tooltip={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">
441
438
  {#if getResolvedTheme() === 'dark'}
442
- <Sun class="h-4 w-4" />
439
+ <Sun class="h-3.5 w-3.5" />
443
440
  {:else}
444
- <Moon class="h-4 w-4" />
441
+ <Moon class="h-3.5 w-3.5" />
445
442
  {/if}
446
443
  </TooltipButton>
447
444
  </div>
@@ -6,7 +6,6 @@ type $$ComponentProps = {
6
6
  onToggle: () => void;
7
7
  onLogout: () => void;
8
8
  menu?: MenuItem[];
9
- /** 'hash' for SPA hash routing, 'path' for SvelteKit/filesystem routing, 'auto' to detect */
10
9
  routeMode?: 'hash' | 'path' | 'auto';
11
10
  };
12
11
  declare const Sidebar: import("svelte").Component<$$ComponentProps, {}, "">;