@svadmin/ui 0.38.1 → 0.38.5

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 (144) hide show
  1. package/dist/app.css +10 -5
  2. package/dist/app.theme.css +1374 -0
  3. package/dist/components/AICommandBar.svelte +16 -15
  4. package/dist/components/AboutSettings.svelte +12 -11
  5. package/dist/components/AdminApp.svelte +144 -70
  6. package/dist/components/AdminApp.svelte.d.ts +6 -3
  7. package/dist/components/ApiSettings.svelte +19 -16
  8. package/dist/components/AppearanceSettings.svelte +18 -16
  9. package/dist/components/AuditLogDrawer.svelte +6 -3
  10. package/dist/components/AuditLogViewer.svelte +18 -15
  11. package/dist/components/Authenticated.svelte +4 -4
  12. package/dist/components/AutoForm.svelte +19 -16
  13. package/dist/components/AutoSaveIndicator.svelte +7 -5
  14. package/dist/components/AutoTable.svelte +85 -71
  15. package/dist/components/AutoTable.svelte.d.ts +1 -1
  16. package/dist/components/Breadcrumbs.svelte +17 -13
  17. package/dist/components/CancelTaskButton.svelte +5 -2
  18. package/dist/components/CatchAllNavigate.svelte +3 -3
  19. package/dist/components/ChatDialog.svelte +22 -19
  20. package/dist/components/ComboboxField.svelte +6 -4
  21. package/dist/components/CommandPalette.svelte +20 -20
  22. package/dist/components/ConfigErrorScreen.svelte +12 -10
  23. package/dist/components/ConfirmDialog.svelte +6 -4
  24. package/dist/components/CreatePage.svelte +4 -2
  25. package/dist/components/DevTools.svelte +11 -9
  26. package/dist/components/DrawerForm.svelte +5 -2
  27. package/dist/components/EditPage.svelte +8 -5
  28. package/dist/components/EmptyState.svelte +5 -3
  29. package/dist/components/ErrorBoundary.svelte +5 -3
  30. package/dist/components/ErrorComponent.svelte +6 -4
  31. package/dist/components/ErrorPage.svelte +12 -10
  32. package/dist/components/FieldRenderer.svelte +17 -15
  33. package/dist/components/ForgotPasswordPage.svelte +22 -19
  34. package/dist/components/Header.svelte +7 -4
  35. package/dist/components/InfiniteList.svelte +6 -3
  36. package/dist/components/IntegrationsSettings.svelte +9 -6
  37. package/dist/components/KeyboardShortcuts.svelte +4 -2
  38. package/dist/components/Layout.svelte +12 -13
  39. package/dist/components/LazyPage.svelte +47 -0
  40. package/dist/components/LazyPage.svelte.d.ts +14 -0
  41. package/dist/components/ListPage.svelte +8 -5
  42. package/dist/components/LoginPage.svelte +24 -22
  43. package/dist/components/MarkdownRenderer.svelte +89 -35
  44. package/dist/components/ModalForm.svelte +4 -2
  45. package/dist/components/NavigateToResource.svelte +3 -3
  46. package/dist/components/NotificationsSettings.svelte +26 -24
  47. package/dist/components/PasswordInput.svelte +4 -2
  48. package/dist/components/PermissionMatrix.svelte +15 -12
  49. package/dist/components/ProfilePage.svelte +23 -21
  50. package/dist/components/RegisterPage.svelte +21 -19
  51. package/dist/components/ResourceOperationsPage.svelte +3 -2
  52. package/dist/components/RetryTaskButton.svelte +5 -2
  53. package/dist/components/RolesSettings.svelte +10 -8
  54. package/dist/components/SecuritySettings.svelte +26 -23
  55. package/dist/components/SettingsPage.svelte +14 -11
  56. package/dist/components/ShowPage.svelte +9 -7
  57. package/dist/components/Sidebar.svelte +36 -28
  58. package/dist/components/SidebarItem.svelte +4 -4
  59. package/dist/components/SidebarItem.svelte.d.ts +1 -1
  60. package/dist/components/StepsForm.svelte +11 -8
  61. package/dist/components/TaskDetails.svelte +14 -11
  62. package/dist/components/TaskList.svelte +14 -11
  63. package/dist/components/TaskQueueDrawer.svelte +34 -32
  64. package/dist/components/TaskStatusBadge.svelte +5 -2
  65. package/dist/components/Toast.svelte +18 -4
  66. package/dist/components/TooltipButton.svelte +25 -3
  67. package/dist/components/TwoFactorAuthPage.svelte +26 -24
  68. package/dist/components/UndoableNotification.svelte +5 -2
  69. package/dist/components/UpdatePasswordPage.svelte +18 -15
  70. package/dist/components/VirtualTable.svelte +0 -4
  71. package/dist/components/account/CompanyProfilePage.svelte +13 -11
  72. package/dist/components/account/GetStartedPage.svelte +15 -13
  73. package/dist/components/account/ImportMembersPage.svelte +14 -12
  74. package/dist/components/account/MembersStarterPage.svelte +10 -8
  75. package/dist/components/account/SecurityLogPage.svelte +24 -22
  76. package/dist/components/account/SettingsEnterprisePage.svelte +6 -4
  77. package/dist/components/account/SettingsPlainPage.svelte +14 -12
  78. package/dist/components/account/SettingsSidebarPage.svelte +11 -9
  79. package/dist/components/account/TeamMembersPage.svelte +10 -8
  80. package/dist/components/buttons/CloneButton.svelte +4 -2
  81. package/dist/components/buttons/CreateButton.svelte +4 -2
  82. package/dist/components/buttons/DeleteButton.svelte +6 -4
  83. package/dist/components/buttons/EditButton.svelte +4 -2
  84. package/dist/components/buttons/ExportButton.svelte +4 -2
  85. package/dist/components/buttons/ImportButton.svelte +4 -2
  86. package/dist/components/buttons/RefreshButton.svelte +4 -2
  87. package/dist/components/buttons/SaveButton.svelte +4 -2
  88. package/dist/components/buttons/ShowButton.svelte +4 -2
  89. package/dist/components/fieldComponentMap.js +0 -3
  90. package/dist/components/fields/ArrayField.svelte +6 -4
  91. package/dist/components/fields/JsonField.svelte +5 -3
  92. package/dist/components/fields/RelationField.svelte +3 -3
  93. package/dist/components/fields/RichTextField.svelte +4 -2
  94. package/dist/components/network/TeamCrewTablePage.svelte +18 -16
  95. package/dist/components/network/UserCardsNFTPage.svelte +11 -9
  96. package/dist/components/profile/ActivityTimeline.svelte +10 -8
  97. package/dist/components/profile/ProfileCard.svelte +13 -11
  98. package/dist/components/profile/ProjectsGrid.svelte +7 -5
  99. package/dist/components/profile/PublicProfilePage.svelte +18 -16
  100. package/dist/components/profile/TeamsShowcase.svelte +7 -5
  101. package/dist/components/toast-host.svelte.d.ts +5 -0
  102. package/dist/components/toast-host.svelte.js +21 -0
  103. package/dist/components/ui/command/command-dialog.svelte +29 -13
  104. package/dist/components/ui/command/command-dialog.svelte.d.ts +4 -3
  105. package/dist/components/ui/command/command-empty.svelte +18 -0
  106. package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
  107. package/dist/components/ui/command/command-group.svelte +41 -0
  108. package/dist/components/ui/command/command-group.svelte.d.ts +8 -0
  109. package/dist/components/ui/command/command-input.svelte +23 -0
  110. package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
  111. package/dist/components/ui/command/command-item.svelte +43 -0
  112. package/dist/components/ui/command/command-item.svelte.d.ts +11 -0
  113. package/dist/components/ui/command/command-list.svelte +18 -0
  114. package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
  115. package/dist/components/ui/command/command-loading.svelte +12 -0
  116. package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
  117. package/dist/components/ui/command/command-separator.svelte +23 -0
  118. package/dist/components/ui/command/command-separator.svelte.d.ts +7 -0
  119. package/dist/components/ui/command/command.svelte +26 -0
  120. package/dist/components/ui/command/command.svelte.d.ts +4 -0
  121. package/dist/components/ui/command/index.d.ts +55 -2
  122. package/dist/components/ui/command/index.js +21 -2
  123. package/dist/components/ui/sheet/sheet-content.svelte +1 -4
  124. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +1 -3
  125. package/dist/components/ui/tabs/tabs-content.svelte +6 -2
  126. package/dist/components/ui/tabs/tabs-context.svelte.d.ts +5 -0
  127. package/dist/components/ui/tabs/tabs-context.svelte.js +2 -0
  128. package/dist/components/ui/tabs/tabs-trigger.svelte +49 -4
  129. package/dist/components/ui/tabs/tabs.svelte +12 -1
  130. package/dist/components/ui/tooltip/index.d.ts +4 -1
  131. package/dist/components/ui/tooltip/index.js +3 -1
  132. package/dist/components/ui/tooltip/tooltip-content.svelte +308 -35
  133. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +42 -3
  134. package/dist/components/ui/tooltip/tooltip-context.svelte.d.ts +37 -0
  135. package/dist/components/ui/tooltip/tooltip-context.svelte.js +220 -0
  136. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.d.ts +11 -0
  137. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.js +93 -0
  138. package/dist/components/ui/tooltip/tooltip-trigger.svelte +56 -3
  139. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +23 -2
  140. package/dist/components/ui/tooltip/tooltip.svelte +63 -3
  141. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +23 -2
  142. package/dist/router-state.svelte.d.ts +13 -0
  143. package/dist/router-state.svelte.js +72 -37
  144. package/package.json +18 -12
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
- import { useLogin, getAuthProvider } from '@svadmin/core';
3
- import { t } from '@svadmin/core/i18n';
4
- import { navigate } from '@svadmin/core/router';
2
+ import { captureAdminContext, useLogin, getAuthProvider } from '@svadmin/core';
3
+ import { useTranslation } from '@svadmin/core/i18n';
5
4
  import { Button } from './ui/button/index.js';
6
5
  import { Input } from './ui/input/index.js';
7
6
  import { Label } from './ui/label/index.js';
@@ -10,6 +9,8 @@
10
9
  import { Separator } from './ui/separator/index.js';
11
10
  import { User, Loader2, AlertCircle, Shield } from '@lucide/svelte';
12
11
 
12
+ const i18n = useTranslation();
13
+
13
14
  interface SocialProvider {
14
15
  name: string;
15
16
  icon?: string;
@@ -32,6 +33,7 @@
32
33
  loginHint?: string;
33
34
  }>();
34
35
 
36
+ const adminContext = captureAdminContext();
35
37
  const login = useLogin({ errorMessage: false });
36
38
  const authProvider = getAuthProvider();
37
39
 
@@ -51,14 +53,14 @@
51
53
  e.preventDefault();
52
54
  error = '';
53
55
 
54
- if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
55
- if (!password) { error = t('auth.passwordRequired'); return; }
56
+ if (!identifier) { error = i18n.t('auth.usernameOrEmailRequired'); return; }
57
+ if (!password) { error = i18n.t('auth.passwordRequired'); return; }
56
58
 
57
59
  const result = await login.mutate({ email: identifier, username: identifier, password });
58
60
  if (result.success) {
59
61
  onSuccess?.();
60
62
  } else {
61
- error = result.error?.message ?? t('common.loginFailed');
63
+ error = result.error?.message ?? i18n.t('common.loginFailed');
62
64
  }
63
65
  }
64
66
  </script>
@@ -83,10 +85,10 @@
83
85
 
84
86
  <div class="max-w-[360px] space-y-4">
85
87
  <h2 class="text-4xl font-bold text-white leading-tight">
86
- {t('auth.welcomeBack')}
88
+ {i18n.t('auth.welcomeBack')}
87
89
  </h2>
88
90
  <p class="text-lg text-white/75 leading-relaxed">
89
- {t('auth.welcomeMessage')}
91
+ {i18n.t('auth.welcomeMessage')}
90
92
  </p>
91
93
  </div>
92
94
 
@@ -109,10 +111,10 @@
109
111
 
110
112
  <div class="space-y-2.5">
111
113
  <h1 class="text-3xl font-semibold tracking-tight text-foreground">
112
- {t('auth.welcomeBack')}
114
+ {i18n.t('auth.welcomeBack')}
113
115
  </h1>
114
116
  <p class="text-base text-muted-foreground leading-relaxed">
115
- {t('auth.welcomeMessage')}
117
+ {i18n.t('auth.welcomeMessage')}
116
118
  </p>
117
119
  </div>
118
120
 
@@ -125,7 +127,7 @@
125
127
  {#if defaultIdentifier}
126
128
  <div class="rounded-xl border border-border/70 bg-background/80 px-3 py-2">
127
129
  <p class="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
128
- {t('auth.usernameOrEmail')}
130
+ {i18n.t('auth.usernameOrEmail')}
129
131
  </p>
130
132
  <p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultIdentifier}</p>
131
133
  </div>
@@ -133,7 +135,7 @@
133
135
  {#if defaultPassword}
134
136
  <div class="rounded-xl border border-border/70 bg-background/80 px-3 py-2">
135
137
  <p class="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
136
- {t('auth.password')}
138
+ {i18n.t('auth.password')}
137
139
  </p>
138
140
  <p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultPassword}</p>
139
141
  </div>
@@ -152,14 +154,14 @@
152
154
 
153
155
  <div class="space-y-2.5">
154
156
  <Label for="login-identifier" class="text-sm font-semibold text-foreground/80 pl-0.5">
155
- {t('auth.usernameOrEmail')}
157
+ {i18n.t('auth.usernameOrEmail')}
156
158
  </Label>
157
159
  <div class="relative">
158
160
  <User class="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground pointer-events-none z-[1]" />
159
161
  <Input
160
162
  id="login-identifier"
161
163
  type="text"
162
- placeholder={t('auth.identifierPlaceholder')}
164
+ placeholder={i18n.t('auth.identifierPlaceholder')}
163
165
  bind:value={identifier}
164
166
  class="pl-12"
165
167
  autocomplete="username"
@@ -170,11 +172,11 @@
170
172
  <div class="space-y-2.5">
171
173
  <div class="flex items-center justify-between pl-0.5">
172
174
  <Label for="login-password" class="text-sm font-semibold text-foreground/80">
173
- {t('auth.password')}
175
+ {i18n.t('auth.password')}
174
176
  </Label>
175
177
  {#if authProvider.forgotPassword}
176
- <Button variant="link" class="text-xs h-auto p-0 font-medium hover:text-primary transition-colors" onclick={() => navigate('/forgot-password')}>
177
- {t('auth.forgotPasswordLink')}
178
+ <Button variant="link" class="text-xs h-auto p-0 font-medium hover:text-primary transition-colors" onclick={() => adminContext.navigate('/forgot-password')}>
179
+ {i18n.t('auth.forgotPasswordLink')}
178
180
  </Button>
179
181
  {/if}
180
182
  </div>
@@ -190,7 +192,7 @@
190
192
  {#if login.isLoading}
191
193
  <Loader2 class="h-5 w-5 animate-spin mr-2" />
192
194
  {/if}
193
- {t('auth.loginButton')}
195
+ {i18n.t('auth.loginButton')}
194
196
  </Button>
195
197
  </form>
196
198
 
@@ -198,7 +200,7 @@
198
200
  <div class="relative my-8">
199
201
  <Separator />
200
202
  <span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-4 text-xs font-semibold text-muted-foreground tracking-wider uppercase">
201
- {t('auth.orContinueWith')}
203
+ {i18n.t('auth.orContinueWith')}
202
204
  </span>
203
205
  </div>
204
206
 
@@ -220,9 +222,9 @@
220
222
 
221
223
  {#if authProvider.register}
222
224
  <div class="flex items-center justify-center gap-1.5 mt-8 pt-6 border-t border-border/60">
223
- <span class="text-sm text-muted-foreground">{t('auth.noAccount')}</span>
224
- <Button variant="link" class="text-sm h-auto p-0 font-semibold text-primary hover:underline" onclick={() => navigate('/register')}>
225
- {t('auth.register')}
225
+ <span class="text-sm text-muted-foreground">{i18n.t('auth.noAccount')}</span>
226
+ <Button variant="link" class="text-sm h-auto p-0 font-semibold text-primary hover:underline" onclick={() => adminContext.navigate('/register')}>
227
+ {i18n.t('auth.register')}
226
228
  </Button>
227
229
  </div>
228
230
  {/if}
@@ -26,7 +26,7 @@
26
26
 
27
27
  let { content, streaming = false, class: className = "" }: Props = $props();
28
28
 
29
- const hasMarkdownDeps = !!(Marked && Object.keys(DOMPurify || {}).length > 0);
29
+ const hasMarkdownDeps = typeof Marked === "function" && typeof DOMPurify?.sanitize === "function";
30
30
 
31
31
  // Configure marked with syntax highlighting if available
32
32
  const markedObj = hasMarkdownDeps ? new Marked(
@@ -69,47 +69,101 @@
69
69
  }, 2000);
70
70
  }
71
71
 
72
+ function createCopyIcon(): SVGSVGElement {
73
+ const svgNamespace = "http://www.w3.org/2000/svg";
74
+ const svg = document.createElementNS(svgNamespace, "svg");
75
+ svg.setAttribute("width", "14");
76
+ svg.setAttribute("height", "14");
77
+ svg.setAttribute("viewBox", "0 0 24 24");
78
+ svg.setAttribute("fill", "none");
79
+ svg.setAttribute("stroke", "currentColor");
80
+ svg.setAttribute("stroke-width", "2");
81
+ svg.setAttribute("stroke-linecap", "round");
82
+ svg.setAttribute("stroke-linejoin", "round");
83
+ svg.setAttribute("aria-hidden", "true");
84
+ svg.classList.add("lucide", "lucide-copy");
85
+
86
+ const rect = document.createElementNS(svgNamespace, "rect");
87
+ rect.setAttribute("width", "14");
88
+ rect.setAttribute("height", "14");
89
+ rect.setAttribute("x", "8");
90
+ rect.setAttribute("y", "8");
91
+ rect.setAttribute("rx", "2");
92
+ rect.setAttribute("ry", "2");
93
+ svg.appendChild(rect);
94
+
95
+ const path = document.createElementNS(svgNamespace, "path");
96
+ path.setAttribute("d", "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2");
97
+ svg.appendChild(path);
98
+
99
+ return svg;
100
+ }
101
+
72
102
  // Inject copy buttons into the HTML after rendering
73
103
  // We do this via action instead of raw string manipulation for safety
74
104
  function enhanceCodeBlocks(node: HTMLElement, _: string) {
105
+ let destroyed = false;
106
+ let enhancementScheduled = false;
107
+
108
+ function enhance() {
109
+ if (destroyed) return;
110
+
111
+ // Find all pre > code blocks that don't have wrappers yet
112
+ const pres = node.querySelectorAll("pre:not(.enhanced)");
113
+ pres.forEach((pre) => {
114
+ pre.classList.add("enhanced");
115
+ const wrapper = document.createElement("div");
116
+ wrapper.className =
117
+ "code-block-wrapper group relative my-4 rounded-md bg-foreground/95";
118
+
119
+ const header = document.createElement("div");
120
+ header.className =
121
+ "flex items-center justify-between px-4 py-2 text-xs text-zinc-400 border-b border-zinc-800";
122
+
123
+ const lang =
124
+ Array.from(pre.querySelector("code")?.classList || [])
125
+ .find((className) => className.startsWith("language-"))
126
+ ?.replace("language-", "") || "Code";
127
+ const languageLabel = document.createElement("span");
128
+ languageLabel.textContent = lang;
129
+
130
+ const copyButton = document.createElement("button");
131
+ copyButton.type = "button";
132
+ copyButton.className =
133
+ "copy-btn hover:text-white transition-colors flex items-center gap-1";
134
+ copyButton.setAttribute("aria-label", "Copy code");
135
+ copyButton.appendChild(createCopyIcon());
136
+
137
+ header.append(languageLabel, copyButton);
138
+ pre.replaceWith(wrapper);
139
+ wrapper.appendChild(header);
140
+
141
+ const scrollContainer = document.createElement("div");
142
+ scrollContainer.className = "overflow-x-auto p-4";
143
+ scrollContainer.appendChild(pre);
144
+ wrapper.appendChild(scrollContainer);
145
+ });
146
+ }
147
+
148
+ function scheduleEnhancement() {
149
+ if (enhancementScheduled || destroyed) return;
150
+ enhancementScheduled = true;
151
+ queueMicrotask(() => {
152
+ enhancementScheduled = false;
153
+ enhance();
154
+ });
155
+ }
156
+
157
+ node.addEventListener("click", handleCopy);
158
+ enhance();
159
+ scheduleEnhancement();
160
+
75
161
  return {
76
162
  update(_newHtml: string) {
77
- // Find all pre > code blocks that don't have wrappers yet
78
- const pres = node.querySelectorAll("pre:not(.enhanced)");
79
- pres.forEach((pre) => {
80
- pre.classList.add("enhanced");
81
- const wrapper = document.createElement("div");
82
- wrapper.className =
83
- "code-block-wrapper group relative my-4 rounded-md bg-foreground/95";
84
-
85
- const header = document.createElement("div");
86
- header.className =
87
- "flex items-center justify-between px-4 py-2 text-xs text-zinc-400 border-b border-zinc-800";
88
-
89
- const lang =
90
- Array.from(pre.querySelector("code")?.classList || [])
91
- .find((c) => c.startsWith("language-"))
92
- ?.replace("language-", "") || "Code";
93
- header.innerHTML = `
94
- <span>${lang}</span>
95
- <button class="copy-btn hover:text-white transition-colors flex items-center gap-1">
96
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
97
- </button>
98
- `;
99
-
100
- pre.replaceWith(wrapper);
101
- wrapper.appendChild(header);
102
-
103
- const scrollContainer = document.createElement("div");
104
- scrollContainer.className = "overflow-x-auto p-4";
105
- scrollContainer.appendChild(pre);
106
- wrapper.appendChild(scrollContainer);
107
- });
108
-
109
- // Add event listener for copy buttons
110
- node.addEventListener("click", handleCopy);
163
+ scheduleEnhancement();
111
164
  },
112
165
  destroy() {
166
+ destroyed = true;
113
167
  node.removeEventListener("click", handleCopy);
114
168
  },
115
169
  };
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { getResource } from '@svadmin/core';
3
- import { t } from '@svadmin/core/i18n';
3
+ import { useTranslation } from '@svadmin/core/i18n';
4
4
  import * as Dialog from './ui/dialog/index.js';
5
5
  import AutoForm from './AutoForm.svelte';
6
6
 
7
+ const i18n = useTranslation();
8
+
7
9
  let { resourceName, mode = 'create', id, open = $bindable(false), onSuccess } = $props<{
8
10
  resourceName: string;
9
11
  mode?: 'create' | 'edit';
@@ -27,7 +29,7 @@
27
29
  <Dialog.DialogContent class="{dialogWidth} max-h-[85vh] overflow-y-auto">
28
30
  <Dialog.DialogHeader>
29
31
  <Dialog.DialogTitle>
30
- {mode === 'create' ? `${t('common.create')}${resource.label}` : `${t('common.edit')}${resource.label}`}
32
+ {mode === 'create' ? `${i18n.t('common.create')}${resource.label}` : `${i18n.t('common.edit')}${resource.label}`}
31
33
  </Dialog.DialogTitle>
32
34
  </Dialog.DialogHeader>
33
35
  <AutoForm {resourceName} {mode} {id} onSuccess={handleSuccess} />
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import { useMenu } from '@svadmin/core';
3
- import { navigate } from '@svadmin/core/router';
2
+ import { captureAdminContext, useMenu } from '@svadmin/core';
4
3
 
4
+ const adminContext = captureAdminContext();
5
5
  const { menuItems } = useMenu();
6
6
 
7
7
  let navigated = false;
@@ -9,7 +9,7 @@
9
9
  // Navigate to first resource ONCE — avoid infinite re-triggers
10
10
  if (!navigated && menuItems.length > 0) {
11
11
  navigated = true;
12
- navigate(menuItems[0].route);
12
+ adminContext.navigate(menuItems[0].route);
13
13
  }
14
14
  });
15
15
  </script>
@@ -1,11 +1,13 @@
1
1
  <script lang="ts">
2
- import { t } from '@svadmin/core/i18n';
2
+ import { useTranslation } from '@svadmin/core/i18n';
3
3
  import { Bell, Mail, Smartphone } from '@lucide/svelte';
4
4
  import * as Card from './ui/card/index.js';
5
5
  import { Button } from './ui/button/index.js';
6
6
  import { Label } from './ui/label/index.js';
7
7
  import { Switch } from './ui/switch/index.js';
8
8
 
9
+ const i18n = useTranslation();
10
+
9
11
  let emailAlerts = $state({ security: true, activity: false, reports: true });
10
12
  let pushAlerts = $state({ security: true, activity: true, reports: false });
11
13
  let smsAlerts = $state({ security: true });
@@ -22,39 +24,39 @@
22
24
  <div class="space-y-6">
23
25
  <div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-start">
24
26
  <div>
25
- <h2 class="text-xl font-semibold text-foreground">{t('settings.notifications')}</h2>
26
- <p class="mt-1 text-sm text-muted-foreground">{t('settings.notificationsDescription')}</p>
27
+ <h2 class="text-xl font-semibold text-foreground">{i18n.t('settings.notifications')}</h2>
28
+ <p class="mt-1 text-sm text-muted-foreground">{i18n.t('settings.notificationsDescription')}</p>
27
29
  </div>
28
- <Button onclick={saveSettings}>{isSaved ? t('common.autoSaved') : t('common.save')}</Button>
30
+ <Button onclick={saveSettings}>{isSaved ? i18n.t('common.autoSaved') : i18n.t('common.save')}</Button>
29
31
  </div>
30
32
 
31
33
  <Card.Card>
32
34
  <Card.CardHeader>
33
35
  <Card.CardTitle class="flex items-center gap-2 text-base">
34
36
  <Mail class="h-4 w-4 text-muted-foreground" />
35
- {t('notifications.emailTitle')}
37
+ {i18n.t('notifications.emailTitle')}
36
38
  </Card.CardTitle>
37
- <Card.CardDescription>{t('notifications.emailDescription')}</Card.CardDescription>
39
+ <Card.CardDescription>{i18n.t('notifications.emailDescription')}</Card.CardDescription>
38
40
  </Card.CardHeader>
39
41
  <Card.CardContent class="space-y-4">
40
42
  <div class="flex items-center justify-between gap-4 border-b py-2">
41
43
  <div class="space-y-1">
42
- <Label for="email-security">{t('notifications.securityAlerts')}</Label>
43
- <p class="text-xs text-muted-foreground">{t('notifications.securityAlertsDesc')}</p>
44
+ <Label for="email-security">{i18n.t('notifications.securityAlerts')}</Label>
45
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.securityAlertsDesc')}</p>
44
46
  </div>
45
47
  <Switch id="email-security" bind:checked={emailAlerts.security} />
46
48
  </div>
47
49
  <div class="flex items-center justify-between gap-4 border-b py-2">
48
50
  <div class="space-y-1">
49
- <Label for="email-activity">{t('notifications.activityLogs')}</Label>
50
- <p class="text-xs text-muted-foreground">{t('notifications.activityLogsDesc')}</p>
51
+ <Label for="email-activity">{i18n.t('notifications.activityLogs')}</Label>
52
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.activityLogsDesc')}</p>
51
53
  </div>
52
54
  <Switch id="email-activity" bind:checked={emailAlerts.activity} />
53
55
  </div>
54
56
  <div class="flex items-center justify-between gap-4 py-2">
55
57
  <div class="space-y-1">
56
- <Label for="email-reports">{t('notifications.systemAlerts')}</Label>
57
- <p class="text-xs text-muted-foreground">{t('notifications.systemAlertsDesc')}</p>
58
+ <Label for="email-reports">{i18n.t('notifications.systemAlerts')}</Label>
59
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.systemAlertsDesc')}</p>
58
60
  </div>
59
61
  <Switch id="email-reports" bind:checked={emailAlerts.reports} />
60
62
  </div>
@@ -65,29 +67,29 @@
65
67
  <Card.CardHeader>
66
68
  <Card.CardTitle class="flex items-center gap-2 text-base">
67
69
  <Bell class="h-4 w-4 text-muted-foreground" />
68
- {t('notifications.pushTitle')}
70
+ {i18n.t('notifications.pushTitle')}
69
71
  </Card.CardTitle>
70
- <Card.CardDescription>{t('notifications.pushDescription')}</Card.CardDescription>
72
+ <Card.CardDescription>{i18n.t('notifications.pushDescription')}</Card.CardDescription>
71
73
  </Card.CardHeader>
72
74
  <Card.CardContent class="space-y-4">
73
75
  <div class="flex items-center justify-between gap-4 border-b py-2">
74
76
  <div class="space-y-1">
75
- <Label for="push-security">{t('notifications.securityAlerts')}</Label>
76
- <p class="text-xs text-muted-foreground">{t('notifications.securityPushDesc')}</p>
77
+ <Label for="push-security">{i18n.t('notifications.securityAlerts')}</Label>
78
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.securityPushDesc')}</p>
77
79
  </div>
78
80
  <Switch id="push-security" bind:checked={pushAlerts.security} />
79
81
  </div>
80
82
  <div class="flex items-center justify-between gap-4 border-b py-2">
81
83
  <div class="space-y-1">
82
- <Label for="push-activity">{t('notifications.activityLogs')}</Label>
83
- <p class="text-xs text-muted-foreground">{t('notifications.activityPushDesc')}</p>
84
+ <Label for="push-activity">{i18n.t('notifications.activityLogs')}</Label>
85
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.activityPushDesc')}</p>
84
86
  </div>
85
87
  <Switch id="push-activity" bind:checked={pushAlerts.activity} />
86
88
  </div>
87
89
  <div class="flex items-center justify-between gap-4 py-2">
88
90
  <div class="space-y-1">
89
- <Label for="push-reports">{t('notifications.systemAlerts')}</Label>
90
- <p class="text-xs text-muted-foreground">{t('notifications.systemPushDesc')}</p>
91
+ <Label for="push-reports">{i18n.t('notifications.systemAlerts')}</Label>
92
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.systemPushDesc')}</p>
91
93
  </div>
92
94
  <Switch id="push-reports" bind:checked={pushAlerts.reports} />
93
95
  </div>
@@ -98,15 +100,15 @@
98
100
  <Card.CardHeader>
99
101
  <Card.CardTitle class="flex items-center gap-2 text-base">
100
102
  <Smartphone class="h-4 w-4 text-muted-foreground" />
101
- {t('notifications.smsTitle')}
103
+ {i18n.t('notifications.smsTitle')}
102
104
  </Card.CardTitle>
103
- <Card.CardDescription>{t('notifications.smsDescription')}</Card.CardDescription>
105
+ <Card.CardDescription>{i18n.t('notifications.smsDescription')}</Card.CardDescription>
104
106
  </Card.CardHeader>
105
107
  <Card.CardContent>
106
108
  <div class="flex items-center justify-between gap-4 py-2">
107
109
  <div class="space-y-1">
108
- <Label for="sms-security">{t('notifications.criticalSecurity')}</Label>
109
- <p class="text-xs text-muted-foreground">{t('notifications.criticalSecurityDesc')}</p>
110
+ <Label for="sms-security">{i18n.t('notifications.criticalSecurity')}</Label>
111
+ <p class="text-xs text-muted-foreground">{i18n.t('notifications.criticalSecurityDesc')}</p>
110
112
  </div>
111
113
  <Switch id="sms-security" bind:checked={smsAlerts.security} />
112
114
  </div>
@@ -4,7 +4,9 @@
4
4
  import TooltipButton from './TooltipButton.svelte';
5
5
  import { Label } from './ui/label/index.js';
6
6
  import { Lock, Eye, EyeOff } from '@lucide/svelte';
7
- import { t } from '@svadmin/core/i18n';
7
+ import { useTranslation } from '@svadmin/core/i18n';
8
+
9
+ const i18n = useTranslation();
8
10
 
9
11
  interface Props {
10
12
  id: string;
@@ -66,7 +68,7 @@
66
68
  autocomplete={autocomplete as any}
67
69
  />
68
70
  <TooltipButton
69
- tooltip={showPassword ? t('common.hidePassword') : t('common.showPassword')}
71
+ tooltip={showPassword ? i18n.t('common.hidePassword') : i18n.t('common.showPassword')}
70
72
  variant="ghost"
71
73
  size="icon"
72
74
  type="button"
@@ -2,7 +2,10 @@
2
2
  import { Shield, AlertCircle, Check } from '@lucide/svelte';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
5
- import { t } from '@svadmin/core/i18n';
5
+ import { useTranslation } from '@svadmin/core/i18n';
6
+
7
+ const i18n = useTranslation();
8
+
6
9
 
7
10
  let {
8
11
  // Basic metadata lists
@@ -57,9 +60,9 @@
57
60
  <div>
58
61
  <h1 class="text-2xl font-bold text-foreground flex items-center gap-2">
59
62
  <Shield class="w-6 h-6 text-primary" />
60
- {t('permissions.title') || 'Permission Matrix'}
63
+ {i18n.t('permissions.title') || 'Permission Matrix'}
61
64
  </h1>
62
- <p class="text-muted-foreground mt-1 text-sm">{t('permissions.description') || 'Configure authorization rules for system modules and roles.'}</p>
65
+ <p class="text-muted-foreground mt-1 text-sm">{i18n.t('permissions.description') || 'Configure authorization rules for system modules and roles.'}</p>
63
66
  </div>
64
67
 
65
68
  <!-- Toast Feedback -->
@@ -78,7 +81,7 @@
78
81
  <div class="flex flex-col md:flex-row gap-6 flex-1 min-h-[500px]">
79
82
  <!-- Left: Role Selector Sidebar -->
80
83
  <div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
81
- <h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{t('permissions.roles') || 'Roles'}</h2>
84
+ <h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{i18n.t('permissions.roles') || 'Roles'}</h2>
82
85
  <div class="space-y-1 flex-1 overflow-y-auto">
83
86
  {#each roles as role, _i (_i)}
84
87
  <button
@@ -90,7 +93,7 @@
90
93
  </button>
91
94
  {/each}
92
95
  {#if roles.length === 0}
93
- <p class="text-center text-muted-foreground text-sm p-4">{t('permissions.noRoles') || 'No roles available'}</p>
96
+ <p class="text-center text-muted-foreground text-sm p-4">{i18n.t('permissions.noRoles') || 'No roles available'}</p>
94
97
  {/if}
95
98
  </div>
96
99
 
@@ -108,12 +111,12 @@
108
111
  <div class="p-5 border-b border-border bg-muted/30 flex items-center justify-between">
109
112
  <div class="flex items-center gap-4">
110
113
  <div>
111
- <h2 class="text-lg font-bold text-foreground">{t('permissions.currentRole') || 'Current role'}:
112
- <span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (t('permissions.noneSelected') || 'None selected')}</span>
114
+ <h2 class="text-lg font-bold text-foreground">{i18n.t('permissions.currentRole') || 'Current role'}:
115
+ <span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (i18n.t('permissions.noneSelected') || 'None selected')}</span>
113
116
  </h2>
114
- <p class="text-xs text-muted-foreground mt-1">{t('permissions.toggleHint') || 'Toggle checkboxes to grant or revoke permissions'}</p>
117
+ <p class="text-xs text-muted-foreground mt-1">{i18n.t('permissions.toggleHint') || 'Toggle checkboxes to grant or revoke permissions'}</p>
115
118
  </div>
116
-
119
+
117
120
  <!-- Slot for header extra logic -->
118
121
  {#if headerExtra}
119
122
  {@render headerExtra()}
@@ -122,7 +125,7 @@
122
125
 
123
126
  {#if conflictCount > 0}
124
127
  <div class="px-3 py-1.5 bg-destructive/10 text-destructive rounded-lg text-xs font-medium ring-1 ring-destructive/20 flex items-center gap-1.5">
125
- <AlertCircle class="w-4 h-4" /> {conflictCount} {t('permissions.conflicts') || 'conflicting rules'}
128
+ <AlertCircle class="w-4 h-4" /> {conflictCount} {i18n.t('permissions.conflicts') || 'conflicting rules'}
126
129
  </div>
127
130
  {/if}
128
131
  </div>
@@ -132,7 +135,7 @@
132
135
  <table class="min-w-full rounded-lg overflow-hidden shadow-sm ring-1 ring-border/10">
133
136
  <thead class="bg-muted/50">
134
137
  <tr>
135
- <th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{t('permissions.resources') || 'Resources'}</th>
138
+ <th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{i18n.t('permissions.resources') || 'Resources'}</th>
136
139
  {#each actions as action, _i (_i)}
137
140
  <th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
138
141
  <div class="flex flex-col items-center">
@@ -146,7 +149,7 @@
146
149
  <tbody class="bg-card">
147
150
  {#if resources.length === 0}
148
151
  <tr>
149
- <td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{t('permissions.noResources') || 'No resources available'}</td>
152
+ <td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{i18n.t('permissions.noResources') || 'No resources available'}</td>
150
153
  </tr>
151
154
  {/if}
152
155
  {#each resources as resource, i (i)}